/*
 * fifop: a FIFO (first-in first-out) with priorities
 *
 * (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/>.
 */

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

/* ------------------------- fifop ------------------------------- */

/*
 * a FIFO (first-in first-out) with priorities
 *
 * an incoming list is added to a fifo (based on its priority)
 * "bang" outputs the next element of the non-empty fifo with the highest priority
 *
 * high priority means low numeric value
 */

static t_class *fifop_class = NULL;

typedef struct _fifop_list {
  int argc;
  t_atom *argv;
  struct _fifop_list *next;
} t_fifop_list;

typedef struct _fifop_prioritylist {
  t_float priority;
  t_fifop_list *fifo_start;
  t_fifop_list *fifo_end;
  struct _fifop_prioritylist *next;
} t_fifop_prioritylist;

typedef struct _fifop {
  t_object x_obj;
  t_fifop_prioritylist *fifo_list;
  t_float priority; /* current priority */
  unsigned long counter;
  t_outlet *x_out, *x_infout;
} t_fifop;

static t_fifop_prioritylist *fifop_genprioritylist(t_fifop *x, t_float priority)
{
  t_fifop_prioritylist *result = 0, *dummy = 0;

  if (x->fifo_list != 0) {
    /*
     * do we already have this priority ?
     * if so, just return a pointer to that fifo
     * else set the dummy-pointer to the fifo BEFORE the new one
     */
    dummy = x->fifo_list;
    while (dummy != 0) {
      t_float prio = dummy->priority;
      if (prio == priority) {
        return dummy;
      }
      if (prio > priority) {
        break;
      }
      result = dummy;
      dummy = dummy->next;
    }
    dummy =
        result; /* dummy points to the FIFO-before the one we want to insert */
  }
  /* create a new priority list */
  result = (t_fifop_prioritylist *)getbytes(sizeof(t_fifop_prioritylist));
  result->priority = priority;
  result->fifo_start = 0;
  result->fifo_end = 0;
  result->next = 0;

  /* insert it into the list of priority lists */
  if (dummy == 0) {
    /* insert at the beginning */
    result->next = x->fifo_list;
    x->fifo_list = result;
  } else {
    /* post insert into the list of FIFOs */
    result->next = dummy->next;
    dummy->next = result;
  }

  /* return the result */
  return result;
}

static int add2fifo(
    t_fifop *x, t_fifop_prioritylist *fifoprio, int argc, t_atom *argv)
{
  t_fifop_list *entry = 0;

  if (fifoprio == 0) {
    pd_error(x, "pfifo: no fifos available");
    return -1;
  }

  /* create an entry for the fifo */
  if (!(entry = (t_fifop_list *)getbytes(sizeof(t_fifop_list)))) {
    pd_error(x, "pfifo: couldn't add entry to end of fifo");
    return -1;
  }
  if (!(entry->argv = (t_atom *)getbytes(argc * sizeof(t_atom)))) {
    pd_error(x, "pfifo: couldn't add list to fifo!");
    return -1;
  }
  memcpy(entry->argv, argv, argc * sizeof(t_atom));
  entry->argc = argc;
  entry->next = 0;

  /* insert entry into fifo */
  if (fifoprio->fifo_end) {
    /* append to the end of the fifo */
    t_fifop_list *fifo = fifoprio->fifo_end;

    /* add new entry to end of fifo */
    fifo->next = entry;
    fifoprio->fifo_end = entry;
  } else {
    /* the new entry is the 1st entry of the fifo */
    fifoprio->fifo_start = entry;
    /* and at the same time, it is the last entry */
    fifoprio->fifo_end = entry;
  }
  return 0;
}
static t_fifop_prioritylist *getFifo(t_fifop_prioritylist *pfifo)
{
  if (pfifo == 0) {
    return 0;
  }
  /* get the highest non-empty fifo */
  while (pfifo->fifo_start == 0 && pfifo->next != 0) {
    pfifo = pfifo->next;
  }
  return pfifo;
}

static void fifop_list(t_fifop *x, t_symbol *UNUSED(s), int argc, t_atom *argv)
{
  t_fifop_prioritylist *pfifo = 0;
  if (!(pfifo = fifop_genprioritylist(x, x->priority))) {
    pd_error(x, "[fifop]: couldn't get priority fifo");
    return;
  }
  if (!add2fifo(x, pfifo, argc, argv)) {
    x->counter++;
  }
}
static void fifop_bang(t_fifop *x)
{
  t_fifop_prioritylist *pfifo = 0;
  t_fifop_list *fifo = 0;
  t_atom *argv = 0;
  int argc = 0;

  if (!(pfifo = getFifo(x->fifo_list))) {
    outlet_bang(x->x_infout);
    return;
  }
  if (!(fifo = pfifo->fifo_start)) {
    outlet_bang(x->x_infout);
    return;
  }

  x->counter--;

  pfifo->fifo_start = fifo->next;
  if (0 == pfifo->fifo_start) {
    pfifo->fifo_end = 0;
  }
  /* get the list from the entry */
  argc = fifo->argc;
  argv = fifo->argv;

  fifo->argc = 0;
  fifo->argv = 0;
  fifo->next = 0;

  /* destroy the fifo-entry (important for recursion! */
  freebytes(fifo, sizeof(t_fifop_list));

  /* output the list */
  outlet_list(x->x_out, gensym("list"), argc, argv);

  /* free the list */
  freebytes(argv, argc * sizeof(t_atom));
}
static void fifop_query(t_fifop *x)
{
  logpost(x, PD_DEBUG, "%d elements in fifo", (int)x->counter);

  outlet_float(x->x_infout, (t_float)x->counter);
}

static void fifop_clear(t_fifop *x)
{
  t_fifop_prioritylist *fifo_list = x->fifo_list;
  while (fifo_list) {
    t_fifop_prioritylist *fifo_list2 = fifo_list;

    t_fifop_list *fifo = fifo_list2->fifo_start;
    fifo_list = fifo_list->next;

    while (fifo) {
      t_fifop_list *fifo2 = fifo;
      fifo = fifo->next;

      if (fifo2->argv) {
        freebytes(fifo2->argv, fifo2->argc * sizeof(t_atom));
      }
      fifo2->argv = 0;
      fifo2->argc = 0;
      fifo2->next = 0;
      freebytes(fifo2, sizeof(t_fifop_list));
    }
    fifo_list2->priority = 0;
    fifo_list2->fifo_start = 0;
    fifo_list2->fifo_end = 0;
    fifo_list2->next = 0;
    freebytes(fifo_list2, sizeof(t_fifop_prioritylist));
  }
  x->fifo_list = 0;
  x->counter = 0;
}
/* this is NOT re-entrant! */
static void fifop_dump(t_fifop *x)
{
  t_fifop_prioritylist *pfifo = getFifo(x->fifo_list);

  if (!pfifo || !pfifo->fifo_start) {
    outlet_bang(x->x_infout);
    return;
  }

  while (pfifo) {
    t_fifop_list *fifo = pfifo->fifo_start;
    while (fifo) {
      t_atom *argv = fifo->argv;
      int argc = fifo->argc;

      /* output the list */
      outlet_list(x->x_out, gensym("list"), argc, argv);

      fifo = fifo->next;
    }
    pfifo = pfifo->next;
  }
}

static void fifop_help(t_fifop *UNUSED(x))
{
  post("\n" HEARTSYMBOL
       " fifop\t\t:: a First-In-First-Out queue with priorities");
}

static void fifop_free(t_fifop *x)
{
  fifop_clear(x);

  outlet_free(x->x_out);
  outlet_free(x->x_infout);
}

static void *fifop_new(void)
{
  t_fifop *x = (t_fifop *)pd_new(fifop_class);

  floatinlet_new(&x->x_obj, &x->priority);
  x->x_out = outlet_new(&x->x_obj, gensym("list"));
  x->x_infout = outlet_new(&x->x_obj, gensym("float"));

  x->fifo_list = 0;
  x->priority = 0;

  return (x);
}

ZEXY_SETUP void fifop_setup(void)
{
  fifop_class =
      zexy_new("fifop", fifop_new, fifop_free, t_fifop, CLASS_DEFAULT, "");

  class_addbang(fifop_class, fifop_bang);
  class_addlist(fifop_class, fifop_list);

  zexy_addmethod(fifop_class, (t_method)fifop_clear, "clear", "");
  zexy_addmethod(fifop_class, (t_method)fifop_dump, "dump", "");

  zexy_addmethod(fifop_class, (t_method)fifop_query, "info", "");
  zexy_addmethod(fifop_class, (t_method)fifop_help, "help", "");

  zexy_register("fifop");
}
