/* arrayfuncs.c: -*- C -*-  Functions specifically for operating on arrays. */

/* Author: Brian J. Fox (bfox@ai.mit.edu) Sat Jul 20 17:22:47 1996.

   This file is part of <Meta-HTML>(tm), a system for the rapid deployment
   of Internet and Intranet applications via the use of the Meta-HTML
   language.

   Copyright (c) 1995, 1996, Brian J. Fox (bfox@ai.mit.edu).
   Copyright (c) 1996, Universal Access Inc. (http://www.ua.com).

   Meta-HTML is free software; you can redistribute it and/or modify
   it under the terms of the UAI Free Software License as published
   by Universal Access Inc.; either version 1, 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
   UAI Free Software License for more details.

   You should have received a copy of the UAI Free Software License
   along with this program; if you have not, you may obtain one by
   writing to:

   Universal Access Inc.
   129 El Paseo Court
   Santa Barbara, CA
   93101  */

#include "language.h"

static void pf_array_size (PFunArgs);
static void pf_array_member (PFunArgs);
static void pf_array_append (PFunArgs);
static void pf_array_add_unique (PFunArgs);
static void pf_array_shift (PFunArgs);
static void pf_foreach (PFunArgs);
static void pf_array_concat (PFunArgs);
static void pf_sort (PFunArgs);

static PFunDesc functab[] = {
  /*   tag	     complex? debug_level	   code    */
  { "ARRAY-SIZE",	0,	0,		pf_array_size },
  { "ARRAY-MEMBER",	0,	0,		pf_array_member },
  { "ARRAY-APPEND",	0,	0,		pf_array_append },
  { "ARRAY-ADD-UNIQUE",	0,	0,		pf_array_add_unique },
  { "ARRAY-SHIFT",	0,	0,		pf_array_shift },
  { "ARRAY-CONCAT",	0,	0,		pf_array_concat },
  { "FOREACH",		1,	0,		pf_foreach },
  { "SORT",		0,	0,		pf_sort },
  { (char *)NULL,	0,	0,		(PFunHandler *)NULL }
};

PACKAGE_INITIALIZER(initialize_array_funcs)

void
initialize_array_funcs (Package *pack)
{
  register int i;
  Symbol *sym;

  /* Install the names and pointers. */
  for (i = 0; functab[i].tag != (char *)NULL; i++)
    {
      sym = symbol_intern_in_package (mhtml_function_package, functab[i].tag);
      sym->type = symtype_FUNCTION;
      sym->values = (char **)(&functab[i]);
    }
}

/* <array-size foo> --> number of allocated elements in FOO. */
static void
pf_array_size (PFunArgs)
{
  char *array_name = mhtml_evaluate_string (get_positional_arg (vars, 0));
  int result = 0;

  if (!empty_string_p (array_name))
    {
      Symbol *sym = symbol_lookup (array_name);

      if ((sym != (Symbol *)NULL) && (sym->type == symtype_STRING))
	result = sym->values_index;
    }

  bprintf_insert (page, start, "%d", result);
  xfree (array_name);
}

/* <array-member item array [caseless=true]>
       --> element-offset of item in array or "". */
static void
pf_array_member (PFunArgs)
{
  char *item = mhtml_evaluate_string (get_positional_arg (vars, 0));
  char *array = mhtml_evaluate_string (get_positional_arg (vars, 1));
  int result = -1;

  if (!empty_string_p (array))
    {
      char **values = symbol_get_values (array);

      if (values != (char **)NULL)
	{
	  register int i;
	  char *caseless;
	  int caseless_p = 0;

	  caseless = mhtml_evaluate_string (get_value (vars, "caseless"));
	  if (!empty_string_p (caseless)) caseless_p++;
	  xfree (caseless);

	  if (item ==(char *)NULL) item = strdup ("");

	  for (i = 0; values[i] != (char *)NULL; i++)
	    if ((caseless && (strcasecmp (item, values[i]) == 0)) ||
		(!caseless && (strcmp (item, values[i]) == 0)))
	      {
		result = i;
		break;
	      }
	}
    }

  if (result > -1)
    bprintf_insert (page, start, "%d", result);

  xfree (item);
  xfree (array);
}

/* <array-append item arrayvar> --> add ITEM to the end of ARRAYVAR. */
static void
pf_array_append (PFunArgs)
{
  char *item = mhtml_evaluate_string (get_positional_arg (vars, 0)); 
  char *array = mhtml_evaluate_string (get_positional_arg (vars, 1));

  if (!empty_string_p (array))
    {
      Symbol *sym = symbol_intern (array);

      if (item == (char *)NULL) item = strdup ("");
      symbol_add_value (sym, item);
    }

  xfree (array);
  xfree (item);
}

/* <array-add-unique item arrayvar [caseless=true]>
       --> add ITEM to ARRAYVAR if not already present. */
static void
pf_array_add_unique (PFunArgs)
{
  char *item = mhtml_evaluate_string (get_positional_arg (vars, 0));
  char *array = mhtml_evaluate_string (get_positional_arg (vars, 1));
  int result = -1;

  if (!empty_string_p (array))
    {
      char **values = symbol_get_values (array);

      if (item == (char *)NULL) item = strdup ("");

      if (values != (char **)NULL)
	{
	  register int i;
	  char *caseless;
	  int caseless_p = 0;

	  caseless = mhtml_evaluate_string (get_value (vars, "caseless"));
	  if (!empty_string_p (caseless)) caseless_p++;
	  xfree (caseless);

	  for (i = 0; values[i] != (char *)NULL; i++)
	    if ((caseless && (strcasecmp (item, values[i]) == 0)) ||
		(!caseless && (strcmp (item, values[i]) == 0)))
	      {
		result = i;
		break;
	      }
	}

      if (result == -1)
	{
	  Symbol *sym = symbol_intern (array);
	  symbol_add_value (sym, item);
	}
    }

  xfree (array);
  xfree (item);
}

/* <array-shift amount arrayvar [start=0]>
   Shift the elements of ARRAYVAR the indicated amount.
   If AMOUNT is negative, the elements are shifted down, with the
   lowest number elements being lost.
   If AMOUNT is positive, the elements are shifted up, with no loss at
   all. */
static void
pf_array_shift (PFunArgs)
{
  char *amount_txt =  mhtml_evaluate_string (get_positional_arg (vars, 0));
  char *array_var = mhtml_evaluate_string (get_positional_arg (vars, 1));
  char *start_var = mhtml_evaluate_string (get_value (vars, "start"));
  int startpos = 0;

  if (!empty_string_p (start_var))
    startpos = atoi (start_var);

  if (startpos < 0) startpos = 0;

  if (!empty_string_p (array_var))
    {
      Symbol *sym = symbol_lookup (array_var);

      if ((sym != (Symbol *)NULL) &&
	  (sym->type == symtype_STRING) &&
	  (sym->values_index > startpos))
	{
	  register int i;
	  int amount = atoi (amount_txt);
	  int newsize = sym->values_index + amount;

	  if (amount < 0)
	    {
	      if (newsize <= 0)
		{
		  for (i = 0; i < sym->values_index; i++)
		    {
		      free (sym->values[i]);
		      sym->values[i] = (char *)NULL;
		    }
		  free (sym->values);
		  sym->values = (char **)NULL;
		  sym->values_index = 0;
		}
	      else
		{
		  amount = -amount;

		  for (i = startpos; i < startpos + amount; i++)
		    free (sym->values[i]);

		  for (; i < sym->values_index; i++)
		    sym->values[i - amount] = sym->values[i];

		  sym->values[i - amount] = (char *)NULL;
		  sym->values_index = newsize;
		}
	    }
	  else if (amount > 0)
	    {
	      if (newsize >= sym->values_slots)
		sym->values = (char **)xrealloc
		  (sym->values, (newsize + 1) * sizeof (char *));

	      for (i = sym->values_index; i > startpos - 1; i--)
		sym->values[i + amount] = sym->values[i];

	      for (i = startpos; i < startpos + amount; i++)
		sym->values[i] = strdup ("");

	      sym->values_index = newsize;
	    }
	}
    }
  else if (debug_level)
    {
      page_debug ("--> array-shift: Needs an array to operate on");
    }
      
  xfree (amount_txt);
  xfree (array_var);
  xfree (start_var);
}

/* <foreach elementvar arrayvar start=X end=X> body </foreach>
   Perform body with ELEMENTVAR bound to successive memebers of
   ARRAYVAR, starting with the element at START (default 0), and
   ending at END (default <array-size ARRAYVAR>). */
static void
pf_foreach (PFunArgs)
{
  char *element_var = mhtml_evaluate_string (get_positional_arg (vars, 0));
  char *array_var = mhtml_evaluate_string (get_positional_arg (vars, 1));
  char *start_arg = mhtml_evaluate_string (get_value (vars, "start"));
  char *end_arg = mhtml_evaluate_string (get_value (vars, "end"));
  char *step_arg = mhtml_evaluate_string (get_value (vars, "step"));
  int start_index, end_index, step;

  if ((!empty_string_p (element_var)) && (!empty_string_p (array_var)))
    {
      Symbol *array = symbol_lookup (array_var);

      if ((array != (Symbol *)NULL) && (array->type == symtype_STRING))
	{
	  register int i;

	  end_index = array->values_index;
	  start_index = 0;
	  step = 1;

	  if (!empty_string_p (step_arg))
	    {
	      step = atoi (step_arg);
	      if (step == 0) step = 1;

	      if (step < 0)
		{
		  end_index = 0;
		  start_index = array->values_index - 1;
		}
	    }

	  if (!empty_string_p (start_arg))
	    start_index = atoi (start_arg);

	  if (start_index < 0)
	    start_index = 0;

	  if (!empty_string_p (end_arg))
	    end_index = atoi (end_arg);

	  if (end_index > array->values_index)
	    end_index = array->values_index;

	  if (end_index < start_index)
	    {
	      if (step == 1)
		step = -1;
	    }

	  if (((step < 0) && (end_index < start_index)) ||
	      ((step > 0) && (end_index > start_index)))
	    {
	      for (i = start_index;
		   (((start_index < end_index) && (i < end_index)) ||
		    ((start_index > end_index) && (i >= end_index)));
		   i += step)
		{
		  char *result;
		  Symbol *element = symbol_remove (element_var);

		  symbol_free (element);
		  element = symbol_intern (element_var);
		  symbol_add_value (element, array->values[i]);
		  result = mhtml_evaluate_string (body->buffer);

		  if (result != (char *)NULL)
		    {
		      bprintf_insert (page, start, "%s", result);
		      start += strlen (result);
		      free (result);
		    }
		}
	    }

	  *newstart = start;
	}
    }

  xfree (element_var);
  xfree (array_var);
  xfree (start_arg);
  xfree (end_arg);
  xfree (step_arg);
}

/* <array-concat receiver contributor...> append CONTRIBUTORs to the end of 
   RECEIVER. */
static void
pf_array_concat (PFunArgs)
{
  char *arrayname = mhtml_evaluate_string (get_positional_arg (vars, 0));

  if (!empty_string_p (arrayname))
    {
      Symbol *arraysym = symbol_intern (arrayname);

      if (arraysym->type == symtype_STRING)
	{
	  register int i, arg_index = 0;
	  int offset;
	  char *arg;

	  for (arg_index = 1;
	       (arg = mhtml_evaluate_string
		(get_positional_arg (vars, arg_index))) != (char *)NULL;
	       arg_index++)
	    {
	      if (!empty_string_p (arg))
		{
		  Symbol *sym = symbol_lookup (arg);

		  if ((sym != (Symbol *)NULL) &&
		      (sym->type == symtype_STRING) &&
		      (sym->values_index > 0))
		    {
		      if ((sym->values_index + arraysym->values_index + 1)
			  > arraysym->values_slots)
			arraysym->values =
			  (char **)xrealloc
			  (arraysym->values,
			   (arraysym->values_slots += (sym->values_index + 10))
			   * sizeof (char *));

		      offset = arraysym->values_index;
		      for (i = 0; i < sym->values_index; i++)
			arraysym->values[offset++] = strdup (sym->values[i]);
		      arraysym->values_index = offset;
		      arraysym->values[offset] = (char *)NULL;
		    }
		}
	      xfree (arg);
	    }
	}
    }
  xfree (arrayname);
}

static char *mhtml_sort_function_name = (char *)NULL;
static int mhtml_sort_is_caseless = 0;
static int mhtml_sort_is_descending = 0;
static int mhtml_sort_is_numeric = 0;

static int
sort_with_function (const void *item1, const void *item2)
{
  char *string1 = *(char **)item1;
  char *string2 = *(char **)item2;
  int should_free = 0;
  int result = 0;

  if (mhtml_sort_function_name)
    {
      PAGE *page = page_create_page ();

      bprintf (page, "<%s %s>", mhtml_sort_function_name, string1);
      string1 = mhtml_evaluate_string (page->buffer);
      page->bindex = 0;
      bprintf (page, "<%s %s>", mhtml_sort_function_name, string2);
      page->buffer[page->bindex] = '\0';
      string2 = mhtml_evaluate_string (page->buffer);

      page_free_page (page);
      should_free++;
    }

  if (string1 && !string2)
    result = 1;
  else if (string2 && !string1)
    result = -1;
  else if (!string1 && !string2)
    result = 0;
  else if (mhtml_sort_is_numeric)
    {
      double x = strtod (string1, (char **)NULL);
      double y = strtod (string2, (char **)NULL);

      if (x != y)
	result = (x > y) ? 1 : -1;
    }
  else if (mhtml_sort_is_caseless)
    result = strcasecmp (string1, string2);
  else
    result = strcmp (string1, string2);

  if (should_free)
    {
      xfree (string1);
      xfree (string2);
    }

  if (result && mhtml_sort_is_descending)
    result = -result;

  return (result);
}

/* <sort array-var [sort-fun] [caseless=true]> */
static void
pf_sort (PFunArgs)
{
  char *sortvar = mhtml_evaluate_string (get_positional_arg (vars, 0));

  if (!empty_string_p (sortvar))
    {
      Symbol *sym = symbol_lookup (sortvar);

      /* If there is anything to sort... */
      if ((sym != (Symbol *)NULL) && (sym->values_index != 0))
	{
	  char *sorter = mhtml_evaluate_string (get_positional_arg (vars, 1));
	  int caseless = 0, descending = 0, numeric = 0;
	  char *temp;

	  temp = mhtml_evaluate_string (get_value (vars, "caseless"));
	  if (!empty_string_p (temp))
	    caseless = 1;
	  xfree (temp);

	  temp = mhtml_evaluate_string (get_value (vars, "descending"));
	  if (!empty_string_p (temp))
	    descending = 1;
	  xfree (temp);

	  temp = mhtml_evaluate_string (get_value (vars, "numeric"));
	  if (!empty_string_p (temp))
	    numeric = 1;
	  xfree (temp);

	  /* Support "sortorder=[reverse,ascending,descending]" syntax. */
	  temp = mhtml_evaluate_string (get_value (vars, "sortorder"));

	  if (!empty_string_p (temp))
	    {
	      if ((strcasecmp (temp, "descending") == 0) ||
		  (strcasecmp (temp, "reverse") == 0))
		descending++;
	    }
	  xfree (temp);

	  mhtml_sort_function_name = (char *)NULL;

	  if (!empty_string_p (sorter))
	    mhtml_sort_function_name = sorter;

	  mhtml_sort_is_numeric = numeric;
	  mhtml_sort_is_caseless = caseless;
	  mhtml_sort_is_descending = descending;

	  qsort ((void *)sym->values, sym->values_index, sizeof (char *),
		 sort_with_function);

	  if (sorter) free (sorter);
	}
    }

  if (sortvar) free (sortvar);
}
