/* 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 "sickle/sic.h"

/* CHECKME for a fixed minimum deltime, if any (5ms for c74's metro) */

typedef struct _snapshot
{
    t_sic     x_sic;
    t_float   x_value;
    int       x_rqoffset;  /* requested */
    int       x_offset;    /* effective (truncated) */
    int       x_stopped;
    int       x_on;        /* !stopped && deltime > 0 */
    float     x_deltime;
    int       x_npoints;
    int       x_nleft;
    int       x_nblock;
    float     x_ksr;
    t_clock  *x_clock;
} t_snapshot;

static t_class *snapshot_class;

static void snapshot_tick(t_snapshot *x)
{
    outlet_float(((t_object *)x)->ob_outlet, x->x_value);
}

static void snapshot_bang(t_snapshot *x)
{
    outlet_float(((t_object *)x)->ob_outlet, x->x_value);
}

static void snapshot_correct(t_snapshot *x)
{
    int wason = x->x_on;
    x->x_offset =
	(x->x_rqoffset < x->x_nblock ? x->x_rqoffset : x->x_nblock - 1);
    x->x_npoints = x->x_deltime * x->x_ksr - x->x_nblock + x->x_offset;
    if (x->x_on = (!x->x_stopped && x->x_deltime > 0.))
    {
	if (!wason) x->x_nleft = x->x_offset;  /* CHECKME */
    }
    else if (wason) clock_unset(x->x_clock);
}

static void snapshot_start(t_snapshot *x)
{
    x->x_stopped = 0;
    if (!x->x_on && x->x_deltime > 0.)  /* CHECKED no default */
    {
	x->x_nleft = x->x_offset;  /* CHECKME */
	x->x_on = 1;
    }
}

static void snapshot_stop(t_snapshot *x)
{
    x->x_stopped = 1;
    if (x->x_on)
    {
	clock_unset(x->x_clock);
	x->x_on = 0;
    }
}

static void snapshot_float(t_snapshot *x, t_float f)
{
    /* CHECKED nonzero/zero, CHECKED incompatible: int only (float ignored) */
    if (f != 0.)
	snapshot_start(x);
    else
	snapshot_stop(x);
}

static void snapshot_ft1(t_snapshot *x, t_floatarg f)
{
    x->x_deltime = (f > 0. ? f : 0.);  /* CHECKED */
    /* CHECKED setting deltime to a positive value starts the clock
       only if it was stopped by setting deltime to zero */
    snapshot_correct(x);
}

static void snapshot_offset(t_snapshot *x, t_floatarg f)
{
    int i = (int)f;  /* CHECKME */
    x->x_rqoffset = (i >= 0 ? i : 0);  /* CHECKME */
    /* CHECKME if the change has an effect prior to next dsp_add call */
    snapshot_correct(x);
}

static t_int *snapshot_perform(t_int *w)
{
    t_snapshot *x = (t_snapshot *)(w[1]);
    t_float *in = (t_float *)(w[2]);
    x->x_value = in[x->x_offset];
    if (x->x_on)
    {
	/* CHECKME nleft vs offset */
	if (x->x_nleft < x->x_nblock)
	{
	    clock_delay(x->x_clock, 0);
	    x->x_nleft = x->x_npoints;
	}
	else x->x_nleft -= x->x_nblock;
    }
    return (w + 3);
}

static void snapshot_dsp(t_snapshot *x, t_signal **sp)
{
    x->x_nblock = sp[0]->s_n;
    x->x_ksr = sp[0]->s_sr * 0.001;
    snapshot_correct(x);
    x->x_nleft = x->x_offset;  /* CHECKME */
    dsp_add(snapshot_perform, 2, x, sp[0]->s_vec);
}

static void snapshot_free(t_snapshot *x)
{
    if (x->x_clock) clock_free(x->x_clock);
}

static void *snapshot_new(t_floatarg f1, t_floatarg f2)
{
    t_snapshot *x = (t_snapshot *)pd_new(snapshot_class);
    x->x_stopped = 0;  /* CHECKED */
    x->x_on = 0;
    x->x_value = 0;
    x->x_nblock = 64;  /* redundant */
    x->x_ksr = 44.1;  /* redundant */
    inlet_new((t_object *)x, (t_pd *)x, &s_float, gensym("ft1"));
    outlet_new((t_object *)x, &s_float);
    x->x_clock = clock_new(x, (t_method)snapshot_tick);
    snapshot_offset(x, f2);  /* CHECKME (this is fixed at nblock-1 in Pd) */
    snapshot_ft1(x, f1);
    return (x);
}

void Snapshot_tilde_setup(void)
{
    snapshot_class = class_new(gensym("Snapshot~"),
			       (t_newmethod)snapshot_new,
			       (t_method)snapshot_free,
			       sizeof(t_snapshot), 0,
			       A_DEFFLOAT, A_DEFFLOAT, 0);
    class_addcreator((t_newmethod)snapshot_new, gensym("cyclone/snapshot~"), A_DEFFLOAT, A_DEFFLOAT, 0);
    sic_setup(snapshot_class, snapshot_dsp, snapshot_float);
    class_addbang(snapshot_class, snapshot_bang);
    class_addmethod(snapshot_class, (t_method)snapshot_ft1,
		    gensym("ft1"), A_FLOAT, 0);
    class_addmethod(snapshot_class, (t_method)snapshot_offset,
		    gensym("offset"), A_FLOAT, 0);
    class_addmethod(snapshot_class, (t_method)snapshot_start,
		    gensym("start"), 0);
    class_addmethod(snapshot_class, (t_method)snapshot_stop,
		    gensym("stop"), 0);
}

void snapshot_tilde_setup(void)
{
    Snapshot_tilde_setup();
}
