/* darray.c - dynamic arrays handling
   Copyright 1996-2017 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, 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
   <https://www.gnu.org/licenses/gpl-3.0.html>.  */

/* Author: Akim Demaille <demaille@inf.enst.fr> */

#include <config.h>

#include <system.h>

#include "darray.h"
#include "routines.h"

int da_exit_error = 1;			/* exit value when encounters	*
					 * an error 			*/

#define QSORT_INSERT_SORT_LIMIT	37	/* Bellow, insert sort is used */
#define QSORT_STACK		100
#define DA_SWAP(a,i,j) 	\
   do {					\
     tmp = a->content [i];		\
     a->content [i] = a->content [j];	\
     a->content [j] = tmp ;		\
   } while (0)

/*
 * Create a dynamic array
 */
struct darray *
da_new (const char * name, size_t size,
	enum da_growth growth, size_t increment,
	da_print_func_t self_print,
	da_cmp_func_t cmp)
{
  struct darray * res;

  /* No longer relevant: size_t cannot be null */
  if (size == 0)
    error (da_exit_error, 0, "invalid size for dynamic array `%s': %zu",
	   name, size);
  if (increment == 0 && growth != da_steady)
    error (da_exit_error, 0, "invalid increment for dynamic array `%s': %zu",
	   name, increment);

  res = XMALLOC (struct darray);
  res->name = name;
  res->original_size = size;
  res->size = size;
  res->content = XCALLOC (res->size, void *);
  res->growth = growth;
  res->increment = increment;
  res->len = 0;

  /* Routines */
  res->self_print = self_print;
  res->cmp = cmp;

  return res;
}

/*
 * Report the status of the array
 */
void
da_print_stats (struct darray * arr, FILE * stream)
{
  const char * cp = NULL;

  fprintf (stream, _("Dynamic array `%s':\n"), arr->name);
  fprintf (stream, _("\tload: %zu/%zu (%2.1f%%)\n"),
	   arr->len, arr->size, 100.0 * (double) arr->len / (double) arr->size);
  switch (arr->growth) {
  case da_steady:
    /* growth is steady, i.e., it cannot grow, it is constant */
    cp = "[const]";
    break;
  case da_linear:
    /* growth is linear. eg. 2, 4, 6, 8 */
    cp = "+=";
    break;
  case da_geometrical:
    /* growth is exponential. eg. 2, 4, 8, 16 */
    cp = "*=";
    break;
  default:
    abort ();
  }
  fprintf (stream, _("\toriginal size: %zu, growth: %s %zu\n"),
	   arr->original_size, cp, arr->increment);
}

/*
 * Resize, unless too small to fit
 */
void
da_resize (struct darray * arr, size_t size)
{
  if (arr->len + 1 < size)
    {
      arr->size = size;
      arr->content = xnrealloc (arr->content, arr->size, sizeof(void *));
    }
}

/*
 * Make a dyn. array bigger
 */
void
da_grow (struct darray * arr)
{
  switch (arr->growth) {
  case da_steady:
    return;

  case da_linear:
    arr->size += arr->increment;
    break;

  case da_geometrical:
    arr->size *= arr->increment;
    break;

  default:
    abort ();
  }
  arr->content = xnrealloc (arr->content, arr->size, sizeof(void *));
}

/*
 * Make a clone
 */
struct darray *
da_clone (struct darray * array)
{
  struct darray * res;
  res = xmemdup (array, sizeof *(array));
  res->content = xmemdup (array->content, (array->len) * sizeof *(array->content));
  return res;
}


/*
 * Is it sorted?
 */
int
da_is_sorted (struct darray * arr)
{
  size_t i;

  for (i = 1 ; i < arr->len ; i++)
    if (arr->cmp (arr->content [i], arr->content [i - 1]) < 0)
      return 0;
  return 1;
}

/*
 * Are two darray equal (pointer-wise)?
 */
int
da_equal (struct darray * ar1, struct darray * ar2)
{
  size_t i;

  if (ar1->len != ar2->len)
    return 0;

  for (i = 0 ; i< ar1->len ; i++)
    if (ar1->content [i] != ar2->content [i])
      return 0;
  return 1;
}

/*
 * Do two arrays have same semantics (wrt cmp) content?
 * (ar1->cmp is used for the comparison)
 */
int
da_cmp_equal (struct darray * ar1, struct darray * ar2)
{
  size_t i;

  if (ar1->len != ar2->len)
    return 0;

  for (i = 0 ; i< ar1->len ; i++)
    if (ar1->cmp (ar1->content [i], ar2->content [i]))
      return 0;
  return 1;
}

/*
 * Where is STUFF in ARR (equal in the sense of self_cmp)
 * -1 if is not in.
 */
int
da_where (struct darray * arr, const void * stuff)
{
  size_t i;

  for (i = 0 ; i < arr->len ; i++)
    if (!arr->cmp (arr->content[i], stuff))
      return (int) i;

  return -1;
}

/*
 * Does this stuff is selfcmp equal to an item of the darray?
 */
int
da_includes (struct darray * arr, const void * stuff)
{
  return (da_where (arr, stuff) != -1);
}

/*
 * Append an element
 */
void
da_append (struct darray * arr, void * elem)
{
  if (da_is_full (arr))
    da_grow (arr);

  arr->content [arr->len++] = elem;
}

/*
 * Insert an element at a given place.
 */
void
da_insert_at (struct darray * arr, void * elem, size_t where)
{
  size_t i;

  if (where > arr->len)
    error (da_exit_error, 0, "can't insert at %zu in darray %s [0,%zu]\n",
	   where, arr->name, arr->len - 1);

  if (da_is_full (arr))
    da_grow (arr);

  for (i = arr->len ; where < i ; i--)
    arr->content [i] = arr->content [i - 1];

  arr->content [ where ] = elem;
  arr->len ++;
}

/*
 * Remove an element at a given place.
 */
void
da_remove_at (struct darray * arr, size_t where)
{
  size_t i;

  if (where >= arr->len)
    error (da_exit_error, 0, "can't remove at %zu in darray %s [0,%zu]\n",
	   where, arr->name, arr->len - 1);

  for (i = where + 1 ; i < arr->len ; i++)
    arr->content [i - 1] = arr->content [i];

  arr->len --;
}

/*
 * Concat the second in the first
 */
void
da_concat (struct darray * arr, struct darray * arr2)
{
  size_t i;
  size_t len = arr->len + arr2->len;

  if (len > arr->size) {
    arr->size = len + 1;
    arr->content = xnrealloc (arr->content, arr->size, sizeof(void *));
  }

  for (i = 0 ; i < arr2->len ; i++)
    arr->content [arr->len++] = arr2->content[i];
}

/*
 * Prefix the content of ARR by that of ARR2
 */
void
da_prefix (struct darray * arr, struct darray * arr2)
{
  size_t i;
  size_t len = arr->len + arr2->len;

  if (len > arr->size) {
    arr->size = len + 1;
    arr->content = xnrealloc (arr->content, arr->size, sizeof(void *));
  }

  /* Move the content of ARR */
  for (i = arr->len ; i-- > 0 ;)
    arr->content [ i + arr2->len ] = arr->content [ i ];

  /* Copy the content of ARR2 */
  for (i = 0 ; i < arr2->len ; i++)
    arr->content [ i ] = arr2->content[ i ];

  arr->len += arr2->len;
}

/*
 * Implementation of QSORT as given by Sedgewick
 */
void
da_qsort (struct darray * arr)
{
  size_t ir, j, k, l, i;
  size_t jstack, *istack;
  void * a, * tmp;

  /* Do not sort an empty array */
  if (arr->len <= 1)
    return;

  istack = XNMALLOC (QSORT_STACK, size_t);
  ir = arr->len - 1;
  l = 0;
  jstack = 0;

  for (;;) {
      if (ir - l < QSORT_INSERT_SORT_LIMIT)
	{	/* Insertion sort is then prefered */
	  for (j = l + 1 ; j <= ir ; j++) {
	    a = arr->content [j];
	    for (i = j ; i-- > l ;) {
	      if (arr->cmp (arr->content [i], a) <= 0)
		break;
	      arr->content [i + 1] = arr->content [i];
	    }
	    arr->content [i + 1] = a;
	  }
	  if (jstack == 0)
	    break;
	  ir = istack [jstack--];
	  l = istack [jstack--];
	}
      else
	{
	  k = (l + ir) / 2;
	  DA_SWAP (arr, k, l + 1);
	  if (arr->cmp (arr->content [l], arr->content [ir]) > 0)
	    DA_SWAP (arr, l, ir);
	  if (arr->cmp (arr->content [l + 1], arr->content [ir]) > 0)
	    DA_SWAP (arr, l + 1, ir);
	  if (arr->cmp (arr->content [l], arr->content [l + 1]) > 0)
	    DA_SWAP (arr, l, l + 1);
	  i = l + 1;
	  j = ir;
	  a = arr->content [l + 1];
	  for (;;) {
	    do i++; while (arr->cmp (arr->content [i], a) < 0);
	    do j--; while (arr->cmp (arr->content [j], a) > 0);
	    if (j < i)
	      break;	/* Partion is completed	*/
	    DA_SWAP (arr, i, j);
	  }
	  arr->content [l + 1] = arr->content [j];
	  arr->content [j] = a;
	  jstack += 2;
	  /* Push pointers to larger subarry on stack.
	   * Process smaller subarrays now	*/
	  if (jstack > QSORT_STACK)
	    error (da_exit_error, 0, "da_qsort: QSORT_STACK too small (%d)",
			   QSORT_STACK);
	  if (ir - i + 1 >= j - l) {
	    istack [jstack]     = ir;
	    istack [jstack - 1] = i;
	    ir = j - 1;
	  } else {
	    istack [jstack]     = j - 1;
	    istack [jstack - 1] = l;
	    l = i;
	  }
	}
  }
}

/*
 * Implementation of QSORT as given by Sedgewick
 */
void
da_qsort_with_arg (struct darray * arr, da_cmp_arg_func_t cmp,
		   const void * arg)
{
  size_t ir, j, k, l, i;
  size_t jstack, *istack;
  void * a, * tmp;

  /* Do not sort an empty array */
  if (arr->len <= 1)
    return;

  istack = XNMALLOC (QSORT_STACK, size_t);
  ir = arr->len - 1;
  l = 0;
  jstack = 0;

  for (;;) {
      if (ir - l < QSORT_INSERT_SORT_LIMIT)
	{	/* Insertion sort is then prefered */
	  for (j = l + 1 ; j <= ir ; j++) {
	    a = arr->content [j];
	    for (i = j ; i-- > l ;) {
	      if (cmp (arr->content [i], a, arg) <= 0)
		break;
	      arr->content [i + 1] = arr->content [i];
	    }
	    arr->content [i + 1] = a;
	  }
	  if (jstack == 0)
	    break;
	  ir = istack [jstack--];
	  l = istack [jstack--];
	}
      else
	{
	  k = (l + ir) / 2;
	  DA_SWAP (arr, k, l + 1);
	  if (cmp (arr->content [l], arr->content [ir], arg) > 0)
	    DA_SWAP (arr, l, ir);
	  if (cmp (arr->content [l + 1], arr->content [ir], arg) > 0)
	    DA_SWAP (arr, l + 1, ir);
	  if (cmp (arr->content [l], arr->content [l + 1], arg) > 0)
	    DA_SWAP (arr, l, l + 1);
	  i = l + 1;
	  j = ir;
	  a = arr->content [l + 1];
	  for (;;) {
	    do i++; while (cmp (arr->content [i], a, arg) < 0);
	    do j--; while (cmp (arr->content [j], a, arg) > 0);
	    if (j < i)
	      break;	/* Partion is completed	*/
	    DA_SWAP (arr, i, j);
	  }
	  arr->content [l + 1] = arr->content [j];
	  arr->content [j] = a;
	  jstack += 2;
	  /* Push pointers to larger subarry on stack.
	   * Process smaller subarrays now	*/
	  if (jstack > QSORT_STACK)
	    error (da_exit_error, 0, "da_qsort: QSORT_STACK too small (%d)",
			   QSORT_STACK);
	  if (ir - i + 1 >= j - l) {
	    istack [jstack]     = ir;
	    istack [jstack - 1] = i;
	    ir = j - 1;
	  } else {
	    istack [jstack]     = j - 1;
	    istack [jstack - 1] = l;
	    l = i;
	  }
	}
  }
}

/*
 * Leave the first of each doubles
 */
void
da_unique (struct darray * arr)
{
  size_t c;

  c = 1;
  while (c < arr->len) {
    if (arr->cmp (arr->content [c - 1], arr->content[c]) == 0)
      da_remove_at (arr, c);
    else
      c++;
  }
}

/*
 * Merge A2 into A1.  Both *are sorted*.
 * In the result there are never two equal entries
 * (in the sense of self_cmp).
 *
 * In case of conflict (equal entries from the point of view
 * of a1->cmp),
 * - if POLICY == da_1_wins, keep that of A1
 * - if POLICY == da_2_wins, keep that of A2
 *
 * If there are doubles in a1 and/or in a2, they still will be doubles
 * in the returned result.
 */
void
da_merge (struct darray * a1, struct darray * a2,
	  enum da_include_policy policy)
{
  size_t c1, c2;		/* Counters on a1, and a2	*/

  c1 = c2 = 0;

  while ((c1 != a1->len) || (c2 != a2->len))
    {
      /* Leave what is in a1 as long as it is strictly smaller than the
       * next item of a2 */
      while ((c1 < a1->len)
	     && ((c2 == a2->len)
		 || (a1->cmp (a1->content [c1], a2->content [c2]) < 0)))
	c1 ++;

      /* Skip whatever appears in a1, but is in a2 too */
      while ((c1 < a1->len) && (c2 < a2->len)
	     && (a1->cmp (a1->content [c1], a2->content [c2]) == 0))
	if (policy == da_1_wins)
          c2++;
	else
          c1++;

      /* Take what is a2 as long as it is smaller or equal to
       * what appeared last in a1 */
      while ((c2 < a2->len)
	     && ((c1 == a1->len)
		 || (a1->cmp (a1->content [c1], a2->content [c2]) >= 0)))
	da_insert_at (a1, a2->content [c2++], c1);
    }
}


/*
 * Dump on stderr the content
 */
void
da_self_print (struct darray * arr, FILE * stream)
{
  size_t i;

  fprintf (stream, _("Dynamic array `%s':\n"), arr->name);
  if (!arr->self_print)
    abort ();
  for (i = 0 ; i < arr->len ; i++) {
    fprintf (stream, "[%2zu] = ", i);
    arr->self_print (arr->content [i], stream);
    fprintf (stream, "\n");
  }
}

/*
 * For each item of ARR, call FN (ITEM)
 */
void
da_map (struct darray * arr, da_map_func_t fn)
{
  size_t i;

  for (i = 0 ; i < arr->len ; i++)
    (*fn) (arr->content [i]);
}

/*
 * Idem, but with an argument
 */
void
da_maparg (struct darray * arr, da_maparg_func_t func, void * arg)
{
  size_t i;
  for (i = 0 ; i < arr->len ; i++)
    (*func) (arr->content [i], arg);
}

/*
 * Some helping routines for special darray cases
 */
/*
 * darray of strings
 */
int
da_str_cmp (const char * s1, const char * s2)
{
  return strcmp (s1, s2);
}

void
da_str_print (const char * s1, FILE * stream)
{
  fputs ((const char *) s1, stream);
}

void
da_str_printnl (const char * s1, FILE * stream)
{
  fputs ((const char *) s1, stream);
  putc ('\n', stream);
}
