/******************************************************
 *
 * oreceive - implementation file
 *
 * copyleft (c) IOhannes m zm�lnig
 *
 *   2307:forum::f�r::uml�ute:2008
 *
 *   institute of electronic music and acoustics (iem)
 *   unsiversity of music and dramatic arts graz (kug)
 *
 ******************************************************
 *
 * license: GNU General Public License v.2
 *
 ******************************************************/


#include "m_pd.h"

#if 0
# define debug_post post
#else
# define debug_post
#endif

static t_class *oreceive_class, *oreceive_proxy_class, *oreceive_guts_class;


/* ------------------------------------------------------------- */

/* 
 * [oreceive] : ordered receive
 *
 *
 * there come the guts: our own bind/unbind mechanism that
 * includes priorities
 *
 * the plan is as follows:
 *  [oreceive] tells us, which name it wants to bind to; 
 *   it doesn't _really_ get bound to this name!
 *
 *  we have an invisible permanent object, that keeps track of 
 *  sorted (by priority) lists of the [oreceive] objects
 *
 *  this object binds itself to the requested name
 *  and when it gets called, it in turn calls all the [oreceive] objects
 *
 * that's it
 */


typedef struct _bind_element {
  t_pd   *object;
  t_float priority;
  struct _bind_element*next;
} t_bind_element;

typedef struct _bind_list {
  t_object p_obj;

  t_symbol*key;
  t_bind_element*elements;
  struct _bind_list*next;
} t_oreceive_proxy;

static t_oreceive_proxy*priority_receiver=0;

static t_oreceive_proxy*guts_find_key(t_symbol*key)
{
  t_oreceive_proxy*binding=0;

  for(binding=priority_receiver; binding; binding=binding->next) {
    if(binding->key == key)
      return binding;
  }
  /* not found */
  return 0;
}


static t_oreceive_proxy*guts_add_key(t_symbol*key)
{
  t_oreceive_proxy*bind_list=0;
  bind_list=(t_oreceive_proxy*)pd_new(oreceive_proxy_class);
  bind_list->key=key;
  bind_list->elements=0;
  bind_list->next=0;

  debug_post("binding %x to %s", bind_list, key->s_name);
  pd_bind(&bind_list->p_obj.ob_pd, key);

  t_oreceive_proxy*last=priority_receiver;

  if(last) {
    while(last->next) {
      last=last->next;
    }
    last->next = bind_list;
  } else {
    priority_receiver = bind_list;
  }

  return bind_list;
}

static void guts_add_element(t_oreceive_proxy*bind_list, t_bind_element*element)
{
  /* insert the object according to it's priority 
   * this is already the right queue
   */
  t_float priority=element->priority;
  t_bind_element*elements=bind_list->elements, *last=0;
  debug_post("priority insert of %x:%g", element, priority);

  if(!elements || elements->priority >= priority) {
    bind_list->elements = element;
    element->next = elements;
    debug_post("inserting in the beginngin");
    return;
  }


  debug_post("trying %x:%g", elements, elements->priority);
  while(elements && elements->priority < priority) {
    debug_post("skipping %x:%g to %x", elements, elements->priority, elements->next);
    last=elements;
    elements=elements->next;
  }

  debug_post("inserting after %x:%g", last,     (last    ?    (last->priority):0));
  debug_post("inserting befor %x:%g", elements, (elements?(elements->priority):0));

  element->next=elements;    
  if(last) {
    last->next = element;
  } else {
    bug("\nlast object invalid when inserting prioritized receiver\n");
  }
}


static void pd_bind_priority(t_pd*x, t_symbol*key, t_float priority) {
  t_oreceive_proxy*bind_list=0;
  t_bind_element*element=0;
  debug_post("trying to bind '%s':%g via %x", key->s_name, priority, priority_receiver);

  bind_list=guts_find_key(key);
  if(!bind_list)
    bind_list=guts_add_key(key);
  if(!bind_list)return;

  element=(t_bind_element*)getbytes(sizeof(t_bind_element));
  element->object=x;
  element->priority=priority;
  element->next=0;

  guts_add_element(bind_list, element);
}


static void pd_unbind_priority(t_pd*x, t_symbol*key) {
  t_oreceive_proxy*list=0, *last=0;
  t_bind_element*elements=0, *lastlmn=0;

  debug_post("trying to unbind '%s' from %x", key->s_name, priority_receiver);

  for(list=priority_receiver; list && list->key!=key; list=list->next){
    last=list;
  }
  if(!list)return;

  for(elements=list->elements; elements && elements->object != x; elements=elements->next) {
    lastlmn=elements;
  }

  if(elements) {
    /* found it, now remove it */
    if(lastlmn) {
      lastlmn->next=elements->next;
    } else {
      list->elements=elements->next;
    }

    elements->object=0;
    elements->priority=0;
    elements->next=0;

    freebytes(elements, sizeof(elements));
  } else {
    // not here...
  }

  if(0==list->elements) {
    // should we unbind ??
    if(last) {
      last->next=list->next;
    } else {
      priority_receiver=list->next;
    }    

    pd_unbind(&list->p_obj.ob_pd, list->key);
    list->next=0;
    list->key=0;

    pd_free(&list->p_obj.ob_pd);
  }
}

/* -------------------- oreceive ------------------------------ */

typedef struct _oreceive
{
  t_object x_obj;
  t_symbol *x_sym;
  t_float   x_priority;

  t_inlet*x_symin,*x_fltin;
} t_oreceive;

static void oreceive_anything(t_oreceive *x, t_symbol *s, int argc, t_atom *argv);

static void oreceive_proxy_anything(t_oreceive_proxy*p, t_symbol*s, int argc, t_atom*argv)
{
  t_bind_element*elements=p->elements;

  debug_post("proxy anything: %x", p);

  while(elements) {
    t_pd*o=elements->object;
    elements=elements->next;
    if(o) {
      oreceive_anything((t_oreceive*)o, s, argc, argv);
    }
  }
}

/* ------------------------------------------------------------- */

static void oreceive_anything(t_oreceive *x, t_symbol *s, int argc, t_atom *argv)
{
    outlet_anything(x->x_obj.ob_outlet, s, argc, argv);
}

static void oreceive_priority(t_oreceive *x, t_float p)
{
  x->x_priority=p;
  if(x->x_sym) {
    pd_unbind_priority(&x->x_obj.ob_pd, x->x_sym);
  }
  pd_bind_priority(&x->x_obj.ob_pd, x->x_sym, x->x_priority);
}

static void oreceive_name(t_oreceive *x, t_symbol*s)
{
  if(x->x_sym) {
    pd_unbind_priority(&x->x_obj.ob_pd, x->x_sym);
  }
  x->x_sym=s;
  pd_bind_priority(&x->x_obj.ob_pd, x->x_sym, x->x_priority);
}


static void *oreceive_new(t_symbol *s, t_float priority)
{
    t_oreceive *x = (t_oreceive *)pd_new(oreceive_class);

    x->x_symin = inlet_new(&x->x_obj, &x->x_obj.ob_pd, &s_symbol, &s_symbol);
    x->x_fltin = inlet_new(&x->x_obj, &x->x_obj.ob_pd, &s_float, &s_float);

    x->x_sym = s;
    x->x_priority = priority;
    pd_bind_priority(&x->x_obj.ob_pd, s, priority);
    outlet_new(&x->x_obj, 0);
    return (x);
}

static void oreceive_free(t_oreceive *x)
{
    pd_unbind_priority(&x->x_obj.ob_pd, x->x_sym);
}

void oreceive_setup(void)
{
    oreceive_class = class_new(gensym("oreceive"), (t_newmethod)oreceive_new, 
                               (t_method)oreceive_free, sizeof(t_oreceive), CLASS_NOINLET, A_DEFSYM, A_DEFFLOAT, 0);
    class_addcreator((t_newmethod)oreceive_new, gensym("r"), A_DEFSYM, A_DEFFLOAT, 0);
    class_addsymbol(oreceive_class, oreceive_name);
    class_addmethod(oreceive_class, (t_method)oreceive_priority, &s_float, A_FLOAT, 0);

    oreceive_proxy_class = class_new(0, 0, 0, sizeof(t_oreceive_proxy), CLASS_NOINLET | CLASS_PD, 0);
    class_addanything(oreceive_proxy_class, oreceive_proxy_anything);
}