/* 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 <stdio.h>
#include "m_pd.h"
#include "common/loud.h"
#include "common/fitter.h"
#include "common/rand.h"
#include "hammer/file.h"

/* CHECKED: no preallocation.  It looks like if new state-entries
   were added to the list's head, and new transition-entries
   were added to the sublist's head.  No sorting of any kind. */

#ifdef KRZYSZCZ
//#define PROB_DEBUG
#endif

typedef struct _probtrans
{
    int  tr_value;  /* state (if a header trans), or suffix value (otherwise) */
    int  tr_count;  /* (a total in case of a header trans) */
    struct _probtrans  *tr_suffix;     /* header trans of a suffix state */
    struct _probtrans  *tr_nexttrans;  /* next trans of this state */
    struct _probtrans  *tr_nextstate;  /* header trans of a next state */
} t_probtrans;

typedef struct _prob
{
    t_object       x_ob;
    t_probtrans   *x_translist;
    t_probtrans   *x_state;
    t_probtrans   *x_default;
    int            x_embedmode;
    int            x_silent;
    unsigned int   x_seed;
    t_outlet      *x_bangout;
    t_hammerfile  *x_filehandle;
} t_prob;

static t_class *prob_class;

static t_probtrans *prob_findstate(t_prob *x, int value, int complain)
{
    t_probtrans *state;
    for (state = x->x_translist; state; state = state->tr_nextstate)
	if (state->tr_value == value)
	    break;
    if (!state && complain && !x->x_silent)
	loud_error((t_pd *)x, "no state %d", value);  /* CHECKED */
    return (state);
}

static void prob_reset(t_prob *x, t_floatarg f)
{
    int value = (int)f;  /* CHECKED: float converted to int */
    t_probtrans *state = prob_findstate(x, value, 1);
    if (state)  /* CHECKED */
    {
	x->x_default = state;
	/* CHECKED (sort of): */
	if (!x->x_state->tr_nexttrans)
	    x->x_state = state;
    }
}

static void prob_embedhook(t_pd *z, t_binbuf *bb, t_symbol *bindsym)
{
    t_prob *x = (t_prob *)z;
    if (x->x_embedmode)
    {
	t_probtrans *pfx, *sfx;
	for (pfx = x->x_translist; pfx; pfx = pfx->tr_nextstate)
	    for (sfx = pfx->tr_nexttrans; sfx; sfx = sfx->tr_nexttrans)
		binbuf_addv(bb, "siii;", bindsym, 
			    pfx->tr_value, sfx->tr_value, sfx->tr_count);
	binbuf_addv(bb, "ssi;", bindsym, gensym("embed"), 1);
	if (x->x_default)
	    binbuf_addv(bb, "ssi;", bindsym, gensym("reset"),
			x->x_default->tr_value);
    }
}

static void prob_embed(t_prob *x, t_floatarg f)
{
    x->x_embedmode = ((int)f != 0);
}

static void prob_clear(t_prob *x)
{
    t_probtrans *state, *nextstate;
    for (state = x->x_translist; state; state = nextstate)
    {
	t_probtrans *trans, *nexttrans;
	for (trans = state->tr_nexttrans; trans; trans = nexttrans)
	{
	    nexttrans = trans->tr_nexttrans;
	    freebytes(trans, sizeof(*trans));
	}
	nextstate = state->tr_nextstate;
	freebytes(state, sizeof(*state));
    }
    x->x_translist = 0;
    x->x_state = 0;
    x->x_default = 0;  /* CHECKED: default number is not kept */
    /* CHECKED embedmode is kept */
}

/* CHECKED */
static void prob_dump(t_prob *x)
{
    t_probtrans *state;
    post("transition probabilities:");
    for (state = x->x_translist; state; state = state->tr_nextstate)
    {
	t_probtrans *trans;
	for (trans = state->tr_nexttrans; trans; trans = trans->tr_nexttrans)
	    post(" from %3d to %3d: %d",
		 state->tr_value, trans->tr_value, trans->tr_count);
	/* CHECKED: dead-ends are reported */
	post("total weights for state %d: %d",
	     state->tr_value, state->tr_count);
    }
}

static void prob_bang(t_prob *x)
{
    if (x->x_state)  /* CHECKED: no output after clear */
    {
	int rnd = rand_int(&x->x_seed, x->x_state->tr_count);
	t_probtrans *trans = x->x_state->tr_nexttrans;
	if (trans)
	{
	    for (trans = x->x_state->tr_nexttrans; trans;
		 trans = trans->tr_nexttrans)
		if ((rnd -= trans->tr_count) < 0)
		    break;
	    if (trans)
	    {
		t_probtrans *nextstate = trans->tr_suffix;
		if (nextstate)
		{
		    outlet_float(((t_object *)x)->ob_outlet,
				 nextstate->tr_value);
		    x->x_state = nextstate;
		}
		else loudbug_bug("prob_bang: void suffix");
	    }
	    else loudbug_bug("prob_bang: search overflow");
	}
	else
	{
	    outlet_bang(x->x_bangout);
	    if (x->x_default)  /* CHECKED: stays at dead-end if no default */
		x->x_state = x->x_default;
	}
    }
}

static void prob_float(t_prob *x, t_float f)
{
    int value;
    if (loud_checkint((t_pd *)x, f, &value, &s_float))  /* CHECKED */
    {
	t_probtrans *state = prob_findstate(x, value, 1);
	if (state)  /* CHECKED */
	    x->x_state = state;
    }
}

static void prob_list(t_prob *x, t_symbol *s, int ac, t_atom *av)
{
    int prefval, suffval, count;
    if (ac == 3 && av->a_type == A_FLOAT
	&& av[1].a_type == A_FLOAT && av[2].a_type == A_FLOAT
	&& (prefval = (int)av->a_w.w_float) == av->a_w.w_float
	&& (suffval = (int)av[1].a_w.w_float) == av[1].a_w.w_float
	&& (count = (int)av[2].a_w.w_float) == av[2].a_w.w_float)
    {
	t_probtrans *prefix = prob_findstate(x, prefval, 0);
	t_probtrans *suffix = prob_findstate(x, suffval, 0);
	t_probtrans *trans;
	if (prefix && suffix)
	{
	    for (trans = prefix->tr_nexttrans; trans;
		 trans = trans->tr_nexttrans)
		if (trans->tr_suffix == suffix)
		    break;
	    if (trans)
	    {
		/* the transition already exists... */
		prefix->tr_count += (count - trans->tr_count);
		trans->tr_count = count;
		return;
	    }
	}
	if (!prefix)
	{
	    if (!(prefix = getbytes(sizeof(*prefix))))
		return;
	    prefix->tr_value = prefval;
	    prefix->tr_count = 0;
	    prefix->tr_suffix = 0;
	    prefix->tr_nexttrans = 0;
	    prefix->tr_nextstate = x->x_translist;
	    x->x_translist = prefix;
	    if (suffval == prefval)
		suffix = prefix;
	}
	if (!suffix)
	{
	    if (!(suffix = getbytes(sizeof(*suffix))))
		return;
	    suffix->tr_value = suffval;
	    suffix->tr_count = 0;
	    suffix->tr_suffix = 0;
	    suffix->tr_nexttrans = 0;
	    suffix->tr_nextstate = x->x_translist;
	    x->x_translist = suffix;
	}
	if (trans = getbytes(sizeof(*trans)))
	{
	    trans->tr_value = suffval;
	    trans->tr_count = count;
	    trans->tr_suffix = suffix;
	    trans->tr_nexttrans = prefix->tr_nexttrans;
	    trans->tr_nextstate = prefix->tr_nextstate;
	    prefix->tr_count += count;
	    prefix->tr_nexttrans = trans;
	}
	if (!x->x_state)  /* CHECKED */
	    x->x_state = prefix;  /* CHECKED */
    }
    else loud_error((t_pd *)x, "bad list message format");  /* CHECKED */
}

static void prob__silent(t_prob *x)
{
    if (!x->x_silent)
    {
	fittermax_warning(prob_class, "no '_silent' message in max");
	x->x_silent = 1;
    }
}

/* CHECKED not available, LATER full editing */
static void prob_click(t_prob *x, t_floatarg xpos, t_floatarg ypos,
		       t_floatarg shift, t_floatarg ctrl, t_floatarg alt)
{
    t_probtrans *state;
    char buf[64];
    hammereditor_open(x->x_filehandle, 0, 0);
    for (state = x->x_translist; state; state = state->tr_nextstate)
    {
	t_probtrans *trans;
	for (trans = state->tr_nexttrans; trans; trans = trans->tr_nexttrans)
	{
	    sprintf(buf, "%d %d %d\n",
		    state->tr_value, trans->tr_value, trans->tr_count);
	    hammereditor_append(x->x_filehandle, buf);
	}
    }
}

static void prob_free(t_prob *x)
{
    prob_clear(x);
    hammerfile_free(x->x_filehandle);
}

static void *prob_new(void)
{
    t_prob *x = (t_prob *)pd_new(prob_class);
    x->x_translist = 0;
    x->x_state = 0;
    x->x_default = 0;
    x->x_embedmode = 0;  /* CHECKED */
    x->x_silent = 0;
    rand_seed(&x->x_seed, 0);
    outlet_new((t_object *)x, &s_float);
    x->x_bangout = outlet_new((t_object *)x, &s_bang);
    x->x_filehandle = hammerfile_new((t_pd *)x, prob_embedhook, 0, 0, 0);
    return (x);
}

void prob_setup(void)
{
    prob_class = class_new(gensym("prob"),
			   (t_newmethod)prob_new,
			   (t_method)prob_free,
			   sizeof(t_prob), 0, 0);
    class_addbang(prob_class, prob_bang);
    class_addfloat(prob_class, prob_float);
    class_addlist(prob_class, prob_list);
    class_addmethod(prob_class, (t_method)prob_embed,
		    gensym("embed"), A_FLOAT, 0);
    class_addmethod(prob_class, (t_method)prob_reset,
		    gensym("reset"), A_FLOAT, 0);
    class_addmethod(prob_class, (t_method)prob_clear,
		    gensym("clear"), 0);
    class_addmethod(prob_class, (t_method)prob_dump,
		    gensym("dump"), 0);
    /* CHECKED: doesn't understand "seed" */
    class_addmethod(prob_class, (t_method)prob__silent,
		    gensym("_silent"), 0);
    class_addmethod(prob_class, (t_method)prob_click,
		    gensym("click"),
		    A_FLOAT, A_FLOAT, A_FLOAT, A_FLOAT, A_FLOAT, 0);
    hammerfile_setup(prob_class, 1);
    fitter_setup(prob_class, 0);
}