/*
 * liststorage: stores a number of lists
 *
 * (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/>.
 */

/*
    this is heavily based on code from [textfile],
    which is part of pd and written by Miller S. Puckette
    pd (and thus [textfile]) come with their own license
*/

#include "zexy.h"

#ifdef __WIN32__
#  include <io.h>
#else
#  include <unistd.h>
#endif

#include <fcntl.h>
#include <stdio.h>
#include <string.h>

/* ****************************************************************************** */
/* liststorage : store several lists in a slots (array of lists of lists) */

/* a list of lists  */
typedef struct _msglist {
  int argc;
  t_atom *argv;
  struct _msglist *next;
} t_msglist;

typedef struct _liststorage {
  t_object x_obj; /* everything */

  t_outlet *x_dataout; /* where the data appears */
  t_outlet *x_infoout; /* where meta-information appears */

  t_inlet *x_slotin; /* setting the current slot */

  int x_numslots, x_defaultnumslots;
  int x_currentslot;

  t_msglist **x_slots;
} t_liststorage;

static t_class *liststorage_class = NULL;

/* ************************************************************************ */
/* helper functions                                                           */

static t_msglist *_liststorage_getslot(t_liststorage *x, int slot)
{
  //  post("getting slot %d of %d|%d", slot, 0, x->x_numslots);
  if (slot < 0 || slot >= x->x_numslots) {
    pd_error(x, "[liststorage]: attempting to access invalid slot %d", slot);
    return NULL;
  }
  return x->x_slots[slot];
}

static void _liststorage_deletemsglist(t_msglist *list)
{
  t_msglist *x = list;
  while (x) {
    t_msglist *y = x;
    x = x->next;

    freebytes(y->argv, y->argc * sizeof(t_atom));
    y->argc = 0;
    y->argv = NULL;
    y->next = NULL;
    freebytes(y, sizeof(t_msglist));
  }
}

static void _liststorage_deleteslot(t_liststorage *x, int slot)
{
  t_msglist *list = _liststorage_getslot(x, slot);
  if (list) {
    _liststorage_deletemsglist(list);
    x->x_slots[slot] = NULL;
  }
}

static t_msglist *_liststorage_newslot(int argc, t_atom *argv)
{
  t_msglist *slot = getbytes(sizeof(t_msglist));
  int i = 0;

  slot->argv = getbytes(sizeof(t_atom) * argc);
  for (i = 0; i < argc; i++) {
    slot->argv[i] = argv[i];
  }

  slot->argc = argc;
  slot->next = NULL;

  return slot;
}

static t_msglist *_liststorage_add2slot(t_msglist *slot, int argc, t_atom *argv)
{
  t_msglist *dummy = slot;
  t_msglist *newlist = _liststorage_newslot(argc, argv);
  if (NULL == slot) {
    //    post("no data yet: new data is %x", newlist);
    return newlist;
  }

  while (dummy->next) {
    dummy = dummy->next;
  }
  dummy->next = newlist;
  //  post("added data to slot @ %x", slot);
  return slot;
}

static int _liststorage_resize(t_liststorage *x, int size)
{
  t_msglist **newarray = NULL;
  int i = 0;

  if (size < 0) {
    pd_error(
        x, "[liststorage]: refusing to resize for negative amount of slots");
    return 0;
  }

  if (size == x->x_numslots) {
    logpost(x, PD_VERBOSE, "[liststorate] no need to resize array");
    return size;
  }

  /* create a new array */
  newarray = getbytes(sizeof(t_msglist *) * size);
  for (i = 0; i < size; i++) {
    newarray[i] = NULL;
  }

  /* copy over all the data from the old array (if there was one */
  i = (size < x->x_numslots) ? size : x->x_numslots;
  while (i-- > 0) {
    newarray[i] = x->x_slots[i];
    x->x_slots[i] = NULL;
  }

  /* delete the old array */
  for (i = 0; i < x->x_numslots; i++) {
    _liststorage_deleteslot(x, i);
  }
  freebytes(x->x_slots, sizeof(t_msglist *));

  /* make the new array the current */
  x->x_slots = newarray;
  x->x_numslots = size;

  return size;
}

static int _liststorage_checkslot(
    t_liststorage *x, const char *string, const int resize)
{
  int slot = x->x_currentslot;
  t_atom atom;
  SETFLOAT(&atom, (t_float)slot);

  if (slot < 0) {
    if (NULL != string) {
      pd_error(x, "[liststorage]: %s %d", string, slot);
    }
    outlet_anything(x->x_infoout, gensym("invalidslot"), 1, &atom);
    return -1;
  }
  if (slot >= x->x_numslots) {
    if (resize) {
      _liststorage_resize(x, slot + 1);
    } else {
      if (NULL != string) {
        pd_error(x, "[liststorage]: %s %d", string, slot);
      }
      outlet_anything(x->x_infoout, gensym("invalidslot"), 1, &atom);
      return -1;
    }
  }
  return slot;
}
/* ************************************************************************ */
/* object methods                                                           */

/* recall all lists from the current slot */
static void liststorage_bang(t_liststorage *x)
{
  t_atom atom;
  t_msglist *list = NULL;

  int slot =
      _liststorage_checkslot(x, "attempting to read data from invalid slot", 0);
  if (slot < 0) {
    return;
  }
  list = _liststorage_getslot(x, slot);

  while (list) {
    outlet_list(x->x_dataout, gensym("list"), list->argc, list->argv);
    list = list->next;
  }
  SETFLOAT(&atom, (t_float)slot);

  /* no need for done: use [t b b b] to get beginning and end of output */
  //  outlet_anything(x->x_infoout, gensym("done"), 1, &atom);
}

/* add a new list to the current slot */
static void liststorage_add(
    t_liststorage *x, t_symbol *UNUSED(s), int ac, t_atom *av)
{
  int slot =
      _liststorage_checkslot(x, "attempting to add data to invalid slot", 1);
  if (slot < 0) {
    return;
  }
  x->x_slots[slot] = _liststorage_add2slot(x->x_slots[slot], ac, av);
}

/* clear the current slot */
static void liststorage_clear(t_liststorage *x)
{
  int slot = _liststorage_checkslot(x, "attempting to clear invalid slot", 0);
  if (slot < 0) {
    return;
  }

  _liststorage_deleteslot(x, slot);
}

/* clear all slots */
static void liststorage_clearall(t_liststorage *x)
{
  int i = 0;
  for (i = 0; i < x->x_numslots; i++) {
    _liststorage_deleteslot(x, i);
  }
}

/* insert an empty slot at (before) given position */
static void liststorage_insert(t_liststorage *x, t_floatarg f)
{
  int current = x->x_currentslot;
  int slot;
  int i = 0;

  x->x_currentslot = f;
  slot = _liststorage_checkslot(x, "attempting to insert invalid slot", 1);
  x->x_currentslot = current;

  if (slot < 0) {
    return;
  }

  _liststorage_resize(x, x->x_numslots + 1);

  for (i = x->x_numslots - 1; i > slot; i--) {
    x->x_slots[i] = x->x_slots[i - 1];
  }
  x->x_slots[slot] = NULL;
}

/* get the number of slots */
static void liststorage_info(t_liststorage *x)
{
  t_atom ap;
  SETFLOAT(&ap, (t_float)x->x_numslots);
  outlet_anything(x->x_infoout, gensym("numslots"), 1, &ap);
}

/* get the number of slots */
static void liststorage_slot(t_liststorage *x, t_floatarg f)
{
  int slot = f;
  x->x_currentslot = slot;
}

/* remove empty slots */
static void liststorage_compress(t_liststorage *x)
{
  t_msglist **newarray = NULL;
  int i = 0, j = 0;
  int size = 0;
  for (i = 0; i < x->x_numslots; i++) {
    if (NULL != x->x_slots[i]) {

      size++;
    }
  }

  if (size >= x->x_numslots) {
    //    post("incomressible: %d of %d", size, x->x_numslots);
    return;
  }

  if (size < x->x_defaultnumslots) {
    size = x->x_defaultnumslots;
  }

  /* create a new array */
  newarray = getbytes(sizeof(t_msglist *) * size);
  for (i = 0; i < size; i++) {
    newarray[i] = NULL;
  }

  /* copy over all the data from the old array (if there was one */
  for (i = 0, j = 0; i < x->x_numslots; i++) {
    if (NULL != x->x_slots[i]) {
      newarray[j] = x->x_slots[i];
      j++;
    }
    x->x_slots[i] = NULL;
  }

  /* delete the old array */
  for (i = 0; i < x->x_numslots; i++) {
    _liststorage_deleteslot(x, i);
  }
  freebytes(x->x_slots, sizeof(t_msglist *));

  /* make the new array the current */
  x->x_slots = newarray;
  x->x_numslots = size;
}

/* ************************************************************************ */
/* constructor/destructor                                                   */

static void liststorage_free(t_liststorage *x)
{
  liststorage_clearall(x);
  _liststorage_resize(x, 0);
}

/* constructor: argument is initial number of slots (can grow) */
static void *liststorage_new(t_floatarg f)
{
  t_liststorage *x = (t_liststorage *)pd_new(liststorage_class);
  int slots = f;

  x->x_slotin =
      inlet_new(&x->x_obj, &x->x_obj.ob_pd, gensym("float"), gensym("slot"));
  x->x_dataout = outlet_new(&x->x_obj, gensym("list"));
  x->x_infoout = outlet_new(&x->x_obj, 0);

  if (slots <= 0) {
    slots = 20;
  }
  x->x_defaultnumslots = slots;
  x->x_numslots = 0;
  x->x_currentslot = 0;
  x->x_slots = NULL;

  _liststorage_resize(x, x->x_defaultnumslots);

  return (x);
}

ZEXY_SETUP void liststorage_setup(void)
{
  liststorage_class = zexy_new("liststorage", liststorage_new, liststorage_free,
      t_liststorage, CLASS_DEFAULT, "F");

  /* recall all lists from the current slot */
  class_addbang(liststorage_class, (t_method)liststorage_bang);

  /* add a new list to the current slot */
  zexy_addmethod(liststorage_class, (t_method)liststorage_add, "add", "*");
  /* clear the current slot */
  zexy_addmethod(liststorage_class, (t_method)liststorage_clear, "clear", "");
  /* clear all slots */
  zexy_addmethod(
      liststorage_class, (t_method)liststorage_clearall, "clearall", "");

  /* add a new list to the current slot */
  zexy_addmethod(liststorage_class, (t_method)liststorage_slot, "slot", "f");

  /* insert an empty slot at (before) given position */
  zexy_addmethod(
      liststorage_class, (t_method)liststorage_insert, "insert", "F");

  /* remove empty slots */
  zexy_addmethod(
      liststorage_class, (t_method)liststorage_compress, "compress", "");

  /* get the number of slots */
  zexy_addmethod(liststorage_class, (t_method)liststorage_info, "info", "");

  zexy_register("liststorage");
}
