/* Copyright (c) 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 whatever args, there are always 3 inlets (refman's rubbish) */

#include "m_pd.h"
#include "common/loud.h"
#include "unstable/forky.h"
#include "sickle/sic.h"

#ifdef KRZYSZCZ
//#define PONG_DEBUG
#endif

#define PONG_DEFLO  0.
#define PONG_DEFHI  1.

typedef struct _pong
{
    t_sic     x_sic;
    t_glist  *x_glist;
    int       x_mode;
} t_pong;

static t_class *pong_class;

static t_int *pong_perform(t_int *w)
{
    t_pong *x = (t_pong *)(w[1]);
    int nblock = (int)(w[2]);
    t_float *in1 = (t_float *)(w[3]);
    t_float *in2 = (t_float *)(w[4]);
    t_float *in3 = (t_float *)(w[5]);
    t_float *out = (t_float *)(w[6]);
    if (x->x_mode) while (nblock--)
    {
	float f = *in1++;
	float lo = *in2++;
	float hi = *in3++;
	float range;
	if (lo < hi) range = hi - lo;
	else if (lo > hi)
	{
	    range = lo;
	    lo = hi;
	    hi = range;
	    range -= lo;
	}
	else
	{
	    *out++ = lo;
	    continue;
	}
	if (f < lo)
	{
	    if (f < lo - range)
	    {
		double dnorm = (f - lo) / range;
		dnorm -= (int)dnorm;
		*out++ = hi + dnorm * range;
	    }
	    else *out++ = f + range;
	}
	else if (f > hi)
	{
	    if (f > hi + range)
	    {
		double dnorm = (f - lo) / range;
		dnorm -= (int)dnorm;
		*out++ = lo + dnorm * range;
	    }
	    else *out++ = f - range;
	}
	else *out++ = f;
    }
    else while (nblock--)
    {
	float f = *in1++;
	float lo = *in2++;
	float hi = *in3++;
	float range;
	if (lo < hi) range = hi - lo;
	else if (lo > hi)
	{
	    range = lo;
	    lo = hi;
	    hi = range;
	    range -= lo;
	}
	else
	{
	    *out++ = lo;
	    continue;
	}
	if (f < lo)
	{
	    f = lo - f;
	    if (f <= range)
	    {
		*out++ = lo + f;
		continue;
	    }
	}
	else if (f > hi) f -= lo;
	else
	{
	    *out++ = f;
	    continue;
	}
	if (f > range + range)
	{
	    double dnorm = f / range;
	    int inorm = (int)dnorm;
	    if (inorm % 2)
		*out++ = lo + ((inorm + 1) - dnorm) * range;
	    else
		*out++ = lo + (dnorm - inorm) * range;
	}
	else *out++ = hi + range - f;
    }
    return (w + 7);
}

static t_int *pong_perform_nofeeders(t_int *w)
{
    t_pong *x = (t_pong *)(w[1]);
    int nblock = (int)(w[2]);
    t_float *in1 = (t_float *)(w[3]);
    t_float *in2 = (t_float *)(w[4]);
    t_float *in3 = (t_float *)(w[5]);
    t_float *out = (t_float *)(w[6]);
    float lo = *in2;
    float hi = *in3;
    float range;
    double coef;
    if (lo < hi) range = hi - lo;
    else if (lo > hi)
    {
	range = lo;
	lo = hi;
	hi = range;
	range -= lo;
    }
    else
    {
	while (nblock--) *out++ = lo;
	goto done;
    }
    coef = 1. / range;
    if (x->x_mode) while (nblock--)
    {
	float f = *in1++;
	if (f < lo)
	{
	    if (f < lo - range)
	    {
		double dnorm = (f - lo) * coef;
		dnorm -= (int)dnorm;
		*out++ = hi + dnorm * range;
	    }
	    else *out++ = f + range;
	}
	else if (f > hi)
	{
	    if (f > hi + range)
	    {
		double dnorm = (f - lo) * coef;
		dnorm -= (int)dnorm;
		*out++ = lo + dnorm * range;
	    }
	    else *out++ = f - range;
	}
	else *out++ = f;
    }
    else while (nblock--)
    {
	float f = *in1++;
	if (f < lo)
	{
	    f = lo - f;
	    if (f <= range)
	    {
		*out++ = lo + f;
		continue;
	    }
	}
	else if (f > hi) f -= lo;
	else
	{
	    *out++ = f;
	    continue;
	}
	if (f > range + range)
	{
	    double dnorm = f * coef;
	    int inorm = (int)dnorm;
	    if (inorm % 2)
		*out++ = lo + ((inorm + 1) - dnorm) * range;
	    else
		*out++ = lo + (dnorm - inorm) * range;
	}
	else *out++ = hi + range - f;
    }
done:
    return (w + 7);
}

static void pong_dsp(t_pong *x, t_signal **sp)
{
    if (forky_hasfeeders((t_object *)x, x->x_glist, 1, &s_signal) ||
	forky_hasfeeders((t_object *)x, x->x_glist, 2, &s_signal))
	dsp_add(pong_perform, 6, x, sp[0]->s_n,
		sp[0]->s_vec, sp[1]->s_vec, sp[2]->s_vec, sp[3]->s_vec);
    else
    {
#ifdef PONG_DEBUG
	loudbug_post("using pong_perform_nofeeders");
#endif
	dsp_add(pong_perform_nofeeders, 6, x, sp[0]->s_n,
		sp[0]->s_vec, sp[1]->s_vec, sp[2]->s_vec, sp[3]->s_vec);
    }
}

static void pong_mode(t_pong *x, t_floatarg f)
{
    x->x_mode = ((int)f != 0);
}

static void *pong_new(t_symbol *s, int ac, t_atom *av)
{
    t_pong *x = (t_pong *)pd_new(pong_class);
    x->x_glist = canvas_getcurrent();
    x->x_mode = (ac && av->a_type == A_FLOAT
		 && (int)av->a_w.w_float != 0);
    sic_inlet((t_sic *)x, 1, PONG_DEFLO, 1, ac, av);
    sic_inlet((t_sic *)x, 2, PONG_DEFHI, 2, ac, av);
    outlet_new((t_object *)x, &s_signal);
    return (x);
}

void pong_tilde_setup(void)
{
    pong_class = class_new(gensym("pong~"),
			   (t_newmethod)pong_new, 0,
			   sizeof(t_pong), 0, A_GIMME, 0);
    sic_setup(pong_class, pong_dsp, SIC_FLOATTOSIGNAL);
    class_addmethod(pong_class, (t_method)pong_mode,
		    gensym("mode"), A_DEFFLOAT, 0);  /* CHECKED default */
}