/* Copyright (c) 2002-2005 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 "common/loud.h"
#include "common/fitter.h"

#define SWITCH_MININLETS       2  /* LATER consider using 1 (with a warning) */
#define SWITCH_C74MAXINLETS  100
#define SWITCH_DEFINLETS       2

typedef struct _switch
{
    t_object  x_ob;
    int       x_open;
    int       x_ninlets;   /* not counting left one */
    int       x_nproxies;  /* as requested (and allocated) */
    t_pd    **x_proxies;
} t_switch;

typedef struct _switch_proxy
{
    t_object   p_ob;
    t_switch  *p_master;
    int        p_id;
} t_switch_proxy;

static t_class *switch_class;
static t_class *switch_proxy_class;

static void switch_proxy_bang(t_switch_proxy *x)
{
    t_switch *master = x->p_master;
    if (master->x_open == x->p_id)
	outlet_bang(((t_object *)master)->ob_outlet);
}

static void switch_proxy_float(t_switch_proxy *x, t_float f)
{
    t_switch *master = x->p_master;
    if (master->x_open == x->p_id)
	outlet_float(((t_object *)master)->ob_outlet, f);
}

static void switch_proxy_symbol(t_switch_proxy *x, t_symbol *s)
{
    t_switch *master = x->p_master;
    if (master->x_open == x->p_id)
	outlet_symbol(((t_object *)master)->ob_outlet, s);
}

static void switch_proxy_pointer(t_switch_proxy *x, t_gpointer *gp)
{
    t_switch *master = x->p_master;
    if (master->x_open == x->p_id)
	outlet_pointer(((t_object *)master)->ob_outlet, gp);
}

static void switch_proxy_list(t_switch_proxy *x,
			      t_symbol *s, int ac, t_atom *av)
{
    t_switch *master = x->p_master;
    if (master->x_open == x->p_id)
	outlet_list(((t_object *)master)->ob_outlet, s, ac, av);
}

static void switch_proxy_anything(t_switch_proxy *x,
				  t_symbol *s, int ac, t_atom *av)
{
    t_switch *master = x->p_master;
    if (master->x_open == x->p_id)
	outlet_anything(((t_object *)master)->ob_outlet, s, ac, av);
}

static void switch_float(t_switch *x, t_float f)
{
    int i = (int)f;
    if (i < 0) i = -i;
    if (i > x->x_ninlets) i = x->x_ninlets;
    x->x_open = i;
}

static void switch_bang(t_switch *x)
{
    outlet_float(((t_object *)x)->ob_outlet, x->x_open);
}

static void switch_free(t_switch *x)
{
    if (x->x_proxies)
    {
	int i = x->x_ninlets;
	while (i--) pd_free(x->x_proxies[i]);
	freebytes(x->x_proxies, x->x_nproxies * sizeof(*x->x_proxies));
    }
}

static void *switch_new(t_floatarg f1, t_floatarg f2)
{
    t_switch *x;
    int i, ninlets, nproxies = (int)f1;
    t_pd **proxies;
    if (nproxies < SWITCH_MININLETS)
	nproxies = SWITCH_DEFINLETS;
    if (nproxies > SWITCH_C74MAXINLETS)
	fittermax_rangewarning(switch_class, SWITCH_C74MAXINLETS, "inlets");
    if (!(proxies = (t_pd **)getbytes(nproxies * sizeof(*proxies))))
	return (0);
    for (ninlets = 0; ninlets < nproxies; ninlets++)
	if (!(proxies[ninlets] = pd_new(switch_proxy_class))) break;
    if (ninlets < SWITCH_MININLETS)
    {
	int i = ninlets;
	while (i--) pd_free(proxies[i]);
	freebytes(proxies, nproxies * sizeof(*proxies));
	return (0);
    }
    x = (t_switch *)pd_new(switch_class);
    x->x_ninlets = ninlets;
    x->x_nproxies = nproxies;
    x->x_proxies = proxies;
    for (i = 0; i < ninlets; i++)
    {
	t_switch_proxy *y = (t_switch_proxy *)proxies[i];
	y->p_master = x;
	y->p_id = i + 1;
	inlet_new((t_object *)x, (t_pd *)y, 0, 0);
    }
    outlet_new((t_object *)x, &s_anything);
    switch_float(x, (f2 > 0 ? f2 : 0));  /* CHECKED */
    return (x);
}

void switch_setup(void)
{
    switch_class = class_new(gensym("switch"),
			     (t_newmethod)switch_new,
			     (t_method)switch_free,
			     sizeof(t_switch), 0,
			     A_DEFFLOAT, A_DEFFLOAT, 0);
    class_addfloat(switch_class, switch_float);
    class_addbang(switch_class, switch_bang);
    switch_proxy_class = class_new(gensym("_switch_proxy"), 0, 0,
				   sizeof(t_switch_proxy),
				   CLASS_PD | CLASS_NOINLET, 0);
    class_addfloat(switch_proxy_class, switch_proxy_float);
    class_addbang(switch_proxy_class, switch_proxy_bang);
    class_addsymbol(switch_proxy_class, switch_proxy_symbol);
    class_addpointer(switch_proxy_class, switch_proxy_pointer);
    class_addlist(switch_proxy_class, switch_proxy_list);
    class_addanything(switch_proxy_class, switch_proxy_anything);
    fitter_setup(switch_class, 0);
}
