/* 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 GATE_MINOUTS       1
#define GATE_C74MAXOUTS  100
#define GATE_DEFOUTS       1

typedef struct _gate
{
    t_object    x_ob;
    int         x_open;
    t_pd       *x_proxy;
    int         x_nouts;  /* requested + 1 (as allocated) */
    t_outlet  **x_outs;
} t_gate;

typedef struct _gate_proxy
{
    t_object  p_ob;
    t_gate   *p_master;
} t_gate_proxy;

static t_class *gate_class;
static t_class *gate_proxy_class;

static void gate_proxy_bang(t_gate_proxy *x)
{
    t_gate *master = x->p_master;
    if (master->x_open)
	outlet_bang(master->x_outs[master->x_open]);
}

static void gate_proxy_float(t_gate_proxy *x, t_float f)
{
    t_gate *master = x->p_master;
    if (master->x_open)
	outlet_float(master->x_outs[master->x_open], f);
}

static void gate_proxy_symbol(t_gate_proxy *x, t_symbol *s)
{
    t_gate *master = x->p_master;
    if (master->x_open)
	outlet_symbol(master->x_outs[master->x_open], s);
}

static void gate_proxy_pointer(t_gate_proxy *x, t_gpointer *gp)
{
    t_gate *master = x->p_master;
    if (master->x_open)
	outlet_pointer(master->x_outs[master->x_open], gp);
}

static void gate_proxy_list(t_gate_proxy *x,
			    t_symbol *s, int ac, t_atom *av)
{
    t_gate *master = x->p_master;
    if (master->x_open)
	outlet_list(master->x_outs[master->x_open], s, ac, av);
}

static void gate_proxy_anything(t_gate_proxy *x,
				t_symbol *s, int ac, t_atom *av)
{
    t_gate *master = x->p_master;
    if (master->x_open)
	outlet_anything(master->x_outs[master->x_open], s, ac, av);
}

static void gate_float(t_gate *x, t_float f)
{
    int i = (int)f;
    if (i < 0) i = 1;
    if (i >= x->x_nouts) i = x->x_nouts - 1;
    x->x_open = i;
}

static void gate_bang(t_gate *x)
{
    outlet_float(x->x_outs[1], x->x_open);
}

static void gate_free(t_gate *x)
{
    if (x->x_proxy) pd_free(x->x_proxy);
    if (x->x_outs)
	freebytes(x->x_outs, x->x_nouts * sizeof(*x->x_outs));
}

static void *gate_new(t_floatarg f1, t_floatarg f2)
{
    t_gate *x;
    int i, nouts = (int)f1;
    t_outlet **outs;
    t_pd *proxy;
    if (nouts < GATE_MINOUTS)
	nouts = GATE_DEFOUTS;
    if (nouts > GATE_C74MAXOUTS)
	fittermax_rangewarning(gate_class, GATE_C74MAXOUTS, "outlets");
    nouts++;  /* for convenience (the cost is one pointer) */
    if (!(outs = (t_outlet **)getbytes(nouts * sizeof(*outs))))
	return (0);
    if (!(proxy = pd_new(gate_proxy_class)))
    {
	freebytes(outs, nouts * sizeof(*outs));
	return (0);
    }
    x = (t_gate *)pd_new(gate_class);
    x->x_nouts = nouts;
    x->x_outs = outs;
    x->x_proxy = proxy;
    ((t_gate_proxy *)proxy)->p_master = x;
    /* from max sdk manual: ``The dst parameter can be changed (or set to 0)
       dynamically with the inlet_to function...  The gate object uses this
       technique to assign its inlet to one of several outlets, or no outlet
       at all.''  We have to use a proxy, because Pd's outlet is not a t_pd
       (besides, Pd does not handle inlets with null destination). */
    inlet_new((t_object *)x, proxy, 0, 0);
    for (i = 1; i < nouts; i++)
	x->x_outs[i] = outlet_new((t_object *)x, &s_anything);
    gate_float(x, (f2 > 0 ? f2 : 0));  /* CHECKED */
    return (x);
}

void gate_setup(void)
{
    gate_class = class_new(gensym("gate"),
			   (t_newmethod)gate_new,
			   (t_method)gate_free,
			   sizeof(t_gate), 0,
			   A_DEFFLOAT, A_DEFFLOAT, 0);
    class_addfloat(gate_class, gate_float);
    class_addbang(gate_class, gate_bang);
    gate_proxy_class = class_new(gensym("_gate_proxy"), 0, 0,
				 sizeof(t_gate_proxy),
				 CLASS_PD | CLASS_NOINLET, 0);
    class_addfloat(gate_proxy_class, gate_proxy_float);
    class_addbang(gate_proxy_class, gate_proxy_bang);
    class_addsymbol(gate_proxy_class, gate_proxy_symbol);
    class_addpointer(gate_proxy_class, gate_proxy_pointer);
    class_addlist(gate_proxy_class, gate_proxy_list);
    class_addanything(gate_proxy_class, gate_proxy_anything);
    fitter_setup(gate_class, 0);
}
