/* 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>

#include "m_pd.h"
#include "g_canvas.h"

/*
This file contains text objects you would put in a canvas to define a
template.  Templates describe objects of type "array" (g_array.c) and
"scalar" (g_scalar.c).
*/

/* ---------------------- fields ----------------------------- */

t_class *field_class;

struct _field
{
    t_object x_obj;
    int x_type;
    t_symbol *x_arraytype;
    t_symbol *x_name;
};

static void *field_new(t_symbol *name, t_symbol *typesym, t_symbol *arraytype)
{
    t_field *x;
    int type;
    if (typesym == &s_float) type = DT_FLOAT;
    else if (typesym == &s_symbol) type = DT_SYMBOL;
    else if (typesym == &s_list) type = DT_LIST;
    else if (typesym == gensym("array")) type = DT_ARRAY;
    else
    {
    	error("%s: no such type", typesym->s_name);
    	return (0);
    }
    x = (t_field *)pd_new(field_class);
    x->x_name = name;
    if (type == DT_ARRAY) x->x_arraytype = arraytype;
    else x->x_arraytype = &s_;
    x->x_type = type;
    	/* LATER find all Pds with this template and update them. */
    return (x);
}

t_field *field_default(void)	/* make up a field -- hack */
{
    static t_field *deffield;
    if (!deffield) deffield = field_new(gensym("y"), &s_float, &s_);
    return (deffield);
}

int field_size(t_field *x)
{  
    return (sizeof(union word));
}

t_symbol *field_name(t_field *x)
{
    return (x->x_name);
}

int field_type(t_field *x)
{
    return (x->x_type);
}

t_symbol *field_arraytemplate(t_field *x)
{
    return (x->x_arraytype);
}

static void field_setup(void)
{
    field_class = class_new(gensym("field"), field_new, 0, sizeof(t_field),
    	CLASS_NOINLET, A_DEFSYM, A_DEFSYM, A_DEFSYM, 0);
}

/* ---------------- curves and polygons (joined segments) ---------------- */

/*
curves belong to templates and describe how the data in the template are to
be drawn.  The coordinates of the curve (and other display features) can
be attached to fields in the template.
*/

/*  	FIELD DESCRIPTORS:
a field descriptor can hold a constant or a variable; if a variable,
it's the name of a field in the template we belong to.  LATER, we might
want to cache the offset of the field so we don't have to search for it
every single time we draw the object.
*/

typedef struct _fielddesc
{
    char fd_type;   	/* LATER consider removing this? */
    char fd_var;
    union
    {
    	t_float fd_float;
    	t_symbol *fd_symbol;
    	t_symbol *fd_varsym;
    } fd_un;
} t_fielddesc;

#define FIELDDESC_SETFLOAT(x, f) \
    ((x)->fd_type = A_FLOAT, (x)->fd_var = 0, (x)->fd_un.fd_float = (f))
#define FIELDDESC_SETSYMBOL(x, s) \
    ((x)->fd_type = A_SYMBOL, (x)->fd_var = 0, (x)->fd_un.fd_symbol = (s))
#define FIELDDESC_SETVAR(x, s, type) \
    ((x)->fd_type = type, (x)->fd_var = 1, (x)->fd_un.fd_varsym = (s))

#define CLOSED 1
#define BEZ 2
#define A_ARRAY 55  	/* LATER decide whether to enshrine this in m_pd.h */

t_class *curve_class;

typedef struct _curve
{
    t_object x_obj;
    int x_flags;     	    /* CLOSED and/or BEZ */
    t_fielddesc x_fillcolor;
    t_fielddesc x_outlinecolor;
    t_fielddesc x_width;
    int x_npoints;
    t_fielddesc *x_vec;
} t_curve;

static void fielddesc_setfloatarg(t_fielddesc *fd, int argc, t_atom *argv)
{
    	if (argc <= 0) FIELDDESC_SETFLOAT(fd, 0);
    	else if (argv->a_type == A_SYMBOL)
    	    FIELDDESC_SETVAR(fd, argv->a_w.w_symbol, A_FLOAT);
    	else FIELDDESC_SETFLOAT(fd, argv->a_w.w_float);
}

static void fielddesc_setarrayarg(t_fielddesc *fd, int argc, t_atom *argv)
{
    	if (argc <= 0) FIELDDESC_SETFLOAT(fd, 0);
    	else if (argv->a_type == A_SYMBOL)
    	    FIELDDESC_SETVAR(fd, argv->a_w.w_symbol, A_ARRAY);
    	else FIELDDESC_SETFLOAT(fd, argv->a_w.w_float);
}

static t_float fielddesc_getfloat(t_fielddesc *f, t_canvas *template,
    t_word *wp, int loud)
{
    float val = 0;
    if (f->fd_type == A_FLOAT)
    {
    	if (f->fd_var)
   	    val = canvas_getfloat(template, f->fd_un.fd_varsym, wp, loud);
    	else val = f->fd_un.fd_float;
    }
    else if (f->fd_type == A_SYMBOL)
    {
    	if (loud) error("curve: can't use symbol for coordinate");
    }
    else bug("curve_getcoord");
    return (val);
}

static void *curve_new(t_symbol *classsym, t_int argc, t_atom *argv)
{
    t_curve *x = (t_curve *)pd_new(curve_class);
    char *classname = classsym->s_name;
    int flags = 0;
    int nxy, i;
    t_fielddesc *fd;
    if (classname[0] == 'f')
    {
    	classname++;
    	flags |= CLOSED;
    	if (argc) fielddesc_setfloatarg(&x->x_fillcolor, argc--, argv++);
    	else FIELDDESC_SETFLOAT(&x->x_outlinecolor, 0); 
    }
    if (classname[0] == 'c') flags |= BEZ;
    x->x_flags = flags;
    if (argc) fielddesc_setfloatarg(&x->x_outlinecolor, argc--, argv++);
    else FIELDDESC_SETFLOAT(&x->x_outlinecolor, 0);
    if (argc) fielddesc_setfloatarg(&x->x_width, argc--, argv++);
    else FIELDDESC_SETFLOAT(&x->x_width, 1);
    if (argc < 0) argc = 0;
    nxy =  (argc + (argc & 1));
    x->x_npoints = (nxy>>1);
    x->x_vec = (t_fielddesc *)t_getbytes(nxy * sizeof(t_fielddesc));
    for (i = 0, fd = x->x_vec; i < argc; i++, fd++, argv++)
    	fielddesc_setfloatarg(fd, 1, argv);
    if (argc & 1) FIELDDESC_SETFLOAT(fd, 0);

    return (x);
}

/* -------------------- widget behavior for curve ------------ */

static void curve_getrect(t_gobj *z, t_glist *glist,
    t_word *data, t_canvas *template, float basex, float basey,
    int *xp1, int *yp1, int *xp2, int *yp2)
{
    t_curve *x = (t_curve *)z;
    int i, n = x->x_npoints;
    t_fielddesc *f = x->x_vec;
    int x1 = 0x7fffffff, x2 = -0x7fffffff, y1 = 0x7fffffff, y2 = -0x7fffffff;
    for (i = 0, f = x->x_vec; i < n; i++, f += 2)
    {
    	int xloc = glist_xtopixels(glist,
    	    basex + fielddesc_getfloat(f, template, data, 0));
    	int yloc = glist_xtopixels(glist,
    	    basey + fielddesc_getfloat(f+1, template, data, 0));
    	if (xloc < x1) x1 = xloc;
    	if (xloc > x2) x2 = xloc;
    	if (yloc < y1) y1 = yloc;
    	if (yloc < y2) y2 = yloc;
    }
    *xp1 = x1;
    *yp1 = y1;
    *xp2 = x2;
    *yp2 = y2; 
}

static void curve_displace(t_gobj *z, t_glist *glist,
    t_word *data, t_canvas *template, float basex, float basey,
    int dx, int dy)
{
    /* refuse */
}

static void curve_select(t_gobj *z, t_glist *glist,
    t_word *data, t_canvas *template, float basex, float basey,
    int state)
{
    post("curve_select %d", state);
    /* fill in later */
}

static void curve_activate(t_gobj *z, t_glist *glist,
    t_word *data, t_canvas *template, float basex, float basey,
    int state)
{
    post("curve_activate %d", state);
}

static int rangecolor(int n)	/* 0 to 9 in 5 steps */
{
    int n2 = n/2;   	    	/* 0 to 4 */
    int ret = (n << 6);     	/* 0 to 256 in 5 steps */
    if (ret > 255) ret = 255;
    return (ret);
}

static void numbertocolor(int n, char *s)
{
    int red, blue, green;
    if (n < 0) n = 0;
    red = n / 100;
    blue = ((n / 10) % 10);
    green = n % 10;
    sprintf(s, "#%2.2x%2.2x%2.2x", rangecolor(red), rangecolor(blue),
    	rangecolor(green));
}

static void curve_vis(t_gobj *z, t_glist *glist, 
    t_word *data, t_canvas *template, float basex, float basey,
    int vis)
{
    t_curve *x = (t_curve *)z;
    int i, n = x->x_npoints;
    t_fielddesc *f = x->x_vec;
    
    if (vis)
    {
    	if (n > 1)
    	{
    	    int flags = x->x_flags, closed = (flags & CLOSED);
    	    float width = fielddesc_getfloat(&x->x_width, template, data, 1);
    	    char outline[20], fill[20];
    	    if (width < 1) width = 1;
    	    numbertocolor(
    	    	fielddesc_getfloat(&x->x_outlinecolor, template, data, 1),
    	    	outline);
    	    if (flags & CLOSED)
    	    {
    	    	numbertocolor(
    	    	    fielddesc_getfloat(&x->x_fillcolor, template, data, 1),
    	    	    fill);
    	    	sys_vgui(".x%x.c create polygon\\\n",
    	    	    glist_getcanvas(glist));
    	    }
    	    else sys_vgui(".x%x.c create line\\\n",
    	    	    glist_getcanvas(glist));
    	    for (i = 0, f = x->x_vec; i < n; i++, f += 2)
    	    {
    		float xloc = glist_xtopixels(glist,
    	    	    basex + fielddesc_getfloat(f, template, data, 1));
    		float yloc = glist_ytopixels(glist,
    	    	    basey + fielddesc_getfloat(f+1, template, data, 1));
    		sys_vgui("%d %d\\\n", (int)xloc, (int)yloc);
    	    }
    	    sys_vgui("-width %f\\\n",
    	    	fielddesc_getfloat(&x->x_width, template, data, 1));
    	    if (flags & CLOSED) sys_vgui("-fill %s -outline %s\\\n",
    	    	fill, outline);
    	    else sys_vgui("-fill %s\\\n", outline);
    	    if (flags & BEZ) sys_vgui("-smooth 1\\\n");
    	    sys_vgui("-tags curve%x\n", data);
    	}
    	else post("warning: curves need at least two points to be graphed");
    }
    else
    {
    	if (n > 1) sys_vgui(".x%x.c delete curve%x\n",
    	    glist_getcanvas(glist), data);    	
    }
}

static void curve_free(t_curve *x)
{
    t_freebytes(x->x_vec, 2 * x->x_npoints * sizeof(*x->x_vec));
}

t_parentwidgetbehavior curve_widgetbehavior =
{
    curve_getrect,
    curve_displace,
    curve_select,
    curve_activate,
    curve_vis,
};

static void curve_setup(void)
{
    curve_class = class_new(gensym("curve"), curve_new, curve_free,
    	sizeof(t_curve), CLASS_NOINLET, A_GIMME, 0);
    class_setdrawcommand(curve_class);
    class_addcreator(curve_new, gensym("polygon"), A_GIMME, 0);
    class_addcreator(curve_new, gensym("fcurve"), A_GIMME, 0);
    class_addcreator(curve_new, gensym("fpolygon"), A_GIMME, 0);
    class_setparentwidget(curve_class, &curve_widgetbehavior);
}

/* ----------------------- plots ----------------------- */

t_class *plot_class;

typedef struct _plot
{
    t_object x_obj;
    int x_flags;
    t_fielddesc x_outlinecolor;
    t_fielddesc x_width;
    t_fielddesc x_xloc;
    t_fielddesc x_yloc;
    t_fielddesc x_xinc;
    t_fielddesc x_data;
} t_plot;

static void *plot_new(t_symbol *classsym, t_int argc, t_atom *argv)
{
    t_plot *x = (t_plot *)pd_new(plot_class);
    int flags = 0;
    int nxy, i;
    t_fielddesc *fd;
    t_symbol *firstarg = atom_getsymbolarg(0, argc, argv);
    if (!strcmp(firstarg->s_name, "curve"))
    {
    	flags |= BEZ;
    	argc--, argv++;
    }
    if (argc) fielddesc_setarrayarg(&x->x_data, argc--, argv++);
    else FIELDDESC_SETFLOAT(&x->x_data, 1);
    if (argc) fielddesc_setfloatarg(&x->x_outlinecolor, argc--, argv++);
    else FIELDDESC_SETFLOAT(&x->x_outlinecolor, 0);
    if (argc) fielddesc_setfloatarg(&x->x_width, argc--, argv++);
    else FIELDDESC_SETFLOAT(&x->x_width, 1);
    if (argc) fielddesc_setfloatarg(&x->x_xloc, argc--, argv++);
    else FIELDDESC_SETFLOAT(&x->x_xloc, 1);
    if (argc) fielddesc_setfloatarg(&x->x_yloc, argc--, argv++);
    else FIELDDESC_SETFLOAT(&x->x_yloc, 1);
    if (argc) fielddesc_setfloatarg(&x->x_xinc, argc--, argv++);
    else FIELDDESC_SETFLOAT(&x->x_xinc, 1);
    x->x_flags = flags;
    return (x);
}

/* -------------------- widget behavior for plot ------------ */

static void plot_getrect(t_gobj *z, t_glist *glist,
    t_word *data, t_canvas *template, float basex, float basey,
    int *xp1, int *yp1, int *xp2, int *yp2)
{
    	/* not yet */
    *xp1 = 0;
    *yp1 = 0;
    *xp2 = 0;
    *yp2 = 0; 
}

static void plot_displace(t_gobj *z, t_glist *glist,
    t_word *data, t_canvas *template, float basex, float basey,
    int dx, int dy)
{
    	/* not yet */
}

static void plot_select(t_gobj *z, t_glist *glist,
    t_word *data, t_canvas *template, float basex, float basey,
    int state)
{
    	/* not yet */
}

static void plot_activate(t_gobj *z, t_glist *glist,
    t_word *data, t_canvas *template, float basex, float basey,
    int state)
{
    	/* not yet */
}

static void plot_vis(t_gobj *z, t_glist *glist, 
    t_word *data, t_canvas *template, float basex, float basey,
    int vis)
{
    t_plot *x = (t_plot *)z;
    int arrayonset, elemsize, yonset;
    t_field *arrayfield, *fy;
    int fieldtype;
    t_symbol *arraytemplatesym;
    t_canvas *arraytemplate;
    	/* find the data and verify it's an array */
    if (x->x_data.fd_type != A_ARRAY || !x->x_data.fd_var)
    {
    	error("plot: needs an array field");
    	return;
    }
    arrayfield = canvas_find_field(template, x->x_data.fd_un.fd_varsym,
    	&arrayonset);
    if (!arrayfield)
    {
    	error("plot: %s: no such field", x->x_data.fd_un.fd_varsym->s_name);
    	return;
    }
    fieldtype = field_type(arrayfield);
    if (fieldtype != DT_ARRAY)
    {
    	error("plot: %s: not an array", x->x_data.fd_un.fd_varsym->s_name);
    	return;
    }
    arraytemplatesym = field_arraytemplate(arrayfield);
    arraytemplate =  (t_canvas *)pd_findbyclass(arraytemplatesym, canvas_class);
    if (!arraytemplate)
    {
    	error("plot: %s: no such template", arraytemplatesym->s_name);
    	return;
    }
    elemsize = canvas_template_size(arraytemplate);
    if (!(fy = canvas_find_field(arraytemplate, gensym("y"), &yonset))
    	|| field_type(fy) != DT_FLOAT)
    {
    	error("%s: needs floating-point 'y' field", arraytemplatesym->s_name);
    	return;
    }
    if (vis)
    {
    	t_array *a = *(t_array **)(((char *)data) + arrayonset);
    	int nelem = a->a_n;
    	char *elem = ((char *)(a->a_vec)) + yonset;

    	float linewidth =
    	    fielddesc_getfloat(&x->x_width, template, data, 1);
    	float xloc =
    	    fielddesc_getfloat(&x->x_xloc, template, data, 1);
    	float xinc =
    	    fielddesc_getfloat(&x->x_xinc, template, data, 1);
    	float yloc =
    	    fielddesc_getfloat(&x->x_yloc, template, data, 1);
    	
    	char outline[20];
    	int lastpixel = -1, ndrawn = 0;
    	float yval, xpix;
    	int ixpix, i;

    	numbertocolor(
    	    	fielddesc_getfloat(&x->x_outlinecolor, template, data, 1),
    	    	outline);

    	sys_vgui(".x%x.c create line \\\n", glist_getcanvas(glist));

    	for (i = 0; i < nelem; i++)
    	{
    	    yval = *(float *)((elem + elemsize * i) + yonset);
    	    xpix = glist_xtopixels(glist, basex + xloc);
    	    ixpix = xpix + 0.5;
    	    if (ixpix != lastpixel)
    	    {
    	    	sys_vgui("%d %f \\\n", ixpix,
    	    	    glist_ytopixels(glist, basey + yloc + yval));
    	    	ndrawn++;
    	    }
    	    lastpixel = ixpix;
    	    if (ndrawn >= 1000) break;
    	    xloc += 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 + 10,
    	    	    glist_ytopixels(glist, basey + yloc + yval));

    	sys_vgui("-width %f\\\n", linewidth);
    	sys_vgui("-fill %s\\\n", outline);
    	if (x->x_flags & BEZ) sys_vgui("-smooth 1\\\n");

    	sys_vgui("-tags plot%x\n", data);

    }
    else
    {
    	sys_vgui(".x%x.c delete plot%x\n",
    	    glist_getcanvas(glist), data);    	
    }
}

t_parentwidgetbehavior plot_widgetbehavior =
{
    plot_getrect,
    plot_displace,
    plot_select,
    plot_activate,
    plot_vis,
};

static void plot_setup(void)
{
    plot_class = class_new(gensym("plot"), plot_new, 0,
    	sizeof(t_plot), CLASS_NOINLET, A_GIMME, 0);
    class_setdrawcommand(plot_class);
    class_setparentwidget(plot_class, &plot_widgetbehavior);
}

/* ---------------------- setup function ---------------------------- */

void g_data_setup(void)
{
    field_setup();
    curve_setup();
    plot_setup();
}

