/* Copyright (c) 2002-2003 krzYszcz and others.
 * For information on usage and redistribution, and for a DISCLAIMER OF ALL
 * WARRANTIES, see the file, "LICENSE.txt," in this distribution.  */

#include "m_pd.h"
#include "g_canvas.h"
#include "unstable/pd_imp.h"
#include "common/loud.h"

/* LATER fragilize */

typedef struct _universal
{
    t_object  x_ob;
    t_glist  *x_glist;
    t_int     x_descend;
} t_universal;

static t_class *universal_class;

static void universal_dobang(t_glist *glist, int descend, t_symbol *cname)
{
    t_gobj *g;
    for (g = glist->gl_list; g; g = g->g_next)
	if (pd_class(&g->g_pd)->c_name == cname)  /* LATER rethink */
	    pd_bang(&g->g_pd);
    if (descend)
	for (g = glist->gl_list; g; g = g->g_next)
	    if (pd_class(&g->g_pd) == canvas_class)  /* LATER rethink */
		universal_dobang((t_glist *)g, descend, cname);
}

static void universal_dofloat(t_glist *glist, int descend, t_symbol *cname,
			      t_float f)
{
    t_gobj *g;
    for (g = glist->gl_list; g; g = g->g_next)
	if (pd_class(&g->g_pd)->c_name == cname)  /* LATER rethink */
	    pd_float(&g->g_pd, f);
    if (descend)
	for (g = glist->gl_list; g; g = g->g_next)
	    if (pd_class(&g->g_pd) == canvas_class)  /* LATER rethink */
		universal_dofloat((t_glist *)g, descend, cname, f);
}

static void universal_dosymbol(t_glist *glist, int descend, t_symbol *cname,
			       t_symbol *s)
{
    t_gobj *g;
    for (g = glist->gl_list; g; g = g->g_next)
	if (pd_class(&g->g_pd)->c_name == cname)  /* LATER rethink */
	    pd_symbol(&g->g_pd, s);
    if (descend)
	for (g = glist->gl_list; g; g = g->g_next)
	    if (pd_class(&g->g_pd) == canvas_class)  /* LATER rethink */
		universal_dosymbol((t_glist *)g, descend, cname, s);
}

static void universal_dopointer(t_glist *glist, int descend, t_symbol *cname,
				t_gpointer *gp)
{
    t_gobj *g;
    for (g = glist->gl_list; g; g = g->g_next)
	if (pd_class(&g->g_pd)->c_name == cname)  /* LATER rethink */
	    pd_pointer(&g->g_pd, gp);
    if (descend)
	for (g = glist->gl_list; g; g = g->g_next)
	    if (pd_class(&g->g_pd) == canvas_class)  /* LATER rethink */
		universal_dopointer((t_glist *)g, descend, cname, gp);
}

static void universal_dolist(t_glist *glist, int descend, t_symbol *cname,
			     int ac, t_atom *av)
{
    t_gobj *g;
    for (g = glist->gl_list; g; g = g->g_next)
	if (pd_class(&g->g_pd)->c_name == cname)  /* LATER rethink */
	    pd_list(&g->g_pd, &s_list, ac, av);
    if (descend)
	for (g = glist->gl_list; g; g = g->g_next)
	    if (pd_class(&g->g_pd) == canvas_class)  /* LATER rethink */
		universal_dolist((t_glist *)g, descend, cname, ac, av);
}

static void universal_doanything(t_glist *glist, int descend, t_symbol *cname,
				 t_symbol *s, int ac, t_atom *av)
{
    t_gobj *g;
    for (g = glist->gl_list; g; g = g->g_next)
	if (pd_class(&g->g_pd)->c_name == cname)  /* LATER rethink */
	    typedmess(&g->g_pd, s, ac, av);
    if (descend)
	for (g = glist->gl_list; g; g = g->g_next)
	    if (pd_class(&g->g_pd) == canvas_class)  /* LATER rethink */
		universal_doanything((t_glist *)g, descend, cname, s, ac, av);
}

/* LATER rethink type-checking -- it is borrowed from typedmess().
   Anyway, do it once, before traversal, bypassing the generic mechanism
   performed for every object. */
static void universal_anything(t_universal *x, t_symbol *s, int ac, t_atom *av)
{
    /* CHECKED selector without arguments ignored with no complaints */
    if (x->x_glist && s && ac)
    {
	if (av->a_type == A_FLOAT)
	{
	    if (ac > 1)
		universal_dolist(x->x_glist, x->x_descend, s, ac, av);
	    else
		universal_dofloat(x->x_glist, x->x_descend, s, av->a_w.w_float);
	}
	else if (av->a_type == A_SYMBOL)
	{
	    if (av->a_w.w_symbol == &s_bang)
		universal_dobang(x->x_glist, x->x_descend, s);
	    else if (av->a_w.w_symbol == &s_float)
	    {
		if (ac == 1)
		    universal_dofloat(x->x_glist, x->x_descend, s, 0.);
		else if (av[1].a_type == A_FLOAT)
		    universal_dofloat(x->x_glist, x->x_descend, s,
				      av[1].a_w.w_float);
		else
		    loud_error((t_pd *)x, "Bad argument for message 'float'");
	    }
	    else if (av->a_w.w_symbol == &s_symbol)
		universal_dosymbol(x->x_glist, x->x_descend, s,
				   (ac > 1 && av[1].a_type == A_SYMBOL ?
				    av[1].a_w.w_symbol : &s_));
	    else if (av->a_w.w_symbol == &s_list)
		universal_dolist(x->x_glist, x->x_descend, s, ac - 1, av + 1);
	    else
		universal_doanything(x->x_glist, x->x_descend, s,
				     av->a_w.w_symbol, ac - 1, av + 1);
	}
	if (av->a_type == A_POINTER)
	    universal_dopointer(x->x_glist, x->x_descend, s,
				av->a_w.w_gpointer);
    }
}

static void universal_send(t_universal *x, t_symbol *s, int ac, t_atom *av)
{
    if (ac && av->a_type == A_SYMBOL)
	universal_anything(x, av->a_w.w_symbol, ac - 1, av + 1);
    /* CHECKED: else ignored without complaints */
}

static void *universal_new(t_floatarg f)
{
    t_universal *x = (t_universal *)pd_new(universal_class);
    x->x_glist = canvas_getcurrent();
    x->x_descend = ((int)f != 0);  /* CHECKED */
    return (x);
}

void universal_setup(void)
{
    universal_class = class_new(gensym("universal"),
			      (t_newmethod)universal_new, 0,
			      sizeof(t_universal), 0, A_DEFFLOAT, 0);
    class_addanything(universal_class, universal_anything);
    /* CHECKED: 'send', not 'sendmessage' */
    class_addmethod(universal_class, (t_method)universal_send,
		    gensym("send"), A_GIMME, 0);
}