/* PSPP - a program for statistical analysis.
   Copyright (C) 2008, 2009, 2011, 2012, 2016 Free Software Foundation, Inc.

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>. */

#include <config.h>

#include "data/attributes.h"

#include <assert.h>
#include <string.h>

#include "libpspp/array.h"
#include "libpspp/compiler.h"
#include "libpspp/hash-functions.h"
#include "libpspp/i18n.h"

#include "gl/xalloc.h"

/* A custom attribute of the sort maintained by the DATAFILE
   ATTRIBUTE and VARIABLE ATTRIBUTE commands.

   Attributes have a name (the rules for which are the same as
   those for PSPP variable names) and one or more values, each of
   which is a string.  An attribute may be part of one attribute
   set. */
struct attribute
  {
    struct hmap_node node;      /* Used by attrset. */
    char *name;                 /* Name. */
    char **values;              /* Each value. */
    size_t n_values;            /* Number of values. */
    size_t allocated_values;    /* Amount of allocated space for values. */
  };

/* Creates and returns a new attribute with the given NAME.  The
   attribute initially has no values.  (Attributes with no values
   cannot be saved to system files, so at least one value should
   be added before the attribute is made available to the PSPP
   user.) */
struct attribute *
attribute_create (const char *name)
{
  struct attribute *attr = xmalloc (sizeof *attr);
  attr->name = xstrdup (name);
  attr->values = NULL;
  attr->n_values = 0;
  attr->allocated_values = 0;
  return attr;
}

/* Creates and returns a new attribute with the same name and
   values as ORIG. */
struct attribute *
attribute_clone (const struct attribute *orig)
{
  struct attribute *attr;
  size_t i;

  attr = attribute_create (orig->name);
  for (i = 0; i < orig->n_values; i++)
    attribute_add_value (attr, orig->values[i]);
  return attr;
}

/* Destroys ATTR and frees all associated memory.

   This function must not be called if ATTR is part of an
   attribute set.  Use attrset_delete() instead. */
void
attribute_destroy (struct attribute *attr)
{
  if (attr != NULL)
    {
      size_t i;

      for (i = 0; i < attr->n_values; i++)
        free (attr->values[i]);
      free (attr->values);
      free (attr->name);
      free (attr);
    }
}

/* Returns the name of ATTR.  The caller must not free or modify
   the returned string. */
const char *
attribute_get_name (const struct attribute *attr)
{
  return attr->name;
}

/* Returns ATTR's value with the given INDEX, or a null pointer
   if INDEX is greater than or equal to the number of values in
   ATTR (that is, attributes are numbered starting from 0).  The
   caller must not free or modify the returned string.  */
const char *
attribute_get_value (const struct attribute *attr, size_t index)
{
  return index < attr->n_values ? attr->values[index] : NULL;
}

/* Returns ATTR's number of values. */
size_t
attribute_get_n_values (const struct attribute *attrs)
{
  return attrs->n_values;
}

/* Adds a copy of VALUE as a new value to ATTR.  The caller
   retains ownership of VALUE. */
void
attribute_add_value (struct attribute *attr, const char *value)
{
  if (attr->n_values >= attr->allocated_values)
    attr->values = x2nrealloc (attr->values, &attr->allocated_values,
                               sizeof *attr->values);
  attr->values[attr->n_values++] = xstrdup (value);
}

/* Adds or replaces the value with the given INDEX in ATTR by a
   copy of VALUE.  The caller retains ownership of VALUE.

   If INDEX is an existing value index, that value is replaced.
   If no value index numbered INDEX exists in ATTR, then it is
   added, and any values intermediate between the last maximum
   index and INDEX are set to the empty string. */
void
attribute_set_value (struct attribute *attr, size_t index, const char *value)
{
  if (index < attr->n_values)
    {
      /* Replace existing value. */
      free (attr->values[index]);
      attr->values[index] = xstrdup (value);
    }
  else
    {
      /* Add new value. */
      while (index > attr->n_values)
        attribute_add_value (attr, "");
      attribute_add_value (attr, value);
    }

}

/* Deletes the value with the given INDEX from ATTR.  Any values
   with higher-numbered indexes are shifted down into the gap
   that this creates.

   If INDEX is greater than the maximum index, this has no effect.*/
void
attribute_del_value (struct attribute *attr, size_t index)
{
  if (index < attr->n_values)
    {
      free (attr->values[index]);
      remove_element (attr->values, attr->n_values, sizeof *attr->values,
                      index);
      attr->n_values--;
    }
}

/* Initializes SET as a new, initially empty attibute set. */
void
attrset_init (struct attrset *set)
{
  hmap_init (&set->map);
}

/* Initializes NEW_SET as a new attribute set whose contents are
   initially the same as that of OLD_SET. */
void
attrset_clone (struct attrset *new_set, const struct attrset *old_set)
{
  struct attribute *old_attr;

  attrset_init (new_set);
  HMAP_FOR_EACH (old_attr, struct attribute, node, &old_set->map)
    {
      struct attribute *new_attr = attribute_clone (old_attr);
      hmap_insert (&new_set->map, &new_attr->node,
                   hmap_node_hash (&old_attr->node));
    }
}

/* Frees the storage associated with SET, if SET is nonnull.
   (Does not free SET itself.) */
void
attrset_destroy (struct attrset *set)
{
  if (set != NULL)
    {
      struct attribute *attr, *next;

      HMAP_FOR_EACH_SAFE (attr, next, struct attribute, node, &set->map)
        attribute_destroy (attr);
      hmap_destroy (&set->map);
    }
}

/* Returns the number of attributes in SET. */
size_t
attrset_count (const struct attrset *set)
{
  return hmap_count (&set->map);
}

/* Returns the attribute in SET whose name matches NAME
   case-insensitively, or a null pointer if SET does not contain
   an attribute with that name. */
struct attribute *
attrset_lookup (const struct attrset *set, const char *name)
{
  const struct attribute *attr;
  HMAP_FOR_EACH_WITH_HASH (attr, struct attribute, node,
                           utf8_hash_case_string (name, 0), &set->map)
    if (!utf8_strcasecmp (attribute_get_name (attr), name))
      break;
  return CONST_CAST (struct attribute *, attr);
}

/* Adds ATTR to SET.  Succeeds and returns true if SET does not already contain
   an attribute with the same name (matched case insensitively); otherwise
   fails and returns false.  On success only, ownership of ATTR is transferred
   to SET. */
bool
attrset_try_add (struct attrset *set, struct attribute *attr)
{
  const char *name = attribute_get_name (attr);
  if (attrset_lookup (set, name))
    return false;
  hmap_insert (&set->map, &attr->node, utf8_hash_case_string (name, 0));
  return true;
}

/* Adds ATTR to SET, which must not already contain an attribute
   with the same name (matched case insensitively).  Ownership of
   ATTR is transferred to SET. */
void
attrset_add (struct attrset *set, struct attribute *attr)
{
  bool ok UNUSED = attrset_try_add (set, attr);
  assert (ok);
}

/* Deletes any attribute from SET that matches NAME
   (case-insensitively). */
void
attrset_delete (struct attrset *set, const char *name)
{
  struct attribute *attr = attrset_lookup (set, name);
  if (attr != NULL)
    {
      hmap_delete (&set->map, &attr->node);
      attribute_destroy (attr);
    }
}

/* Deletes all attributes from SET. */
void
attrset_clear (struct attrset *set)
{
  attrset_destroy (set);
  attrset_init (set);
}

static struct attribute *iterator_data (struct attrset_iterator *iterator)
{
  return HMAP_NULLABLE_DATA (iterator->node, struct attribute, node);
}

/* Returns the first attribute in SET and initializes ITERATOR.
   If SET is empty, returns a null pointer.

   The caller must not destroy the returned attribute, but it may
   add or remove values.

   Attributes are visited in no particular order.  Calling
   attrset_add() during iteration can cause some attributes to
   be visited more than once and others not at all. */
struct attribute *
attrset_first (const struct attrset *set, struct attrset_iterator *iterator)
{
  iterator->node = hmap_first (&set->map);
  return iterator_data (iterator);
}

/* Returns the next attribute in SET and advances ITERATOR, which
   should have been initialized by calling attrset_first().  If
   all the attributes in SET have already been visited, returns a
   null pointer.

   The caller must not destroy the returned attribute, but it may
   add or remove values.

   Attributes are visited in no particular order.  Calling
   attrset_add() during iteration can cause some attributes to
   be visited more than once and others not at all. */
struct attribute *
attrset_next (const struct attrset *set, struct attrset_iterator *iterator)
{
  iterator->node = hmap_next (&set->map, iterator->node);
  return iterator_data (iterator);
}

static int
compare_attribute_by_name (const void *a_, const void *b_)
{
  const struct attribute *const *a = a_;
  const struct attribute *const *b = b_;

  return strcmp ((*a)->name, (*b)->name);
}

/* Allocates and returns an array of pointers to attributes
   that is sorted by attribute name.  The array has
   'attrset_count (SET)' elements.  The caller is responsible for
   freeing the array. */
struct attribute **
attrset_sorted (const struct attrset *set)
{
  if (set != NULL && attrset_count (set) > 0)
    {
      struct attribute **attrs;
      struct attribute *attr;
      size_t i;

      attrs = xmalloc (attrset_count (set) * sizeof *attrs);
      i = 0;
      HMAP_FOR_EACH (attr, struct attribute, node, &set->map)
        attrs[i++] = attr;
      assert (i == attrset_count (set));
      qsort (attrs, attrset_count (set), sizeof *attrs,
             compare_attribute_by_name);
      return attrs;
    }
  else
    return NULL;
}
