/* 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 "m_pd.h"
#include "t_tk.h"
#include "g_canvas.h"
#include <stdio.h>
#include <string.h>

/* -------------- functions common to graphs and canvases -------------- */

static int glist_valid = 10000;

void glist_add(t_glist *x, t_gobj *y)
{
    y->g_next = 0;
    if (!x->gl_list) x->gl_list = y;
    else
    {
    	t_gobj *y2;
    	for (y2 = x->gl_list; y2->g_next; y2 = y2->g_next);
    	y2->g_next = y;
    }
    if (canvas_isvisible(glist_getcanvas(x))) gobj_vis(y, x, 1);
    if (class_isdrawcommand(y->g_pd)) 
    	canvas_redrawallfortemplate(glist_getcanvas(x));
    if (y->g_pd == field_class)
    	canvas_zapallfortemplate(glist_getcanvas(x));
}

    /* delete an object from a glist and free it */
void glist_delete(t_glist *x, t_gobj *y)
{
    t_gobj *g;
    t_gotfn chkdsp = zgetfn(&y->g_pd, gensym("dsp"));
    int drawcommand = class_isdrawcommand(y->g_pd);
    int field = (y->g_pd == field_class);
    	/*  LATER decide whether all visible glists must have an editor? */
    if (canvas_isvisible(glist_getcanvas(x)) && x->gl_editor)
    {
    	if (x->gl_editor->e_grab == y) x->gl_editor->e_grab = 0;
    	if (glist_isselected(x, y)) glist_deselect(x, y);
    }
    gobj_delete(y, x);
    if (canvas_isvisible(glist_getcanvas(x))) gobj_vis(y, x, 0);
    if (x->gl_list == y) x->gl_list = y->g_next;
    else for (g = x->gl_list; g; g = g->g_next)
    	if (g->g_next == y)
    {
    	g->g_next = y->g_next;
    	break;
    }
    pd_free(&y->g_pd);
    if (chkdsp) canvas_update_dsp();
    if (field) canvas_zapallfortemplate(glist_getcanvas(x));
    if (drawcommand) canvas_redrawallfortemplate(glist_getcanvas(x));
    x->gl_valid = ++glist_valid;
}

void glist_retext(t_glist *glist, t_text *y)
{
    t_canvas *c = glist_getcanvas(glist);
    if (canvas_isvisible(c))
    {
    	t_rtext *rt = glist_findrtext(glist, y);
    	rtext_retext(rt);
    }
}

void glist_grab(t_glist *x, t_gobj *y, t_int xpos, t_int ypos)
{
    x->gl_editor->e_onmotion = MA_PASSOUT;
    x->gl_editor->e_grab = y;
    x->gl_editor->e_xwas = xpos;
    x->gl_editor->e_ywas = ypos;
}

t_canvas *glist_getcanvas(t_glist *x)
{
    while (x->gl_owner) x = x->gl_owner;
    if (pd_class(&x->gl_pd) != canvas_class) bug("glist_getcanvas");
    return((t_canvas *)x);
}

int glist_isvisible(t_glist *x)
{
    return (canvas_isvisible(glist_getcanvas(x)));
}

int glist_getfont(t_glist *x)
{
    return (canvas_getfont(glist_getcanvas(x)));
}

void glist_init(t_glist *x)
{
    x->gl_owner = 0;
    x->gl_editor = 0;
    x->gl_list = 0;
    x->gl_gobj.g_next = 0;
    x->gl_xtick.k_lperb = 0;
    x->gl_ytick.k_lperb = 0;
    x->gl_nxlabels = 0;
    x->gl_xlabel = (t_symbol **)t_getbytes(0);
    x->gl_nylabels = 0;
    x->gl_ylabel = (t_symbol **)t_getbytes(0);
    x->gl_valid = ++glist_valid;
    x->gl_stub = gstub_new(x, 0);
}

void glist_cleanup(t_glist *x)
{
    gstub_cutoff(x->gl_stub);
}

/* --------------------- functions peculiar to graphs -------------- */

static void graph_redraw(t_glist *x);

static void graph_bounds(t_glist *x, t_floatarg x1, t_floatarg y1,
    t_floatarg x2, t_floatarg y2)
{
    x->gl_x1 = x1;
    x->gl_x2 = x2;
    x->gl_y1 = y1;
    x->gl_y2 = y2;
    if (x->gl_x2 == x->gl_x1 ||
    	x->gl_y2 == x->gl_y1)
    {
	error("graph: empty bounds rectangle");
	x1 = y1 = 0;
	x2 = y2 = 1;
    }
    graph_redraw(x);
}

void graph_xticks(t_glist *x, t_floatarg point, t_floatarg inc, t_floatarg f)
{
    x->gl_xtick.k_point = point;
    x->gl_xtick.k_inc = inc;
    x->gl_xtick.k_lperb = f;
    graph_redraw(x);
}

void graph_yticks(t_glist *x, t_floatarg point, t_floatarg inc, t_floatarg f)
{
    x->gl_ytick.k_point = point;
    x->gl_ytick.k_inc = inc;
    x->gl_ytick.k_lperb = f;
    graph_redraw(x);
}

void graph_xlabel(t_glist *x, t_symbol *s, int argc, t_atom *argv)
{
    int i;
    if (argc < 1) error("graph_xlabel: no y value given");
    else
    {
    	x->gl_xlabely = atom_getfloat(argv);
    	argv++; argc--;
    	x->gl_xlabel = (t_symbol **)t_resizebytes(x->gl_xlabel, 
    	    x->gl_nxlabels * sizeof (t_symbol *), argc * sizeof (t_symbol *));
    	x->gl_nxlabels = argc;
    	for (i = 0; i < argc; i++) x->gl_xlabel[i] = atom_gensym(&argv[i]);
    }
    graph_redraw(x);
}
    
void graph_ylabel(t_glist *x, t_symbol *s, int argc, t_atom *argv)
{
    int i;
    if (argc < 1) error("graph_ylabel: no x value given");
    else
    {
    	x->gl_ylabelx = atom_getfloat(argv);
    	argv++; argc--;
    	x->gl_ylabel = (t_symbol **)t_resizebytes(x->gl_ylabel, 
    	    x->gl_nylabels * sizeof (t_symbol *), argc * sizeof (t_symbol *));
    	x->gl_nylabels = argc;
    	for (i = 0; i < argc; i++) x->gl_ylabel[i] = atom_gensym(&argv[i]);
    }
    graph_redraw(x);
}

/****** routines to convert pixels to X or Y value and vice versa ******/

float glist_pixelstox(t_glist *x, float xpix)
{
    return (x->gl_px1 + (x->gl_px2 - x->gl_px1)  *
    	(xpix - x->gl_x1) / (x->gl_x2 - x->gl_x1));
}

float glist_pixelstoy(t_glist *x, float ypix)
{
    return (x->gl_py1 + (x->gl_py2 - x->gl_py1)  *
    	(ypix - x->gl_y1) / (x->gl_y2 - x->gl_y1));
}

float glist_xtopixels(t_glist *x, float xval)
{
    return (x->gl_px1 + (x->gl_px2 - x->gl_px1)  *
    	(xval - x->gl_x1) / (x->gl_x2 - x->gl_x1));
}

float glist_ytopixels(t_glist *x, float yval)
{
    return (x->gl_py1 + (x->gl_py2 - x->gl_py1)  *
    	(yval - x->gl_y1) / (x->gl_y2 - x->gl_y1));
}

void graph_vis(t_gobj *g, t_glist *unused_glist, t_int vis)
{
    t_glist *x = (t_glist *)g;
    t_glist *gl = (t_glist *)g;
    if (vis)
    {
    	int i;
    	float f;
    	t_gobj *g;
    	float plotx1 = x->gl_px1;
    	float ploty1 = x->gl_py1;
    	float plotx2 = x->gl_px2;
    	float ploty2 = x->gl_py2;
    	    /* draw a rectangle around the graph */
    	sys_vgui(".x%x.c create line\
    	    %f %f %f %f %f %f %f %f %f %f -tags graph%x\n",
	    glist_getcanvas(x->gl_owner),
    	    plotx1, ploty1, plotx1, ploty2, plotx2, ploty2,
    	    plotx2, ploty1, plotx1, ploty1, x);

    	    /* draw ticks on horizontal borders.  If lperb field is
    	    zero, this is disabled. */
    	if (x->gl_xtick.k_lperb)
    	{
    	    for (i = 0, f = x->gl_xtick.k_point;
    		f < 0.99 * x->gl_x2 + 0.01*x->gl_x1; i++,
    		    f += x->gl_xtick.k_inc)
    	    {
    		float tickpix = (i % x->gl_xtick.k_lperb ? 2 : 4);
    		sys_vgui(".x%x.c create line %f %f %f %f -tags graph%x\n",
	    	    glist_getcanvas(x->gl_owner),
	    	    glist_xtopixels(gl, f), ploty1,
	    	    glist_xtopixels(gl, f), ploty1 - tickpix, x);
    		sys_vgui(".x%x.c create line %f %f %f %f -tags graph%x\n",
	    	    glist_getcanvas(x->gl_owner),
	    	    glist_xtopixels(gl, f), ploty2,
	    	    glist_xtopixels(gl, f), ploty2 + tickpix, x);
    	    }
    	    for (i = 1, f = x->gl_xtick.k_point - x->gl_xtick.k_inc;
    		f > 0.99 * x->gl_x1 + 0.01*x->gl_x2;
    		    i++, f -= x->gl_xtick.k_inc)
    	    {
    		float tickpix = (i % x->gl_xtick.k_lperb ? 2 : 4);
    		sys_vgui(".x%x.c create line %f %f %f %f -tags graph%x\n",
	    	    glist_getcanvas(x->gl_owner),
	    	    glist_xtopixels(gl, f), ploty1,
	    	    glist_xtopixels(gl, f), ploty1 - tickpix, x);
    		sys_vgui(".x%x.c create line %f %f %f %f -tags graph%x\n",
	    	    glist_getcanvas(x->gl_owner),
	    	    glist_xtopixels(gl, f), ploty2,
	    	    glist_xtopixels(gl, f), ploty2 + tickpix, x);
    	    }
    	}

    	    /* draw ticks in vertical borders*/
    	if (x->gl_ytick.k_lperb)
    	{
    	    for (i = 0, f = x->gl_ytick.k_point;
    		f < 0.99 * x->gl_y2 + 0.01*x->gl_y1;
    		    i++, f += x->gl_ytick.k_inc)
    	    {
    		float tickpix = (i % x->gl_ytick.k_lperb ? 2 : 4);
    		sys_vgui(".x%x.c create line %f %f %f %f -tags graph%x\n",
	    	    glist_getcanvas(x->gl_owner),
	    	    plotx1, glist_ytopixels(gl, f), 
	    	    plotx1 + tickpix, glist_ytopixels(gl, f), x);
    		sys_vgui(".x%x.c create line %f %f %f %f -tags graph%x\n",
	    	    glist_getcanvas(x->gl_owner),
	    	    plotx2, glist_ytopixels(gl, f), 
	    	    plotx2 - tickpix, glist_ytopixels(gl, f), x);
    	    }
    	    for (i = 1, f = x->gl_ytick.k_point - x->gl_ytick.k_inc;
    		f > 0.99 * x->gl_y1 + 0.01*x->gl_y2;
    		    i++, f -= x->gl_ytick.k_inc)
    	    {
    		float tickpix = (i % x->gl_ytick.k_lperb ? 2 : 4);
    		sys_vgui(".x%x.c create line %f %f %f %f -tags graph%x\n",
	    	    glist_getcanvas(x->gl_owner),
	    	    plotx1, glist_ytopixels(gl, f), 
	    	    plotx1 + tickpix, glist_ytopixels(gl, f), x);
    		sys_vgui(".x%x.c create line %f %f %f %f -tags graph%x\n",
	    	    glist_getcanvas(x->gl_owner),
	    	    plotx2, glist_ytopixels(gl, f), 
	    	    plotx2 - tickpix, glist_ytopixels(gl, f), x);
    	    }
    	}
    	    /* draw x labels */
    	for (i = 0; i < x->gl_nxlabels; i++)
    	    sys_vgui(".x%x.c create text\
    	    	%f %f -text {%s} -font %s -tags graph%x\n",
    	    	glist_getcanvas(x),
	    	glist_xtopixels(gl, atof(x->gl_xlabel[i]->s_name)),
	    	glist_ytopixels(gl, x->gl_xlabely), x->gl_xlabel[i]->s_name,
	    	glist_getfont(x), x);

    	    /* draw y labels */
    	for (i = 0; i < x->gl_nylabels; i++)
    	    sys_vgui(".x%x.c create text\
    	        %f %f -text {%s} -font %s -tags graph%x\n",
    	    	glist_getcanvas(x),
	    	glist_xtopixels(gl, x->gl_ylabelx),
	    	glist_ytopixels(gl, atof(x->gl_ylabel[i]->s_name)),
	    	x->gl_ylabel[i]->s_name,
	    	glist_getfont(x), x);

    	    /* draw contents of graph as glist */
     	for (g = x->gl_list; g; g = g->g_next)
     	    gobj_vis(g, x, 1);
    }
    else
    {
    	sys_vgui(".x%x.c delete graph%x\n",
    	    glist_getcanvas(x->gl_owner), x);
     	for (g = x->gl_list; g; g = g->g_next)
     	    gobj_vis(g, x, 0);
    }
}

static void graph_redraw(t_glist *x)
{  
    if (glist_isvisible(x))
    {
    	graph_vis(&x->gl_gobj, 0, 0); 
    	graph_vis(&x->gl_gobj, 0, 1);
    }
}

t_class *graph_class;

/* --------------------------- widget behavior  ------------------- */

static void graph_getrect(t_gobj *z, t_glist *glist,
    int *xp1, int *yp1, int *xp2, int *yp2)
{
    t_glist *x = (t_glist *)z;
    float x1 = x->gl_px1, x2 = x->gl_px2, tmp;
    float y1 = x->gl_py1, y2 = x->gl_py2;
    if (x1 > x2) tmp = x2, x2 = x1, x1 = tmp;
    if (y1 > y2) tmp = y2, y2 = y1, y1 = tmp;
    *xp1 = x1;
    *yp1 = y1;
    *xp2 = x2;
    *yp2 = y2;
}

static void graph_displace(t_gobj *z, t_glist *glist, int dx, int dy)
{
    t_glist *x = (t_glist *)z;
    x->gl_px1 += dx;
    x->gl_py1 += dy;
    x->gl_px2 += dx;
    x->gl_py2 += dy;
    graph_redraw(x);
}

static void graph_select(t_gobj *z, t_glist *glist, int state)
{
    sys_vgui(".x%x.c itemconfigure graph%x -fill %s\n", glist_getcanvas(glist), 
    	z, (state? "blue" : "black"));
}

static void graph_activate(t_gobj *z, t_glist *glist, int state)
{
    /* fill in later */
}

static void graph_delete(t_gobj *z, t_glist *glist)
{
    t_glist *x = (t_glist *)z;
    t_gobj *y;
    while (y = x->gl_list) glist_delete(x, y);
    if (glist_isvisible(x))
    	sys_vgui(".x%x.c delete graph%x\n", glist_getcanvas(glist), x);
}

void graph_save(t_gobj *z, t_binbuf *b)
{
    t_gobj *y;
    t_glist *x = (t_glist *)z;
    binbuf_addv(b, "sssffffffff;", gensym("#X"), gensym("graph"),
    	x->gl_name,
    	x->gl_x1, x->gl_y1,
    	x->gl_x2, x->gl_y2,
    	x->gl_px1, x->gl_py1,
    	x->gl_px2, x->gl_py2);
    for (y = x->gl_list; y; y = y->g_next) gobj_save(y, b);
    binbuf_addv(b, "ss;", gensym("#X"), gensym("pop"));
}

extern void graph_vis(t_gobj *x, t_glist *glist, t_int flag);

t_widgetbehavior graph_widgetbehavior =
{
    graph_getrect,
    graph_displace,
    graph_select,
    graph_activate,
    graph_delete,
    graph_vis,
    graph_save,
};

void glist_graph(t_glist *g, t_symbol *s, int argc, t_atom *argv)
{
    static int gcount = 0;
    int zz;
    int menu = 0;
    t_symbol *sym = atom_getsymbolarg(0, argc, argv);   
    float x1 = atom_getfloatarg(1, argc, argv);  
    float y1 = atom_getfloatarg(2, argc, argv);  
    float x2 = atom_getfloatarg(3, argc, argv);  
    float y2 = atom_getfloatarg(4, argc, argv);  
    float px1 = atom_getfloatarg(5, argc, argv);  
    float py1 = atom_getfloatarg(6, argc, argv);  
    float px2 = atom_getfloatarg(7, argc, argv);  
    float py2 = atom_getfloatarg(8, argc, argv);  
    t_glist *x = (t_glist *)pd_new(graph_class);
    char *str;
    if (!*sym->s_name)
    {
    	char buf[40];
    	sprintf(buf, "graph%d", ++gcount);
    	sym = gensym(buf);
    	menu = 1;
    }
    else if (!strncmp((str = s->s_name), "graph", 5)
    	&& (zz = atoi(str + 5)) > gcount) gcount = zz;
    if (x1 == x2 || y1 == y2) x1 = 0, x2 = 100, y1 = -1, y2 = 1;
    if (px1 == px2 || py1 == py2) px1 = 200, py1 = 320, px2 = 600, py2 = 20;
    glist_init(x);
    x->gl_name = sym;
    x->gl_x1 = x1;
    x->gl_x2 = x2;
    x->gl_y1 = y1;  /* no vertical flip here.  LATER rethink this */
    x->gl_y2 = y2;
    x->gl_px1 = px1;
    x->gl_py1 = py1;
    x->gl_px2 = px2;
    x->gl_py2 = py2;
    pd_bind(&x->gl_gobj.g_pd, sym);
    x->gl_owner = g;
    if (!menu) pd_pushsym(&x->gl_gobj.g_pd);
    glist_add(g, &x->gl_gobj);
}

    /* find the graph most recently added to this glist; if none
    	exists, we'll just make one.  x1, etc., are probably fossils. */

t_glist *glist_findgraph(t_glist *x, float x1, float x2, float y1, float y2)
{
    t_gobj *y = 0, *z;
    for (z = x->gl_list; z; z = z->g_next)
    	if (pd_class(&z->g_pd) == graph_class) y = z;
    if (y) return ((t_glist *)y);
    vmess(&x->gl_pd, gensym("graph"), "");
    return (glist_findgraph(x, 0, 0, 0, 0));
}

void graph_free(t_glist *x) 	/* LATER compare me with canvas_free() */
{
    t_gobj *y;
    while (y = x->gl_list) glist_delete(x, y);
    pd_unbind(&x->gl_gobj.g_pd, x->gl_name);
    glist_cleanup(x);
}

extern void glist_text(t_glist *gl, t_symbol *s, int argc, t_atom *argv);
extern void graph_array(t_glist *canvas);

void g_graph_setup()
{
    graph_class = class_new(gensym("graph"), 0, graph_free,
    	sizeof(t_glist), CLASS_GOBJ, 0);
    class_setwidget(graph_class, &graph_widgetbehavior);
    class_addmethod(graph_class, (t_method)graph_bounds, gensym("bounds"),
    	A_FLOAT, A_FLOAT, A_FLOAT, A_FLOAT, 0);
    class_addmethod(graph_class, pd_popsym, gensym("pop"), 0);
    class_addmethod(graph_class, (t_method)graph_xticks, gensym("xticks"),
    	A_FLOAT, A_FLOAT, A_FLOAT, 0);
    class_addmethod(graph_class, graph_xlabel, gensym("xlabel"), A_GIMME, 0);
    class_addmethod(graph_class, (t_method)graph_yticks, gensym("yticks"),
    	A_FLOAT, A_FLOAT, A_FLOAT, 0);
    class_addmethod(graph_class, graph_ylabel, gensym("ylabel"), A_GIMME, 0);
    class_addmethod(graph_class, glist_text, gensym("text"),
    	A_GIMME, A_NULL);
    class_addmethod(graph_class, (t_method)graph_array, gensym("array"),
    	A_SYMBOL, A_FLOAT, A_SYMBOL, A_NULL);
}
