/*
 * index: associative dictionary
 *
 * (c) 1999-2011 IOhannes m zmölnig, forum::für::umläute, institute of electronic music and acoustics (iem)
 *
 * 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 2
 * 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/>.
 */

/*
   (c) 2005:forum::für::umläute:2000

   "index" simulates an associative index :: that is : convert a symbol to an index

   CAVEATS: starts to count at "1"

   TODO: use "symbol" instead of "char*" : FIXED
   TODO: "dump" the contents (so we can share between [index]es, ...) FIXED
   TODO: "compact": move all entries to the beginning of the array FIXED
   TODO: "sort" FIXED
   TODO: "add" at a specific position (like "add 10 hallo" of "add hallo 10") (??) FIXED
   TODO: "delete" from a specific position (like "delete 4" deletes the 4th element) FIXED
   TODO: get the number of stored entries ("bang") FIXED

   TODO: resize the array if it gets to small

*/

#include "zexy.h"
#include <string.h>

/* ----------------------- index --------------------- */

static t_class *index_class = NULL;

typedef struct _index {
  t_object x_obj;

  int entries, maxentries;
  int auto_mode; /* 1--add if key doesn't already exist; 0--do not add; */
  int auto_resize; /* 1--resize the array if we are running out of slots; 0--don't */

  t_symbol **names;
} t_index;

/************************************
 * index_helpers
 */

/* find the last non-NULL entry in the array *
 * LATER: shouldn't this return "-1" on failure ?
 */
static int find_last(t_symbol **names, int maxentries)
{
  /* returns the index of the last entry (0..(maxentries-1)) */
  while (maxentries--)
    if (names[maxentries]) {
      return maxentries;
    }
  return 0;
}

/* search the array for "key"
 * if it is not there, return "-1"
 */
static int find_item(const t_symbol *key, t_symbol **names, int maxentries)
{
  /* returns index (0..[maxentries-1?]) on success; -1 if the item could not be found */
  int i = -1;
  int max = find_last(names, maxentries);

  while (++i <= max)
    if (names[i] && key == names[i]) {
      return i;
    }

  return -1;
}

/* find the first NULL entry in the array
 * return "-1" if none can be found
 */
static int find_free(t_symbol **names, int maxentries)
{
  int i = 0;

  while (i < maxentries) {
    if (!names[i]) {
      return i;
    }
    i++;
  }
  return -1;
}

/************************************
 * methods
 */
static void index_add(t_index *x, t_symbol *s, t_float f);

/* look up a symbol in the map */
static void index_symbol(t_index *x, t_symbol *s)
{
  int element;
  if ((element = find_item(s, x->names, x->maxentries) + 1)) {
    outlet_float(x->x_obj.ob_outlet, (t_float)element);
  } else if (x->auto_mode) { /* not yet stored: add automatically */
    index_add(x, s, 0);
  } else {
    outlet_float(x->x_obj.ob_outlet, 0.f); /* not yet stored but do not add */
  }
}

/* output the entry at a given index */
static void index_float(t_index *x, t_float findex)
{
  int iindex = (int)findex;
  if ((iindex > 0) && (iindex <= x->maxentries) && (x->names[iindex - 1])) {
    /* TB: output symbol to outlet */
    outlet_symbol(x->x_obj.ob_outlet, x->names[iindex - 1]);
  }
}

/* add a symbol to the map (if possible) */
static void index_add(t_index *x, t_symbol *s, t_float f)
{
  int newentry = (int)f;

  if (!(find_item(s, x->names, x->maxentries) + 1)) {
    if (x->auto_resize &&
        (x->entries == x->maxentries || newentry >= x->maxentries)) {
      /* do some resizing */
      int maxentries =
          (newentry > x->maxentries) ? newentry : (x->maxentries * 2);
      t_symbol **buf = (t_symbol **)getbytes(sizeof(t_symbol *) * maxentries);
      if (buf != 0) {
        int i;
        memcpy(buf, x->names, sizeof(t_symbol *) * x->maxentries);
        for (i = x->maxentries; i < maxentries; i++) {
          buf[i] = 0;
        }

        freebytes(x->names, sizeof(t_symbol *) * x->maxentries);

        x->names = buf;
        x->maxentries = maxentries;
      }
    }

    if (x->entries < x->maxentries) {
      if (newentry > 0) {
        newentry--;
        if (x->names[newentry]) { /* it is already taken! */
          logpost(x, PD_DEBUG,
              "index :: couldn't add element '%s' at position %d (already "
              "taken)",
              s->s_name, newentry + 1);
          outlet_float(x->x_obj.ob_outlet, -1.f);
          return;
        }
      } else {
        newentry = find_free(x->names, x->maxentries);
      }
      if (newentry + 1) {
        x->entries++;
        x->names[newentry] = s;
        outlet_float(x->x_obj.ob_outlet, (t_float)newentry + 1);
        return;

      } else {
        pd_error(x, "index :: couldn't find any place for new entry");
      }
    } else {
      pd_error(
          x, "index :: max number of elements (%d) reached !", x->maxentries);
    }
  } else {
    logpost(x, PD_DEBUG, "index :: element '%s' already exists", s->s_name);
  }
  /* couldn't add the symbol to our index table */
  outlet_float(x->x_obj.ob_outlet, -1.f);
}
/* delete a symbol from the map (if it is in there) */
static void index_delete(
    t_index *x, t_symbol *UNUSED(s), int argc, t_atom *argv)
{
  int idx = -1;
  if (argc != 1) {
    pd_error(x, "index :: delete what ?");
    return;
  } else {
    if (argv->a_type == A_FLOAT) {
      idx = atom_getint(argv) - 1;
    } else if (argv->a_type == A_SYMBOL) {
      idx = find_item(atom_getsymbol(argv), x->names, x->maxentries);
    } else {
      pd_error(x, "index :: delete what ?");
      return;
    }
  }

  if (idx >= 0 && idx < x->maxentries) {
    x->names[idx] = 0;
    x->entries--;
    outlet_float(x->x_obj.ob_outlet, 0.0);
  } else {
    logpost(x, PD_DEBUG, "index :: couldn't find element");
    outlet_float(x->x_obj.ob_outlet, -1.0);
  }
}

/* delete all symbols from the map */
static void index_reset(t_index *x)
{
  int i = x->maxentries;

  while (i--)
    if (x->names[i]) {
      x->names[i] = 0;
    }

  x->entries = 0;

  outlet_float(x->x_obj.ob_outlet, 0.f);
}

/* output the number of entries stored in the array */
static void index_bang(t_index *x)
{
  outlet_float(x->x_obj.ob_outlet, (t_float)x->entries);
}
/* dump each entry in the format: "list <symbol> <index>" */
static void index_dump(t_index *x)
{
  t_atom ap[2];
  int i = 0;
  for (i = 0; i < x->maxentries; i++) {
    if (x->names[i]) {
      SETSYMBOL(ap, x->names[i]);
      SETFLOAT(ap + 1, i + 1);
      outlet_list(x->x_obj.ob_outlet, 0, 2, ap);
    }
  }
}

/* compact all entries, removing all holes in the map */
static void index_compact(t_index *x)
{
  int i, j;
  for (i = 0; i < x->entries; i++) {
    if (!x->names[i]) {
      for (j = i + 1; j < x->maxentries; j++) {
        if (x->names[j]) {
          x->names[i] = x->names[j];
          x->names[j] = 0;
          break;
        }
      }
    }
  }
}
/* sort the map alphabetically */
static void index_sort(t_index *x)
{
  int entries = x->entries;
  int step = entries;
  int loops = 1, n;
  t_symbol **buf = x->names;
  index_compact(
      x); /* couldn't we do it more "in-place", e.g. don't touch empty slots ? */

  while (step > 1) {
    int i = loops;
    step += step % 2;
    step >>= 1;
    loops += 2;

    while (i--) { /* there might be some optimization in here */
      for (n = 0; n < (x->entries - step); n++) {
        int comp = strcmp(buf[n]->s_name, buf[n + step]->s_name);
        if (comp > 0) { /* compare STRINGS not SYMBOLS */
          t_symbol *s_tmp = buf[n];
          buf[n] = buf[n + step];
          buf[n + step] = s_tmp;
        }
      }
    }
  }
}

/* turn on/off auto-adding of elements that are not yet in the map */
static void index_auto(t_index *x, t_float automod)
{
  x->auto_mode = !(!automod);
}
/* turn on/off auto-resizing of the map if it gets to small */
static void index_resize(t_index *x, t_float automod)
{
  x->auto_resize = !(!automod);
}

static void *index_new(t_symbol *UNUSED(s), int argc, t_atom *argv)
{
  t_index *x = (t_index *)pd_new(index_class);
  t_symbol **buf;

  int maxentries = 0, automod = 0;

  if (argc--) {
    maxentries = (int)atom_getfloat(argv++);
    if (argc) {
      automod = (int)atom_getfloat(argv++);
    }
  }

  if (maxentries < 1) {
    maxentries = 128;
  }

  buf = (t_symbol **)getbytes(sizeof(t_symbol *) * maxentries);

  x->entries = 0;
  x->maxentries = maxentries;
  x->names = buf;
  x->auto_mode = !(!automod);
  x->auto_resize = 1;

  while (maxentries--) {
    buf[maxentries] = 0;
  }

  outlet_new(&x->x_obj, gensym("float"));

  return (x);
}

static void index_free(t_index *x)
{
  freebytes(x->names, sizeof(t_symbol *) * x->maxentries);
}

static void index_helper(t_index *UNUSED(x))
{
  endpost();
  post("" HEARTSYMBOL " index :: index symbols to indices");
  post("<symbol>             : look up the <symbol> in the index and return "
       "it's index");
  post(
      "<int>                : look up the element at index <int> in the index");
  post("'add <symbol>'       : add a new symbol to the index-map");
  post("'add <symbol> <int>' : add a new symbol at the index <int>");
  post("'delete <symbol>'    : delete a symbol from the index-map");
  post("'delete <int>'       : delete the entry at index <int> from the "
       "index-map");
  post("'reset'              : delete the whole index-map");
  post("'bang'               : return the number of entries in the index-map");
  post("'dump'               : dump each entry in the format \"list <symbol> "
       "<index>\"");
  post("'compact'            : remove holes in the index-map");
  endpost();
  post("'sort'               : alphabetically sort the entries");
  post("'auto <1/0>          : if auto is 1 and a yet unknown symbol is looked "
       "up it is\n\t\t\t automatically added to the index-map");
  post("'resize <1/0>        : if resize is 1 (default), the index-map is "
       "resized\n\t\t\t automatically if needed");
  post("'help'               : view this");
  post("outlet : <n>         : index of the <symbol>");
  post("         <symbol>    : entry at <index>");
  endpost();
  post("creation:\"index [<maxelements> [<auto>]]\": creates a <maxelements> "
       "sized index");
}

ZEXY_SETUP void index_setup(void)
{
  index_class =
      zexy_new("index", index_new, index_free, t_index, CLASS_DEFAULT, "*");

  class_addsymbol(index_class, index_symbol);

  zexy_addmethod(index_class, (t_method)index_reset, "reset", "");
  zexy_addmethod(index_class, (t_method)index_delete, "delete", "*");
  zexy_addmethod(index_class, (t_method)index_add, "add", "sF");

  zexy_addmethod(index_class, (t_method)index_auto, "auto", "f");
  zexy_addmethod(index_class, (t_method)index_resize, "resize", "f");

  class_addfloat(index_class, (t_method)index_float);
  class_addbang(index_class, (t_method)index_bang);
  zexy_addmethod(index_class, (t_method)index_sort, "sort", "");
  zexy_addmethod(index_class, (t_method)index_compact, "compact", "");
  zexy_addmethod(index_class, (t_method)index_dump, "dump", "");

  zexy_addmethod(index_class, (t_method)index_helper, "help", "");
  zexy_register("index");
}
