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

/* CHECKED refman's rubbish: 1st outlet used only if there is a match,
   unchanged messages go through the 2nd outlet (no bang there),
   3rd argument ignored (no single-replacement mode). */

#include <string.h>
#include "m_pd.h"
#include "common/grow.h"

#define SUBSTITUTE_INISIZE   32  /* LATER rethink */
#define SUBSTITUTE_MAXSIZE  256

typedef struct _substitute
{
    t_object   x_ob;
    t_pd      *x_proxy;
    t_atom     x_match;
    t_atom     x_repl;
    int        x_size;    /* as allocated */
    t_atom    *x_message;
    t_atom     x_messini[SUBSTITUTE_INISIZE];
    int        x_entered;
    t_atom     x_auxmatch;
    t_atom     x_auxrepl;
    t_outlet  *x_passout;
} t_substitute;

typedef struct _substitute_proxy
{
    t_object       p_ob;
    t_atom        *p_match;  /* pointing to parent's (aux)match */
    t_atom        *p_repl;
} t_substitute_proxy;

static t_class *substitute_class;
static t_class *substitute_proxy_class;

/* LATER rethink */
static void substitute_dooutput(t_substitute *x,
				t_symbol *s, int ac, t_atom *av, int pass)
{
    t_outlet *out = (pass ? x->x_passout : ((t_object *)x)->ob_outlet);
    if (s == &s_float)
    {
	if (ac > 1)
	    outlet_list(out, &s_list, ac, av);
	else
	    outlet_float(out, av->a_w.w_float);
    }
    else if (s == &s_bang && !ac)  /* CHECKED */
	outlet_bang(out);
    else if (s == &s_symbol && ac == 1 && av->a_type == A_SYMBOL)
	outlet_symbol(out, av->a_w.w_symbol);
    else if (s)
	outlet_anything(out, s, ac, av);
    else if (ac)
	outlet_list(out, &s_list, ac, av);
}

static int substitute_check(t_substitute *x, t_symbol *s, int ac, t_atom *av)
{
    if (x->x_repl.a_type == A_NULL)
	return (-2);
    /* see substitute_proxy_validate() for possible types and values */
    if (x->x_match.a_type == A_FLOAT)
    {
	t_float f = x->x_match.a_w.w_float;
	int i;
	for (i = 0; i < ac; i++, av++)
	    if (av->a_type == A_FLOAT && av->a_w.w_float == f)
		return (i);
    }
    else if (x->x_match.a_type == A_SYMBOL)
    {
	/* match symbol is validated -- never null */
	t_symbol *match = x->x_match.a_w.w_symbol;
	int i;
	if (s == match)
	    return (-1);
	for (i = 0; i < ac; i++, av++)
	    if (av->a_type == A_SYMBOL && av->a_w.w_symbol == match)
		return (i);
    }
    return (-2);
}

static void substitute_doit(t_substitute *x,
			    t_symbol *s, int ac, t_atom *av, int startndx)
{
    int cnt = ac - startndx;
    if (cnt > 0)
    {
	t_atom *ap = av + startndx;
	if (x->x_match.a_type == A_FLOAT)
	{
	    t_float f = x->x_match.a_w.w_float;
	    while (cnt--)
	    {
		if (ap->a_type == A_FLOAT && ap->a_w.w_float == f)
		    *ap = x->x_repl;
		ap++;
	    }
	}
	else if (x->x_match.a_type == A_SYMBOL)
	{
	    t_symbol *match = x->x_match.a_w.w_symbol;
	    while (cnt--)
	    {
		if (ap->a_type == A_SYMBOL && ap->a_w.w_symbol == match)
		    *ap = x->x_repl;
		ap++;
	    }
	}
    }
    substitute_dooutput(x, s, ac, av, 0);
}

static void substitute_anything(t_substitute *x,
				t_symbol *s, int ac, t_atom *av)
{
    int matchndx = substitute_check(x, s, ac, av);
    if (matchndx < -1)
	substitute_dooutput(x, s, ac, av, 1);
    else
    {
	int reentered = x->x_entered;
	int prealloc = !reentered;
	int ntotal = ac;
	t_atom *buf;
	t_substitute_proxy *proxy = (t_substitute_proxy *)x->x_proxy;
	x->x_entered = 1;
	proxy->p_match = &x->x_auxmatch;
	proxy->p_repl = &x->x_auxrepl;
	if (s == &s_) s = 0;
	if (matchndx == -1)
	{
	    if (x->x_repl.a_type == A_FLOAT)
	    {
		ntotal++;
		if (ac) s = &s_list;
		else s = &s_float;
	    }
	    else if (x->x_repl.a_type == A_SYMBOL)
	    {
		s = x->x_repl.a_w.w_symbol;
		matchndx = 0;
	    }
	}
	else if (matchndx == 0
		 && (!s || s == &s_list || s == &s_float)
		 && av->a_type == A_FLOAT
		 && x->x_repl.a_type == A_SYMBOL)
	{
	    s = x->x_repl.a_w.w_symbol;
	    ac--;
	    av++;
	    ntotal = ac;
	}
	if (prealloc && ac > x->x_size)
	{
	    if (ntotal > SUBSTITUTE_MAXSIZE)
		prealloc = 0;
	    else
		x->x_message = grow_nodata(&ntotal, &x->x_size, x->x_message,
					   SUBSTITUTE_INISIZE, x->x_messini,
					   sizeof(*x->x_message));
	}
	if (prealloc) buf = x->x_message;
	else
	    /* LATER consider using the stack if ntotal <= MAXSTACK */
	    buf = getbytes(ntotal * sizeof(*buf));
	if (buf)
	{
	    int ncopy = ntotal;
	    t_atom *bp = buf;
	    if (matchndx == -1)
	    {
		SETFLOAT(bp++, x->x_repl.a_w.w_float);
		ncopy--;
	    }
	    if (ncopy)
		memcpy(bp, av, ncopy * sizeof(*buf));
	    substitute_doit(x, s, ntotal, buf, matchndx);
	    if (buf != x->x_message)
		freebytes(buf, ntotal * sizeof(*buf));
	}
	if (!reentered)
	{
	    x->x_entered = 0;
	    if (x->x_auxmatch.a_type != A_NULL)
	    {
		x->x_match = x->x_auxmatch;
		x->x_auxmatch.a_type = A_NULL;
	    }
	    if (x->x_auxrepl.a_type != A_NULL)
	    {
		x->x_repl = x->x_auxrepl;
		x->x_auxrepl.a_type = A_NULL;
	    }
	    proxy->p_match = &x->x_match;
	    proxy->p_repl = &x->x_repl;
	}
    }
}

static void substitute_bang(t_substitute *x)
{
    substitute_anything(x, &s_bang, 0, 0);
}

static void substitute_float(t_substitute *x, t_float f)
{
    t_atom at;
    SETFLOAT(&at, f);
    substitute_anything(x, 0, 1, &at);
}

/* CHECKED (but LATER rethink) */
static void substitute_symbol(t_substitute *x, t_symbol *s)
{
    t_atom at;
    SETSYMBOL(&at, s);
    substitute_anything(x, &s_symbol, 1, &at);
}

/* LATER gpointer */

static void substitute_list(t_substitute *x, t_symbol *s, int ac, t_atom *av)
{
    substitute_anything(x, 0, ac, av);
}

static int substitute_atomvalidate(t_atom *ap)
{
    return (ap->a_type == A_FLOAT
	    || (ap->a_type == A_SYMBOL
		&& ap->a_w.w_symbol && ap->a_w.w_symbol != &s_));
}

/* CHECKED: 'set' is ignored, single '<atom>' does not modify a replacement */
static void substitute_proxy_anything(t_substitute_proxy *x,
				      t_symbol *s, int ac, t_atom *av)
{
    if (s == &s_) s = 0;
    if (s)
    {
	SETSYMBOL(x->p_match, s);
	if (ac && substitute_atomvalidate(av))
	    *x->p_repl = *av;
    }
    else if (ac && substitute_atomvalidate(av))
    {
	*x->p_match = *av++;
	if (ac > 1 && substitute_atomvalidate(av))
	    *x->p_repl = *av;
    }
}

static void substitute_proxy_bang(t_substitute_proxy *x)
{
    SETSYMBOL(x->p_match, &s_bang);
}

static void substitute_proxy_float(t_substitute_proxy *x, t_float f)
{
    SETFLOAT(x->p_match, f);
}

/* CHECKED (but LATER rethink) */
static void substitute_proxy_symbol(t_substitute_proxy *x, t_symbol *s)
{
    SETSYMBOL(x->p_match, &s_symbol);
    SETSYMBOL(x->p_repl, s);
}

/* LATER gpointer */

static void substitute_proxy_list(t_substitute_proxy *x,
				  t_symbol *s, int ac, t_atom *av)
{
    substitute_proxy_anything(x, 0, ac, av);
}

static void substitute_free(t_substitute *x)
{
    if (x->x_proxy) pd_free(x->x_proxy);
    if (x->x_message != x->x_messini)
	freebytes(x->x_message, x->x_size * sizeof(*x->x_message));
}

static void *substitute_new(t_symbol *s, int ac, t_atom *av)
{
    t_substitute *x = 0;
    t_substitute_proxy *proxy =
	(t_substitute_proxy *)pd_new(substitute_proxy_class);
    if (proxy)
    {
	x = (t_substitute *)pd_new(substitute_class);
	proxy->p_match = &x->x_match;
	proxy->p_repl = &x->x_repl;
	x->x_proxy = (t_pd *)proxy;
	x->x_size = SUBSTITUTE_INISIZE;
	x->x_message = x->x_messini;
	x->x_entered = 0;
	/* CHECKED: everything is to be passed unchanged, until both are set */
	/* CHECKED: max crashes if a match has been set, but not a replacement,
	   and there is a match */
	x->x_match.a_type = x->x_repl.a_type = A_NULL;
	x->x_auxmatch.a_type = x->x_auxrepl.a_type = A_NULL;
	inlet_new((t_object *)x, (t_pd *)proxy, 0, 0);
	outlet_new((t_object *)x, &s_anything);
	/* CHECKED (refman error: 'a bang is sent') */
	x->x_passout = outlet_new((t_object *)x, &s_anything);
	substitute_proxy_anything(proxy, 0, ac, av);
    }
    return (x);
}

void substitute_setup(void)
{
    substitute_class = class_new(gensym("substitute"),
			      (t_newmethod)substitute_new,
			      (t_method)substitute_free,
			      sizeof(t_substitute), 0,
			      A_GIMME, 0);
    class_addbang(substitute_class, substitute_bang);
    class_addfloat(substitute_class, substitute_float);
    class_addsymbol(substitute_class, substitute_symbol);
    class_addlist(substitute_class, substitute_list);
    class_addanything(substitute_class, substitute_anything);
    substitute_proxy_class = class_new(gensym("_substitute_proxy"), 0, 0,
				       sizeof(t_substitute_proxy),
				       CLASS_PD | CLASS_NOINLET, 0);
    class_addbang(substitute_proxy_class, substitute_proxy_bang);
    class_addfloat(substitute_proxy_class, substitute_proxy_float);
    class_addsymbol(substitute_proxy_class, substitute_proxy_symbol);
    class_addlist(substitute_proxy_class, substitute_proxy_list);
    class_addanything(substitute_proxy_class, substitute_proxy_anything);
    class_addmethod(substitute_proxy_class, (t_method)substitute_proxy_list,
		    gensym("set"), A_GIMME, 0);
}
