/* Copyright (c) 1997 The Regents of the University of California.
* For information on usage and redistribution, and for a DISCLAIMER OF ALL
* WARRANTIES, see the file, "LICENSE.txt," in this distribution.  */

#include <stdlib.h>
#include <string.h>
#include <stdio.h>  	/* for read/write to files */
#include "m_pd.h"
#include "g_canvas.h"

/* ---------- pure arrays (cf. "garrays," graphical arrays, below) -------- */

t_array *array_new(t_symbol *templatesym, t_gpointer *parent)
{
    t_array *x = (t_array *)getbytes(sizeof (*x));
    t_canvas *template =
    	(t_canvas *)pd_findbyclass(templatesym, canvas_class);
    t_datatype *datatypes;
    int elemsize, nitems;
    t_gpointer *gp;
    if (!template)
    {
    	bug("%s: array: no such template", templatesym->s_name);
    	    /* LATER figure out how to recover from this? */
    	return (0);
    }
    canvas_setusedastemplate(template);
    elemsize = canvas_template_size(template);
    datatypes = canvas_getdatatypes(template, &nitems);
    x->a_n = 1;
    x->a_elemsize = elemsize;
    x->a_vec = (t_word *)getbytes(elemsize);
    x->a_gp = *parent;
    x->a_stub = gstub_new(0, x);
    word_restore(x->a_vec, datatypes, nitems, 0, 0, parent);
    freebytes(datatypes, nitems * sizeof(*datatypes));
    return (x);
}

void array_resize(t_array *x, t_symbol *templatesym, int n)
{
    int elemsize = x->a_elemsize;
    int oldn = x->a_n;
    t_canvas *template =
    	(t_canvas *)pd_findbyclass(templatesym, canvas_class);
    t_datatype *datatypes;
    int nitems;
    t_gpointer *gp;

    if (!template)
    {
    	bug("%s: array: no such template", templatesym->s_name);
    	return;
    }
    if (elemsize != canvas_template_size(template)) bug("array_resize");

    datatypes = canvas_getdatatypes(template, &nitems);


    x->a_vec = (t_word *)resizebytes(x->a_vec, oldn * elemsize,
    	n * elemsize);
    if (n > oldn)
    {
    	char *cp = ((char *)x->a_vec) + elemsize * oldn;
    	int i = n - oldn;
    	for (; i--; cp += elemsize)
    	{
    	    t_word *wp = (t_word *)cp;
    	    word_restore(wp, datatypes, nitems, 0, 0, &x->a_gp);
    	}
    }
}

void array_free(t_array *x)
{  
    gpointer_unset(&x->a_gp);
    gstub_cutoff(x->a_stub);
    freebytes(x->a_vec, x->a_elemsize * x->a_n);
    freebytes(x, sizeof *x);
}

/* --------------------- graphical arrays (garrays) ------------------- */

t_class *garray_class;

struct _garray
{
    t_gobj x_gobj;
    t_glist *x_glist;
    int x_n;
    int x_elemsize;
    char *x_vec;
    t_symbol *x_name;
    t_symbol *x_template;
    t_float x_firstx;	    /* X value of first item */
    t_float x_xinc; 	    /* X increment */
    char x_usedindsp;	    /* true if some DSP routine is using this */
};

void graph_array(t_glist *gl, t_symbol *s, t_symbol *template, t_floatarg f)
{
    int n = f;
    static int gcount = 0;
    int zz;
    t_garray *x;
    t_canvas *c;
    char *str;
    if (s == &s_)
    {
    	char buf[40];
    	sprintf(buf, "array%d", ++gcount);
    	s = gensym(buf);    	
    	template = &s_float;
    	n = 100;
    }
    else if (!strncmp((str = s->s_name), "array", 5)
    	&& (zz = atoi(str + 5)) > gcount) gcount = zz;
    c = (t_canvas *)pd_findbyclass(template, canvas_class);
    if (!c && template != &s_float)
    {
    	error("array: couldn't find template %s", template->s_name);
    	return;
    }
    x = (t_garray *)pd_new(garray_class);
    
    pd_bind(&x->x_gobj.g_pd, s);
    if (n <= 0) n = 100;  
    x->x_n = n;
    x->x_elemsize = canvas_template_size(c);
    x->x_vec = getbytes(x->x_n * x->x_elemsize);
    memset(x->x_vec, 0, x->x_n * x->x_elemsize);
    	/* LATER should check that malloc */
    x->x_name = s;
    x->x_template = template;
    x->x_firstx = 0;
    x->x_xinc = 1;	    	/* LATER make methods to set this... */
    glist_add(gl, &x->x_gobj);
    x->x_glist = gl;
    x->x_usedindsp = 0;
}

void canvas_array(t_glist *canvas)
{
    t_glist *gl;
    gl = glist_findgraph(canvas, 0, 100, -1, 1);
    graph_array(gl, &s_, 0, 0);
}

static void garray_free(t_garray *x)
{
    pd_unbind(&x->x_gobj.g_pd, x->x_name);
    freebytes(x->x_vec, x->x_n * x->x_elemsize);
}

/* -------------------- widget behavior for garray ------------ */

static void garray_getrect(t_gobj *z, t_glist *owner,
    int *x1, int *y1, int *x2, int *y2)
{
    t_garray *x = (t_garray *)z;
    *x1 = 0;
    *y1 = 0;
    *x2 = 0;
    *y2 = 0; /* fill in later */ 
}

static void garray_displace(t_gobj *z, t_glist *glist, int dx, int dy)
{
    /* refuse */
}

static void garray_select(t_gobj *z, t_glist *glist, int state)
{
    t_garray *x = (t_garray *)z;
    /* fill in later */
}

static void garray_activate(t_gobj *z, t_glist *glist, int state)
{
    post("garray_activate %d", state);
}

static void garray_delete(t_gobj *z, t_glist *glist)
{
    /* nothing to do */
}

static void garray_vis(t_gobj *z, t_glist *glist, int vis)
{
    t_garray *x = (t_garray *)z;
    if (vis)
    {
    	int i, elemsize, xonset, yonset;
    	t_field *fx, *fy;
    	t_canvas *c = (t_canvas *)pd_findbyclass(x->x_template, canvas_class);
    	elemsize = canvas_template_size(c);
    	if (!(fy = canvas_find_field(c, gensym("y"), &yonset))
    	    || field_type(fy) != DT_FLOAT)
    	{
    	    error("%s: needs floating-point 'y' field", x->x_template->s_name);
    	    sys_vgui(".x%x.c create text 50 50 -text foo\
    	    	-tags .x%x.a%x\n",
    	    	glist_getcanvas(glist), glist_getcanvas(glist), x);
    	}
    	else if (!(fx = canvas_find_field(c, gensym("x"), &xonset))
    	    || field_type(fx) != DT_FLOAT)
    	{
    	    float firsty, xcum = x->x_firstx;
    	    int lastpixel = -1, ndrawn = 0;
    	    float yval, xpix;
    	    int ixpix;
    	    sys_vgui(".x%x.c create line \\\n", glist_getcanvas(glist));
    	    for (i = 0; i < x->x_n; i++)
    	    {
    	    	yval = *(float *)((x->x_vec + elemsize * i) + yonset);
    	    	xpix = glist_xtopixels(glist, xcum);
    	    	ixpix = xpix + 0.5;
    	    	if (ixpix != lastpixel)
    	    	{
    	    	    sys_vgui("%d %f \\\n", ixpix,
    	    	    	glist_ytopixels(glist, yval));
    	    	    ndrawn++;
    	    	}
    	    	lastpixel = ixpix;
    	    	if (ndrawn >= 1000) break;
    	    	xcum += x->x_xinc;
    	    }
    	    	/* TK will complain if there aren't at least 2 points... */
    	    if (ndrawn == 0) sys_vgui("0 0 0 0 \\\n");
    	    else if (ndrawn == 1) sys_vgui("%d %f \\\n", ixpix,
    	    	    	glist_ytopixels(glist, yval));
    	    sys_vgui("-tags .x%x.a%x\n", glist_getcanvas(glist), x);
    	    firsty = *(float *)(x->x_vec + yonset);
    	    sys_vgui(".x%x.c create text %f %f -text {%s} -anchor e\
    	    	 -font {courier %d bold} -tags .x%x.a%x\n",
    	    	glist_getcanvas(glist),
    	    	glist_xtopixels(glist, x->x_firstx) - 5.,
    	    	glist_ytopixels(glist, firsty),
    	    	x->x_name->s_name, glist_getfont(glist),
    	    	    glist_getcanvas(glist), x);
    	}
    	else
    	{
    	    post("x, y arrays not yet supported");
    	}
    }
    else
    {
    	sys_vgui(".x%x.c delete .x%x.a%x\n",
    	    glist_getcanvas(glist), glist_getcanvas(glist), x);
    }
}

static void garray_save(t_gobj *z, t_binbuf *b)
{
    t_garray *x = (t_garray *)z;
    binbuf_addv(b, "sssis;", gensym("#X"), gensym("array"),
    	x->x_name, x->x_n, x->x_template);
}

t_widgetbehavior garray_widgetbehavior =
{
    garray_getrect,
    garray_displace,
    garray_select,
    garray_activate,
    garray_delete,
    garray_vis,
    garray_save
};

/* ----------------------- public functions -------------------- */

void garray_usedindsp(t_garray *x)
{
    x->x_usedindsp = 1;
}

void garray_redraw(t_garray *x)
{
    if (glist_isvisible(x->x_glist))
    {
    	garray_vis(&x->x_gobj, x->x_glist, 0); 
    	garray_vis(&x->x_gobj, x->x_glist, 1);
    }
}

t_canvas *garray_template(t_garray *x)	/* get the template of an array */
{
    return ((t_canvas *)pd_findbyclass(x->x_template, canvas_class));
}

int garray_npoints(t_garray *x)	/* get the length */
{
    return (x->x_n);
}

char *garray_vec(t_garray *x) /* get the contents */
{
    return (x->x_vec);
}

    /* routine that checks if we're just an array of floats and if
    so returns the goods */

int garray_getfloatarray(t_garray *x, int *size, t_float **vec)
{
    t_canvas *c = garray_template(x);
    int elemsize = canvas_template_size(c);
    t_field *fy;
    int yonset;
    if (!(fy = canvas_find_field(c, gensym("y"), &yonset))
    	|| field_type(fy) != DT_FLOAT)
    	    error("%s: needs floating-point 'y' field", x->x_template->s_name);
    else if (elemsize != sizeof(float))
    	error("%s: has more than one field", x->x_template->s_name);
    else
    {
    	*size = garray_npoints(x);
    	*vec =  (float *)garray_vec(x);
    	return (1);
    }
    return (0);
}

    /* get any floating-point field of any element of an array */
float garray_get(t_garray *x, t_symbol *s, t_int index)
{
    int elemsize, yonset;
    t_field *fy;
    t_canvas *c = (t_canvas *)pd_findbyclass(x->x_template, canvas_class);
    elemsize = canvas_template_size(c);
    if (!(fy = canvas_find_field(c, s, &yonset))
    	|| field_type(fy) != DT_FLOAT)
    {
    	error("%s: needs floating-point '%s' field", x->x_template->s_name,
    	    s->s_name);
    	return (0);
    }
    if (index < 0) index = 0;
    else if (index >= x->x_n) index = x->x_n - 1;
    return (*(float *)((x->x_vec + elemsize * index) + yonset));
}

/*------------------- Pd messages ------------------------ */
static void garray_const(t_garray *x, double g)
{
    int i, elemsize, yonset;
    t_field *fy;
    t_canvas *c = (t_canvas *)pd_findbyclass(x->x_template, canvas_class);
    elemsize = canvas_template_size(c);
    if (!(fy = canvas_find_field(c, gensym("y"), &yonset))
    	|| field_type(fy) != DT_FLOAT)
    	    error("%s: needs floating-point 'y' field", x->x_template->s_name);
    else for (i = 0; i < x->x_n; i++)
    	*(float *)((x->x_vec + elemsize * i) + yonset) = g;
    garray_redraw(x);
}

static void garray_read(t_garray *x, t_symbol *filename)
{
    int i, elemsize, yonset;
    t_field *fy;
    FILE *fd;
    t_canvas *c = (t_canvas *)pd_findbyclass(x->x_template, canvas_class);
    char buf[MAXPDSTRING];
    elemsize = canvas_template_size(c);
    if (!(fy = canvas_find_field(c, gensym("y"), &yonset))
    	|| field_type(fy) != DT_FLOAT)
    {
    	error("%s: needs floating-point 'y' field", x->x_template->s_name);
    	return;
    }
    canvas_makefilename(glist_getcanvas(x->x_glist), filename->s_name,
    	buf, MAXPDSTRING);
    if (!(fd = fopen(buf, "r")))
    {
    	error("%s: can't open", buf);
    	return;
    }
    for (i = 0; i < x->x_n; i++)
    {
    	if (!fscanf(fd, "%f", (float *)((x->x_vec + elemsize * i) + yonset)))
    	{
    	    post("%s: only %d elements out of %d read",
    	    	filename->s_name, i, x->x_n);
    	    break;
    	}
    }
    fclose(fd);
    garray_redraw(x);
}

static int garray_ambigendian(void)
{
    unsigned short s = 1;
    unsigned char c = *(char *)(&s);
    return (c==0);
}

#ifdef NT
#define BINREADMODE "rb"
#else
#define BINREADMODE "r"
#endif

static void garray_read16(t_garray *x, t_symbol *filename,
    t_symbol *endian, t_floatarg fskip)
{
    int skip = fskip;
    int i, nelem;
    float *vec;
    FILE *fd;
    char buf[MAXPDSTRING];
    short s;
    int cpubig = garray_ambigendian(), swap = 0;
    char c = endian->s_name[0];
    if (c == 'b')
    {
    	if (!cpubig) swap = 1;
    }
    else if (c == 'l')
    {
    	if (cpubig) swap = 1;
    }
    else if (c)
    {
    	error("array_read16: endianness is 'l' (low byte first ala INTEL)");
    	post("... or 'b' (high byte first ala MIPS,DEC,PPC)");
    }
    if (!garray_getfloatarray(x, &nelem, &vec))
    {
    	error("%s: not a float array", x->x_template->s_name);
    	return;
    }
    canvas_makefilename(glist_getcanvas(x->x_glist), filename->s_name,
    	buf, MAXPDSTRING);

    if (!(fd = fopen(buf, BINREADMODE)))
    {
    	error("%s: can't open", buf);
    	return;
    }

    if (skip)
    {
    	long pos = fseek(fd, (long)skip, SEEK_SET);
    	if (pos < 0)
    	{
    	    error("%s: can't seek to byte %d", buf, skip);
    	    fclose(fd);
    	    return;
    	}
    }

    for (i = 0; i < nelem; i++)
    {
    	if (fread(&s, sizeof(s), 1, fd) < 1)
    	{
    	    post("%s: only %d elements out of %d read",
    	    	filename->s_name, i, nelem);
    	    break;
    	}
    	if (swap) s = ((s & 0xff) << 8) | ((s & 0xff00) >> 8);
    	vec[i] = s * (1./32768.);
    }
    fclose(fd);
    garray_redraw(x);
}

static void garray_write(t_garray *x, t_symbol *filename)
{
    int i, elemsize, yonset;
    t_field *fy;
    FILE *fd;
    t_canvas *c = (t_canvas *)pd_findbyclass(x->x_template, canvas_class);
    char buf[MAXPDSTRING];
    elemsize = canvas_template_size(c);
    if (!(fy = canvas_find_field(c, gensym("y"), &yonset))
    	|| field_type(fy) != DT_FLOAT)
    {
    	error("%s: needs floating-point 'y' field", x->x_template->s_name);
    	return;
    }
    canvas_makefilename(glist_getcanvas(x->x_glist), filename->s_name,
    	buf, MAXPDSTRING);
    if (!(fd = fopen(buf, "w")))
    {
    	error("%s: can't create", buf);
    	return;
    }
    for (i = 0; i < x->x_n; i++)
    {
    	if (fprintf(fd, "%g\n",
    	    *(float *)((x->x_vec + elemsize * i) + yonset)) < 1)
    	{
    	    post("%s: write error", filename->s_name);
    	    break;
    	}
    }
    fclose(fd);
}

static void garray_resize(t_garray *x, t_floatarg f)
{
    int was = x->x_n, elemsize;
    t_glist *gl;
    t_canvas *c;
    int dspwas;
    int n = f;
    if (n < 0) n = 0;
    c = (t_canvas *)pd_findbyclass(x->x_template, canvas_class);
    elemsize = canvas_template_size(c);

    x->x_vec = t_resizebytes(x->x_vec, was * elemsize, n * elemsize);
    	/* LATER should check t_resizebytes result */
    if (n > was)
    	memset(x->x_vec + was*elemsize,
    	    0, (n - was) * elemsize);
    x->x_n = n;
    
    	/* if this is the only array in the graph,
    	    reset the graph's coordinates */
    gl = x->x_glist;
    if (gl->gl_list == &x->x_gobj && !x->x_gobj.g_next)
    {
    	vmess(&gl->gl_pd, gensym("bounds"), "ffff",
    	    0., gl->gl_y1, (double)n, gl->gl_y2);
    }
    else garray_redraw(x);
    if (x->x_usedindsp) canvas_update_dsp();
}

static void garray_print(t_garray *x)
{  
    post("garray %s: template %s, length %d",
    	x->x_name->s_name, x->x_template->s_name, x->x_n);
}


void g_array_setup()
{
    garray_class = class_new(gensym("array"), 0, garray_free, sizeof(t_garray),
    	CLASS_GOBJ, 0);
    class_setwidget(garray_class, &garray_widgetbehavior);
    class_addmethod(garray_class, garray_const, gensym("const"),
    	A_DEFFLOAT, A_NULL);
    class_addmethod(garray_class, garray_read, gensym("read"),
    	A_SYMBOL, A_NULL);
    class_addmethod(garray_class, (t_method)garray_read16, gensym("read16"),
    	A_SYMBOL, A_DEFFLOAT, A_DEFSYM, A_NULL);
    class_addmethod(garray_class, garray_write, gensym("write"),
    	A_SYMBOL, A_NULL);
    class_addmethod(garray_class, (t_method)garray_resize, gensym("resize"),
    	A_FLOAT, A_NULL);
    class_addmethod(garray_class, garray_print, gensym("print"), A_NULL);
}

