/* 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 <string.h>
#include "m_pd.h"
#include "common/grow.h"

#define CLIP_INISIZE   32  /* LATER rethink */
#define CLIP_MAXSIZE  256

typedef struct _clip
{
    t_object   x_ob;
    float      x_f1;
    float      x_f2;
    int        x_size;  /* as allocated */
    t_atom    *x_message;
    t_atom     x_messini[CLIP_INISIZE];
    int        x_entered;
} t_clip;

static t_class *clip_class;

/* CHECKED case of f1 > f2:  x <= f2 => f1, x > f2 => f2
   (Pd implementation of clip has it the other way around) */
static void clip_float(t_clip *x, t_float f)
{
    outlet_float(((t_object *)x)->ob_outlet,
		 (f > x->x_f2 ? x->x_f2 : (f < x->x_f1 ? x->x_f1 : f)));
}

static void clip_list(t_clip *x, t_symbol *s, int ac, t_atom *av)
{
    if (ac)
    {
	int docopy = 0;
	int i;
	t_atom *ap;
	t_float f1 = x->x_f1;
	t_float f2 = x->x_f2;
	for (i = 0, ap = av; i < ac; i++, ap++)
	{
	    t_float f;
	    if (ap->a_type == A_FLOAT)
		f = ap->a_w.w_float;
	    else
	    {
		docopy = 1;
		/* CHECKED: symbols inside lists are converted to zeros */
		f = 0;
	    }
	    if (f < f1 || f > f2) docopy = 1;
	}
	if (docopy)
	{
	    t_atom *buf;
	    t_atom *bp;
	    int reentered = x->x_entered;
	    int prealloc = !reentered;
	    x->x_entered = 1;
	    if (prealloc && ac > x->x_size)
	    {
		if (ac > CLIP_MAXSIZE)
		    prealloc = 0;
		else
		    x->x_message = grow_nodata(&ac, &x->x_size, x->x_message,
					       CLIP_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(ac * sizeof(*buf));
	    if (buf)
	    {
		for (i = 0, ap = av, bp = buf; i < ac; i++, ap++, bp++)
		{
		    t_float f = (ap->a_type == A_FLOAT ? ap->a_w.w_float : 0);
		    if (f < f1)
			f = f1;
		    else if (f > f2)
			f = f2;
		    SETFLOAT(bp, f);
		}
		outlet_list(((t_object *)x)->ob_outlet, &s_list, ac, buf);
		if (buf != x->x_message)
		    freebytes(buf, ac * sizeof(*buf));
	    }
	    if (!reentered)
	    {
		x->x_entered = 0;
	    }
	}
	else outlet_list(((t_object *)x)->ob_outlet, &s_list, ac, av);
    }
}

static void clip_set(t_clip *x, t_symbol *s, int ac, t_atom *av)
{
    x->x_f1 = 0;
    x->x_f2 = 0;
    if (ac)  /* CHECKED: 'set' without arguments sets both values to 0 */
    {
	if (av->a_type == A_FLOAT)  /* CHECKED: symbol is converted to 0 */
	    x->x_f1 = av->a_w.w_float;
	av++;
	if (--ac)
	{
	    if (av->a_type == A_FLOAT)
		x->x_f2 = av->a_w.w_float;
	}
	else x->x_f2 = x->x_f1;  /* CHECKED */
    }
}

static void clip_free(t_clip *x)
{
    if (x->x_message != x->x_messini)
	freebytes(x->x_message, x->x_size * sizeof(*x->x_message));
}

static void *clip_new(t_symbol *s, int ac, t_atom *av)
{
    t_clip *x = (t_clip *)pd_new(clip_class);
    x->x_f1 = 0;
    x->x_f2 = 0;
    x->x_size = CLIP_INISIZE;
    x->x_message = x->x_messini;
    x->x_entered = 0;
    floatinlet_new((t_object *)x, &x->x_f1);
    floatinlet_new((t_object *)x, &x->x_f2);
    outlet_new(&x->x_ob, &s_anything);
    clip_set(x, 0, ac, av);
    return (x);
}

void Clip_setup(void)
{
    clip_class = class_new(gensym("Clip"),
			   (t_newmethod)clip_new,
			   (t_method)clip_free,
			   sizeof(t_clip), 0,
			   A_GIMME, 0);
    class_addcreator((t_newmethod)clip_new, gensym("clip"), A_GIMME, 0);
    class_addcreator((t_newmethod)clip_new, gensym("cyclone/clip"), A_GIMME, 0);
    class_addfloat(clip_class, clip_float);
    class_addlist(clip_class, clip_list);
    class_addmethod(clip_class, (t_method)clip_set,
		    gensym("set"), A_GIMME, 0);
}

void clip_setup(void)
{
    Clip_setup();
}
