/* 
WaVeVieW - waveform viewer widget for pd (c) 2004 Tom Schouten
This program is free software covered under version 2 of the GPL.
See the file COPYING included in the distribution.
*/

#include "m_pd.h"
#include "debug.h"
#include "widget.h"
#include "macros.h"

// For deprecated automatic cast from string constant to char*
#define GENSYM(x) gensym((char*)x)

// pd classes
t_class *dispatcher_class = 0;
t_class *wvvw_class = 0;

// pd side widget object
typedef struct wvvw_ {
    /* pd */
    t_object obj;
    t_outlet *outlet;


    /* array name & slice */
    t_symbol *arrayname;
    int start;
     int length;

    /* widget instance */
    class widget *widget;

    /* comm: no queues, just a single event buffer
       data will be dropped when when selection/view events
       are sent too fast. */

} wvvw_t;


static void wvvw_gui_send(wvvw_t *x, event_t *e){
  if (!x->widget){
    post("no widget connected");
    return;
  }
  e->sender = x;
  e->receiver = x->widget;
  gui_send(e);
}

static void wvvw_free(wvvw_t *x){
  event_t e;  e.type = SUICIDE;
  wvvw_gui_send(x, &e);
}


static void wvvw_redraw(wvvw_t *x){
  event_t e;  e.type = REDRAW;
  wvvw_gui_send(x, &e);
}


/* setters */

static void wvvw_array(wvvw_t *w, t_symbol *s, float fstart, float flength){
  event_t e; e.type = ARRAY;
  w->start = (int)fstart;
  w->length = (int)flength;
  int elements;
  float *vec;

  if (!s) return; 
  if (!s->s_name[0]) return; // skip dummies
  w->arrayname = s;

  /* get pd array */
  t_garray *a = (t_garray *)pd_findbyclass(s, garray_class);
  if ((!a) || (!garray_getfloatarray(a, &elements, &vec))){
    post("edit: can't find array %s", s->s_name);
    return;
  }
  //post("found array %x %d", e.e.array.vec, e.e.array.elements);

  /* adjust start if valid */
  if (w->start < elements){
    vec += w->start;
    elements -= w->start;
  }
  /* adjust size if valid */
  if (w->length && (elements > w->length)){
    elements = w->length;
  }

  /* send mesage */
  e.e.array.elements = elements;
  e.e.array.vec = vec;
  wvvw_gui_send(w, &e);
}

static void wvvw_refresh(wvvw_t *w){wvvw_array(w, w->arrayname, w->start, w->length);}


static void wvvw_center(wvvw_t *w){
  event_t e; e.type = CENTER;
  wvvw_gui_send(w, &e);
}

static void wvvw_needle(wvvw_t *w, float f){
  event_t e; e.type = POINT;
  e.e.i = (int)f;
  wvvw_gui_send(w, &e);
}

static void wvvw_view(wvvw_t *w, float fstart, float flength){
  event_t e; e.type = VIEW;
  e.e.range.start = (int)fstart;
  e.e.range.length = (int)flength;
  wvvw_gui_send(w, &e);
}

static void wvvw_select(wvvw_t *w, float fstart, float flength){
  event_t e; e.type = SELECTION;
  e.e.range.start = (int)fstart;
  e.e.range.length = (int)flength;
  wvvw_gui_send(w, &e);
}

static void *wvvw_new(t_symbol *name, float fstart, float flength){
    wvvw_t *w = (wvvw_t *)pd_new(wvvw_class);
    w->widget = new wvvw(w); // create a widget connected to us

    gui_add(w->widget);

    w->outlet = outlet_new(&w->obj, GENSYM("anything"));
    wvvw_array(w, name, fstart, flength);
    return w;
}

// event dispatcher object
typedef struct {
  t_object obj;
  t_clock *clock; // polling clock
} dispatcher_t;

static dispatcher_t *dispatcher = 0;

static void dispatcher_poll(dispatcher_t *x) {
  event_t e;
  clock_delay(x->clock, 10.0f);
  while (app_receive(&e)){
    wvvw_t *w = (wvvw_t *)e.receiver; // assume message is to wvvw_t only
    
    switch(e.type){
    case REQ_ARRAY:    wvvw_refresh(w); return;
    case OUTLET_RANGE:
      char sym[] = {0,0,0};
      int key = e.e.range.key;
      if ((key >= '0') && (key <= '9')){
	sym[0] = 'n';
	sym[1] = key;
      }
      else {
	sym[0] = key;
      }
      t_atom argv[2];
      SETFLOAT(argv+0, e.e.range.start);
      SETFLOAT(argv+1, e.e.range.length);
      outlet_anything(w->outlet, GENSYM(sym), 2, argv);
      return;
    }

  }
}

static void *dispatcher_new(void){
  post("creating dispatcher");
  if (dispatcher){
    post("dispatcher already running");
    return 0;
  }
  dispatcher_t *x = (dispatcher_t *)pd_new(dispatcher_class);
  x->clock = clock_new(x, (t_method)dispatcher_poll);
  dispatcher_poll(x);
  dispatcher = x;
  return (void *)x;
}

static void dispatcher_free(dispatcher_t *x){
  clock_unset(x->clock);
  clock_free(x->clock);
}




/* GLOBAL SETUP */

void gui_start(class container *);

extern "C" {

void wvvw_setup(void){

    /* create window */
    gui_start();

    /* create dispatcher pd class */
    dispatcher_class = class_new(GENSYM("widget-dispatcher"),
				 (t_newmethod)dispatcher_new,
				 (t_method)dispatcher_free,
				 sizeof(dispatcher_t), (t_atomtype)0, (t_atomtype)0);

    dispatcher_new(); // create sigleton

    /* create wvvw pd class */
    wvvw_class = class_new(GENSYM("wvvw"), (t_newmethod)wvvw_new,
			   (t_method)wvvw_free, sizeof(wvvw_t), 0, A_DEFSYMBOL, A_DEFFLOAT, A_DEFFLOAT, 0);
    class_addmethod(wvvw_class, (t_method)wvvw_array,  GENSYM("array"), A_SYMBOL, A_DEFFLOAT, A_DEFFLOAT, 0);
    class_addmethod(wvvw_class, (t_method)wvvw_redraw, GENSYM("refresh"), (t_atomtype)0);
    class_addmethod(wvvw_class, (t_method)wvvw_center, GENSYM("center"), (t_atomtype)0);
    class_addmethod(wvvw_class, (t_method)wvvw_needle, GENSYM("needle"), A_FLOAT, 0);
    class_addmethod(wvvw_class, (t_method)wvvw_view,   GENSYM("view"), A_FLOAT, A_FLOAT, 0);
    class_addmethod(wvvw_class, (t_method)wvvw_select, GENSYM("select"), A_FLOAT, A_FLOAT, 0);

    post("WVVW: version "  VERSION);

}

}

