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

/* LATER profile for the bottlenecks of insertion and sorting */
/* LATER make sure that ``reentrancy protection hack'' is really working... */

#ifdef KRZYSZCZ
//#define COLL_DEBUG
#endif

enum { COLL_HEADRESET,
       COLL_HEADNEXT, COLL_HEADPREV,  /* distinction not used, currently */
       COLL_HEADDELETED };

typedef struct _collelem
{
    int                e_hasnumkey;
    int                e_numkey;
    t_symbol          *e_symkey;
    struct _collelem  *e_prev;
    struct _collelem  *e_next;
    int                e_size;
    t_atom            *e_data;
} t_collelem;

typedef struct _collcommon
{
    t_pd           c_pd;
    struct _coll  *c_refs;  /* used in read-banging and dirty flag handling */
    int            c_increation;
    int            c_volatile;
    int            c_selfmodified;
    int            c_entered;    /* a counter, LATER rethink */
    int            c_embedflag;  /* common field (CHECKED in 'TEXT' files) */
    t_symbol      *c_filename;   /* CHECKED common for all, read and write */
    t_canvas      *c_lastcanvas;
    t_hammerfile  *c_filehandle;
    t_collelem    *c_first;
    t_collelem    *c_last;
    t_collelem    *c_head;
    int            c_headstate;
} t_collcommon;

typedef struct _coll
{
    t_object       x_ob;
    t_canvas      *x_canvas;
    t_symbol      *x_name;
    t_collcommon  *x_common;
    t_hammerfile  *x_filehandle;
    t_outlet      *x_keyout;
    t_outlet      *x_filebangout;
    t_outlet      *x_dumpbangout;
    struct _coll  *x_next;
} t_coll;

static t_class *coll_class;
static t_class *collcommon_class;

static t_collelem *collelem_new(int ac, t_atom *av, int *np, t_symbol *s)
{
    t_collelem *ep = (t_collelem *)getbytes(sizeof(*ep));
    if (ep->e_hasnumkey = (np != 0))
	ep->e_numkey = *np;
    ep->e_symkey = s;
    ep->e_prev = ep->e_next = 0;
    if (ep->e_size = ac)
    {
	t_atom *ap = getbytes(ac * sizeof(*ap));
	ep->e_data = ap;
	if (av) while (ac--)
	    *ap++ = *av++;
	else while (ac--)
	{
	    SETFLOAT(ap, 0);
	    ap++;
	}
    }
    else ep->e_data = 0;
    return (ep);
}

static void collelem_free(t_collelem *ep)
{
    if (ep->e_data)
	freebytes(ep->e_data, ep->e_size * sizeof(*ep->e_data));
    freebytes(ep, sizeof(*ep));
}

/* CHECKME again... apparently c74 is not able to fix this for good */
/* result: 1 for ep1 < ep2, 0 for ep1 >= ep2, all symbols are < any float */
static int collelem_less(t_collelem *ep1, t_collelem *ep2, int ndx, int swap)
{
    int isless;
    if (swap)
    {
	t_collelem *ep = ep1;
	ep1 = ep2;
	ep2 = ep;
    }
    if (ndx < 0)
    {
	if (ep1->e_symkey)
	    isless =
		(ep2->e_symkey ? strcmp(ep1->e_symkey->s_name,
					ep2->e_symkey->s_name) < 0
		 : 1);  /* CHECKED incompatible with 4.07, but consistent */
	else if (ep2->e_symkey)
	    isless = 0;  /* CHECKED incompatible with 4.07, but consistent */
	else
	    isless = (ep1->e_numkey < ep2->e_numkey);  /* CHECKED in 4.07 */
    }
    else
    {
	t_atom *ap1 = (ndx < ep1->e_size ?
		       ep1->e_data + ndx : ep1->e_data + ep1->e_size - 1);
	t_atom *ap2 = (ndx < ep2->e_size ?
		       ep2->e_data + ndx : ep2->e_data + ep2->e_size - 1);
	if (ap1->a_type == A_FLOAT)
	{
 	    if (ap2->a_type == A_FLOAT)
		isless = (ap1->a_w.w_float < ap2->a_w.w_float);
 	    else if (ap2->a_type == A_SYMBOL)
		isless = 0;
	    else
		isless = 1;
	}
	else if (ap1->a_type == A_SYMBOL)
	{
	    if (ap2->a_type == A_FLOAT)
		isless = 1;
	    else if (ap2->a_type == A_SYMBOL)
		isless = (strcmp(ap1->a_w.w_symbol->s_name,
				 ap2->a_w.w_symbol->s_name) < 0);
	    else
		isless = 1;
	}
	else isless = 0;
    }
    return (isless);
}

static t_collelem *collcommon_numkey(t_collcommon *cc, int numkey)
{
    t_collelem *ep;
    for (ep = cc->c_first; ep; ep = ep->e_next)
	if (ep->e_hasnumkey && ep->e_numkey == numkey)
	    return (ep);
    return (0);
}

static t_collelem *collcommon_symkey(t_collcommon *cc, t_symbol *symkey)
{
    t_collelem *ep;
    for (ep = cc->c_first; ep; ep = ep->e_next)
	if (ep->e_symkey == symkey)
	    return (ep);
    return (0);
}

static void collcommon_takeout(t_collcommon *cc, t_collelem *ep)
{
    if (ep->e_prev)
	ep->e_prev->e_next = ep->e_next;
    else
	cc->c_first = ep->e_next;
    if (ep->e_next)
	ep->e_next->e_prev = ep->e_prev;
    else
	cc->c_last = ep->e_prev;
    if (cc->c_head == ep)
    {
	cc->c_head = ep->e_next;  /* asymmetric, LATER rethink */
	cc->c_headstate = COLL_HEADDELETED;
    }

}

static void collcommon_modified(t_collcommon *cc, int relinked)
{
    if (cc->c_increation)
	return;
    if (relinked)
    {
	cc->c_volatile = 1;
    }
    if (cc->c_embedflag)
    {
	t_coll *x;
	for (x = cc->c_refs; x; x = x->x_next)
	    if (x->x_canvas && glist_isvisible(x->x_canvas))
		canvas_dirty(x->x_canvas, 1);
    }
}

/* atomic collcommon modifiers:  clearall, remove, replace,
   putbefore, putafter, swaplinks, swapkeys, changesymkey, renumber, sort */

static void collcommon_clearall(t_collcommon *cc)
{
    if (cc->c_first)
    {
	t_collelem *ep1 = cc->c_first, *ep2;
	do
	{
	    ep2 = ep1->e_next;
	    collelem_free(ep1);
	}
	while (ep1 = ep2);
	cc->c_first = cc->c_last = 0;
	cc->c_head = 0;
	cc->c_headstate = COLL_HEADRESET;
	collcommon_modified(cc, 1);
    }
}

static void collcommon_remove(t_collcommon *cc, t_collelem *ep)
{
    collcommon_takeout(cc, ep);
    collelem_free(ep);
    collcommon_modified(cc, 1);
}

static void collcommon_replace(t_collcommon *cc, t_collelem *ep,
			       int ac, t_atom *av, int *np, t_symbol *s)
{
    if (ep->e_hasnumkey = (np != 0))
	ep->e_numkey = *np;
    ep->e_symkey = s;
    if (ac)
    {
	int i = ac;
	t_atom *ap;
	if (ep->e_data)
	{
	    if (ep->e_size != ac)
		ap = resizebytes(ep->e_data,
				 ep->e_size * sizeof(*ap), ac * sizeof(*ap));
	    else ap = ep->e_data;
	}
	else
	    ap = getbytes(ac * sizeof(*ap));
	ep->e_data = ap;
	if (av) while (i --)
	    *ap++ = *av++;
	else while (i --)
	{
	    SETFLOAT(ap, 0);
	    ap++;
	}
    }
    else
    {
	if (ep->e_data)
	    freebytes(ep->e_data, ep->e_size * sizeof(*ep->e_data));
	ep->e_data = 0;
    }
    ep->e_size = ac;
    collcommon_modified(cc, 0);
}

static void collcommon_putbefore(t_collcommon *cc,
				 t_collelem *ep, t_collelem *next)
{
    if (next)
    {
	ep->e_next = next;
	if (ep->e_prev = next->e_prev)
	    ep->e_prev->e_next = ep;
	else
	    cc->c_first = ep;
	next->e_prev = ep;
    }
    else if (cc->c_first || cc->c_last)
	loudbug_bug("collcommon_putbefore");
    else
	cc->c_first = cc->c_last = ep;
    collcommon_modified(cc, 1);
}

static void collcommon_putafter(t_collcommon *cc,
				t_collelem *ep, t_collelem *prev)
{
    if (prev)
    {
	ep->e_prev = prev;
	if (ep->e_next = prev->e_next)
	    ep->e_next->e_prev = ep;
	else
	    cc->c_last = ep;
	prev->e_next = ep;
    }
    else if (cc->c_first || cc->c_last)
	loudbug_bug("collcommon_putafter");
    else
	cc->c_first = cc->c_last = ep;
    collcommon_modified(cc, 1);
}

/* LATER consider making it faster, if there is a real need.
   Now called only in the sort routine, once per sort. */
static void collcommon_swaplinks(t_collcommon *cc,
				 t_collelem *ep1, t_collelem *ep2)
{
    if (ep1 != ep2)
    {
	t_collelem *prev1 = ep1->e_prev, *prev2 = ep2->e_prev;
	if (prev1 == ep2)
	{
	    collcommon_takeout(cc, ep2);
	    collcommon_putafter(cc, ep2, ep1);
	}
	else if (prev2 == ep1)
	{
	    collcommon_takeout(cc, ep1);
	    collcommon_putafter(cc, ep1, ep2);
	}
	else if (prev1)
	{
	    if (prev2)
	    {
		collcommon_takeout(cc, ep1);
		collcommon_takeout(cc, ep2);
		collcommon_putafter(cc, ep1, prev2);
		collcommon_putafter(cc, ep2, prev1);
	    }
	    else
	    {
		t_collelem *next2 = ep2->e_next;
		collcommon_takeout(cc, ep1);
		collcommon_takeout(cc, ep2);
		collcommon_putbefore(cc, ep1, next2);
		collcommon_putafter(cc, ep2, prev1);
	    }
	}
	else if (prev2)
	{
	    t_collelem *next1 = ep1->e_next;
	    collcommon_takeout(cc, ep1);
	    collcommon_takeout(cc, ep2);
	    collcommon_putafter(cc, ep1, prev2);
	    collcommon_putbefore(cc, ep2, next1);
	}
	else loudbug_bug("collcommon_swaplinks");
    }
}

static void collcommon_swapkeys(t_collcommon *cc,
				t_collelem *ep1, t_collelem *ep2)
{
    int hasnumkey = ep2->e_hasnumkey, numkey = ep2->e_numkey;
    t_symbol *symkey = ep2->e_symkey;
    ep2->e_hasnumkey = ep1->e_hasnumkey;
    ep2->e_numkey = ep1->e_numkey;
    ep2->e_symkey = ep1->e_symkey;
    ep1->e_hasnumkey = hasnumkey;
    ep1->e_numkey = numkey;
    ep1->e_symkey = symkey;
    collcommon_modified(cc, 0);
}

static void collcommon_changesymkey(t_collcommon *cc,
				    t_collelem *ep, t_symbol *s)
{
    ep->e_symkey = s;
    collcommon_modified(cc, 0);
}

static void collcommon_renumber(t_collcommon *cc, int startkey)
{
    t_collelem *ep;
    for (ep = cc->c_first; ep; ep = ep->e_next)
	if (ep->e_hasnumkey)
	    ep->e_numkey = startkey++;
    collcommon_modified(cc, 0);
}

/* LATER choose a better algo, after coll's storage structures stabilize.
   Note, that even the simple insertion sort below (n-square) might prove better
   for bi-directional lists, than theoretically efficient algo (nlogn) requiring
   random access emulation.  Avoiding recursion is not a bad idea, too. */
static void collcommon_sort(t_collcommon *cc, int descending, int ndx)
{
    t_collelem *min = cc->c_first;
    t_collelem *ep;
    if (min && (ep = min->e_next))
    {
	cc->c_increation = 1;
	/* search for a sentinel element */
 	do
	    if (collelem_less(ep, min, ndx, descending))
		min = ep;
	while (ep = ep->e_next);
	/* prepend it */
	collcommon_swaplinks(cc, cc->c_first, min);
	/* sort */
 	ep = min->e_next->e_next;
	while (ep)
	{
	    t_collelem *next = ep->e_next;
	    for (min = ep->e_prev;
		 min &&  /* LATER remove */
		     collelem_less(ep, min, ndx, descending);
		 min = min->e_prev);
	    if (!min)  /* LATER remove */
		loudbug_bug("collcommon_sort");
	    else if (ep != min->e_next)
	    {
		collcommon_takeout(cc, ep);
		collcommon_putafter(cc, ep, min);
	    }
	    ep = next;
	}
	cc->c_increation = 0;
	collcommon_modified(cc, 1);
    }
}

static void collcommon_adddata(t_collcommon *cc, t_collelem *ep,
			       int ac, t_atom *av)
{
    if (ac)
    {
	t_atom *ap;
	int newsize = ep->e_size + ac;
	if (ep->e_data)
	    ap = resizebytes(ep->e_data,
			     ep->e_size * sizeof(*ap), newsize * sizeof(*ap));
	else
	{
	    ep->e_size = 0;  /* redundant, hopefully */
	    ap = getbytes(newsize * sizeof(*ap));
	}
	ep->e_data = ap;
	ap += ep->e_size;
	if (av) while (ac--)
	    *ap++ = *av++;
	else while (ac--)
	{
	    SETFLOAT(ap, 0);
	    ap++;
	}
	ep->e_size = newsize;
	collcommon_modified(cc, 0);
    }
}

static t_collelem *collcommon_tonumkey(t_collcommon *cc, int numkey,
				       int ac, t_atom *av, int replace)
{
    t_collelem *old = collcommon_numkey(cc, numkey), *new;
    if (old && replace)
	collcommon_replace(cc, new = old, ac, av, &numkey, 0);
    else
    {
	new = collelem_new(ac, av, &numkey, 0);
	if (old)
	{
	    collcommon_putbefore(cc, new, old);
	    do
		if (old->e_hasnumkey)
		    /* CHECKED incremented up to the last one; incompatible:
		       elements with numkey == 0 not incremented (a bug?) */
		    old->e_numkey++;
	    while (old = old->e_next);
	}
	else
	{
	    /* CHECKED negative numkey always put before the last element,
	       zero numkey always becomes the new head */
	    int closestkey = 0;
	    t_collelem *closest = 0, *ep;
	    for (ep = cc->c_first; ep; ep = ep->e_next)
	    {
		if (ep->e_hasnumkey)
		{
		    if (numkey >= closestkey && numkey <= ep->e_numkey)
		    {
			collcommon_putbefore(cc, new, ep);
			break;
		    }
		    closestkey = ep->e_numkey;
		}
		closest = ep;
	    }
	    if (!ep)
	    {
		if (numkey <= closestkey)
		    collcommon_putbefore(cc, new, closest);
		else
		    collcommon_putafter(cc, new, closest);
	    }
	}
    }
    return (new);
}

static t_collelem *collcommon_tosymkey(t_collcommon *cc, t_symbol *symkey,
				       int ac, t_atom *av, int replace)
{
    t_collelem *old = collcommon_symkey(cc, symkey), *new;
    if (old && replace)
	collcommon_replace(cc, new = old, ac, av, 0, symkey);
    else
	collcommon_putafter(cc, new = collelem_new(ac, av, 0, symkey),
			    cc->c_last);
    return (new);
}

static int collcommon_fromatoms(t_collcommon *cc, int ac, t_atom *av)
{
    int hasnumkey = 0, numkey;
    t_symbol *symkey = 0;
    int size = 0;
    t_atom *data = 0;
    int nlines = 0;
    cc->c_increation = 1;
    collcommon_clearall(cc);
    while (ac--)
    {
	if (data)
	{
	    if (av->a_type == A_SEMI)
	    {
		t_collelem *ep = collelem_new(size, data,
					      hasnumkey ? &numkey : 0, symkey);
		collcommon_putafter(cc, ep, cc->c_last);
		hasnumkey = 0;
		symkey = 0;
		data = 0;
		nlines++;
	    }
	    if (av->a_type == A_COMMA)
	    {
		/* CHECKED rejecting a comma */
		collcommon_clearall(cc);  /* LATER rethink */
		cc->c_increation = 0;
		return (-nlines);
	    }
	    else size++;
	}
	else if (av->a_type == A_COMMA)
	{
	    size = 0;
	    data = av + 1;
	}
	else if (av->a_type == A_SYMBOL)
	    symkey = av->a_w.w_symbol;
	else if (av->a_type == A_FLOAT &&
		 loud_checkint(0, av->a_w.w_float, &numkey, 0))
	    hasnumkey = 1;
	else
	{
	    loud_error(0, "coll: bad atom");
	    collcommon_clearall(cc);  /* LATER rethink */
	    cc->c_increation = 0;
	    return (-nlines);
	}
	av++;
    }
    if (data)
    {
	loud_error(0, "coll: incomplete");
	collcommon_clearall(cc);  /* LATER rethink */
	cc->c_increation = 0;
	return (-nlines);
    }
    cc->c_increation = 0;
    return (nlines);
}

static int collcommon_frombinbuf(t_collcommon *cc, t_binbuf *bb)
{
    return (collcommon_fromatoms(cc, binbuf_getnatom(bb), binbuf_getvec(bb)));
}

static void collcommon_doread(t_collcommon *cc, t_symbol *fn, t_canvas *cv)
{
    t_binbuf *bb;
    char buf[MAXPDSTRING];
    if (!fn && !(fn = cc->c_filename))  /* !fn: 'readagain' */
	return;
    /* FIXME use open_via_path() */
    if (cv || (cv = cc->c_lastcanvas))  /* !cv: 'read' w/o arg, 'readagain' */
	canvas_makefilename(cv, fn->s_name, buf, MAXPDSTRING);
    else
    {
    	strncpy(buf, fn->s_name, MAXPDSTRING);
    	buf[MAXPDSTRING-1] = 0;
    }
    if (!cc->c_refs)
    {
	/* loading during object creation --
	   avoid binbuf_read()'s complaints, LATER rethink */
	FILE *fp;
	if (!(fp = sys_fopen(buf, "r")))
	{
	    loud_warning(&coll_class, 0, "no coll file '%s'", buf);
	    return;
	}
	fclose(fp);
    }
    bb = binbuf_new();
    if (binbuf_read(bb, buf, "", 0))
	loud_error(0, "coll: error reading text file '%s'", fn->s_name);
    else
    {
	int nlines = collcommon_frombinbuf(cc, bb);
	if (nlines > 0)
	{
	    t_coll *x;
	    /* LATER consider making this more robust */
	    for (x = cc->c_refs; x; x = x->x_next)
		outlet_bang(x->x_filebangout);
	    cc->c_lastcanvas = cv;
	    cc->c_filename = fn;
	    post("coll: finished reading %d lines from text file '%s'",
		 nlines, fn->s_name);
	}
	else if (nlines < 0)
	    loud_error(0, "coll: error in line %d of text file '%s'",
		       1 - nlines, fn->s_name);
	else
	    loud_error(0, "coll: error reading text file '%s'", fn->s_name);
	if (cc->c_refs)
	    collcommon_modified(cc, 1);
    }
    binbuf_free(bb);
}

static void collcommon_readhook(t_pd *z, t_symbol *fn, int ac, t_atom *av)
{
    collcommon_doread((t_collcommon *)z, fn, 0);
}

static void collcommon_tobinbuf(t_collcommon *cc, t_binbuf *bb)
{
    t_collelem *ep;
    t_atom at[3];
    for (ep = cc->c_first; ep; ep = ep->e_next)
    {
	t_atom *ap = at;
	int cnt = 1;
	if (ep->e_hasnumkey)
	{
	    SETFLOAT(ap, ep->e_numkey);
	    ap++; cnt++;
	}
	if (ep->e_symkey)
	{
	    SETSYMBOL(ap, ep->e_symkey);
	    ap++; cnt++;
	}
	SETCOMMA(ap);
	binbuf_add(bb, cnt, at);
	binbuf_add(bb, ep->e_size, ep->e_data);
	binbuf_addsemi(bb);
    }
}

static void collcommon_dowrite(t_collcommon *cc, t_symbol *fn, t_canvas *cv)
{
    t_binbuf *bb;
    int ac;
    t_atom *av;
    char buf[MAXPDSTRING];
    if (!fn && !(fn = cc->c_filename))  /* !fn: 'writeagain' */
	return;
    if (cv || (cv = cc->c_lastcanvas))  /* !cv: 'write' w/o arg, 'writeagain' */
	canvas_makefilename(cv, fn->s_name, buf, MAXPDSTRING);
    else
    {
    	strncpy(buf, fn->s_name, MAXPDSTRING);
    	buf[MAXPDSTRING-1] = 0;
    }
    bb = binbuf_new();
    collcommon_tobinbuf(cc, bb);
    if (binbuf_write(bb, buf, "", 0))
	loud_error(0, "coll: error writing text file '%s'", fn->s_name);
    else
    {
	cc->c_lastcanvas = cv;
	cc->c_filename = fn;
    }
    binbuf_free(bb);
}

static void collcommon_writehook(t_pd *z, t_symbol *fn, int ac, t_atom *av)
{
    collcommon_dowrite((t_collcommon *)z, fn, 0);
}

static void coll_embedhook(t_pd *z, t_binbuf *bb, t_symbol *bindsym)
{
    t_coll *x = (t_coll *)z;
    t_collcommon *cc = x->x_common;
    if (cc->c_embedflag)
    {
	t_collelem *ep;
	t_atom at[6];
	binbuf_addv(bb, "ssii;", bindsym, gensym("flags"), 1, 0);
	SETSYMBOL(at, bindsym);
	for (ep = cc->c_first; ep; ep = ep->e_next)
	{
	    t_atom *ap = at + 1;
	    int cnt;
	    if (ep->e_hasnumkey && ep->e_symkey)
	    {
		SETSYMBOL(ap, gensym("nstore"));
		ap++;
		SETSYMBOL(ap, ep->e_symkey);
		ap++;
		SETFLOAT(ap, ep->e_numkey);
		cnt = 4;
	    }
	    else if (ep->e_symkey)
	    {
		SETSYMBOL(ap, gensym("store"));
		ap++;
		SETSYMBOL(ap, ep->e_symkey);
		cnt = 3;
	    }
	    else
	    {
		SETFLOAT(ap, ep->e_numkey);
		cnt = 2;
	    }
	    binbuf_add(bb, cnt, at);
	    binbuf_add(bb, ep->e_size, ep->e_data);
	    binbuf_addsemi(bb);
	}
    }
}

static void collcommon_editorhook(t_pd *z, t_symbol *s, int ac, t_atom *av)
{
    int nlines = collcommon_fromatoms((t_collcommon *)z, ac, av);
    if (nlines < 0)
	loud_error(0, "coll: editing error in line %d", 1 - nlines);
}

static void collcommon_free(t_collcommon *cc)
{
    t_collelem *ep1, *ep2 = cc->c_first;
    while (ep1 = ep2)
    {
	ep2 = ep1->e_next;
	collelem_free(ep1);
    }
}

static void *collcommon_new(void)
{
    t_collcommon *cc = (t_collcommon *)pd_new(collcommon_class);
    cc->c_embedflag = 0;
    cc->c_first = cc->c_last = 0;
    cc->c_head = 0;
    cc->c_headstate = COLL_HEADRESET;
    return (cc);
}

static t_collcommon *coll_checkcommon(t_coll *x)
{
    if (x->x_name &&
	x->x_common != (t_collcommon *)pd_findbyclass(x->x_name,
						      collcommon_class))
    {
	loudbug_bug("coll_checkcommon");
	return (0);
    }
    return (x->x_common);
}

static void coll_unbind(t_coll *x)
{
    /* LATER consider calling coll_checkcommon(x) */
    t_collcommon *cc = x->x_common;
    t_coll *prev, *next;
    if ((prev = cc->c_refs) == x)
    {
	if (!(cc->c_refs = x->x_next))
	{
	    hammerfile_free(cc->c_filehandle);
	    collcommon_free(cc);
	    if (x->x_name) pd_unbind(&cc->c_pd, x->x_name);
	    pd_free(&cc->c_pd);
	}
    }
    else if (prev)
    {
	while (next = prev->x_next)
	{
	    if (next == x)
	    {
		prev->x_next = next->x_next;
		break;
	    }
	    prev = next;
	}
    }
    x->x_common = 0;
    x->x_name = 0;
    x->x_next = 0;
}

static void coll_bind(t_coll *x, t_symbol *name)
{
    t_collcommon *cc = 0;
    if (name == &s_)
	name = 0;
    else if (name)
	cc = (t_collcommon *)pd_findbyclass(name, collcommon_class);
    if (!cc)
    {
	cc = (t_collcommon *)collcommon_new();
	cc->c_refs = 0;
	cc->c_increation = 0;
	if (name)
	{
	    pd_bind(&cc->c_pd, name);
	    /* LATER rethink canvas unpredictability */
	    collcommon_doread(cc, name, x->x_canvas);
	}
	else
	{
	    cc->c_filename = 0;
	    cc->c_lastcanvas = 0;
	}
	cc->c_filehandle = hammerfile_new((t_pd *)cc, 0, collcommon_readhook,
					  collcommon_writehook,
					  collcommon_editorhook);
    }
    x->x_common = cc;
    x->x_name = name;
    x->x_next = cc->c_refs;
    cc->c_refs = x;
}

static int coll_rebind(t_coll *x, t_symbol *name)
{
    t_collcommon *cc;
    if (name && name != &s_ &&
	(cc = (t_collcommon *)pd_findbyclass(name, collcommon_class)))
    {
	coll_unbind(x);
	x->x_common = cc;
	x->x_name = name;
	x->x_next = cc->c_refs;
	cc->c_refs = x;
	return (1);
    }
    else return (0);
}

static void coll_dooutput(t_coll *x, int ac, t_atom *av)
{
    if (ac > 1)
    {
	if (av->a_type == A_FLOAT)
	    outlet_list(((t_object *)x)->ob_outlet, &s_list, ac, av);
	else if (av->a_type == A_SYMBOL)
	    outlet_anything(((t_object *)x)->ob_outlet,
			    av->a_w.w_symbol, ac-1, av+1);
    }
    else if (ac)
    {
	if (av->a_type == A_FLOAT)
	    outlet_float(((t_object *)x)->ob_outlet, av->a_w.w_float);
	else if (av->a_type == A_SYMBOL)
	    outlet_symbol(((t_object *)x)->ob_outlet, av->a_w.w_symbol);
    }
}

static void coll_keyoutput(t_coll *x, t_collelem *ep)
{
    t_collcommon *cc = x->x_common;
    if (!cc->c_entered++) cc->c_selfmodified = 0;
    cc->c_volatile = 0;
    if (ep->e_hasnumkey)
	outlet_float(x->x_keyout, ep->e_numkey);
    else if (ep->e_symkey)
	outlet_symbol(x->x_keyout, ep->e_symkey);
    else
	outlet_float(x->x_keyout, 0);
    if (cc->c_volatile) cc->c_selfmodified = 1;
    cc->c_entered--;
}

static t_collelem *coll_findkey(t_coll *x, t_atom *key, t_symbol *mess)
{
    t_collcommon *cc = x->x_common;
    t_collelem *ep = 0;
    if (key->a_type == A_FLOAT)
    {
	int numkey;
	if (loud_checkint((t_pd *)x, key->a_w.w_float, &numkey, mess))
	    ep = collcommon_numkey(cc, numkey);
	else
	    mess = 0;
    }
    else if (key->a_type == A_SYMBOL)
	ep = collcommon_symkey(cc, key->a_w.w_symbol);
    else if (mess)
    {
	loud_messarg((t_pd *)x, mess);
	mess = 0;
    }
    if (!ep && mess)
	loud_error((t_pd *)x, "no such key");
    return (ep);
}

static t_collelem *coll_tokey(t_coll *x, t_atom *key, int ac, t_atom *av,
			      int replace, t_symbol *mess)
{
    t_collcommon *cc = x->x_common;
    t_collelem *ep = 0;
    if (key->a_type == A_FLOAT)
    {
	int numkey;
	if (loud_checkint((t_pd *)x, key->a_w.w_float, &numkey, mess))
	    ep = collcommon_tonumkey(cc, numkey, ac, av, replace);
    }
    else if (key->a_type == A_SYMBOL)
	ep = collcommon_tosymkey(cc, key->a_w.w_symbol, ac, av, replace);
    else if (mess)
	loud_messarg((t_pd *)x, mess);
    return (ep);
}

static t_collelem *coll_firsttyped(t_coll *x, int ndx, t_atomtype type)
{
    t_collcommon *cc = x->x_common;
    t_collelem *ep;
    for (ep = cc->c_first; ep; ep = ep->e_next)
	if (ep->e_size > ndx && ep->e_data[ndx].a_type == type)
	    return (ep);
    return (0);
}

/* the methods */

static void coll_float(t_coll *x, t_float f)
{
    t_collcommon *cc = x->x_common;
    t_collelem *ep;
    int numkey;
    if (loud_checkint((t_pd *)x, f, &numkey, &s_float) &&
	(ep = collcommon_numkey(cc, numkey)))
    {
	coll_keyoutput(x, ep);
	if (!cc->c_selfmodified || (ep = collcommon_numkey(cc, numkey)))
	    coll_dooutput(x, ep->e_size, ep->e_data);
    }
}

static void coll_symbol(t_coll *x, t_symbol *s)
{
    t_collcommon *cc = x->x_common;
    t_collelem *ep;
    if (ep = collcommon_symkey(cc, s))
    {
	coll_keyoutput(x, ep);
	if (!cc->c_selfmodified || (ep = collcommon_symkey(cc, s)))
	    coll_dooutput(x, ep->e_size, ep->e_data);
    }
}

static void coll_list(t_coll *x, t_symbol *s, int ac, t_atom *av)
{
    if (ac >= 2 && av->a_type == A_FLOAT)
	coll_tokey(x, av, ac-1, av+1, 1, &s_list);
    else
	loud_messarg((t_pd *)x, &s_list);
}

static void coll_anything(t_coll *x, t_symbol *s, int ac, t_atom *av)
{
    coll_symbol(x, s);
}

static void coll_store(t_coll *x, t_symbol *s, int ac, t_atom *av)
{
    if (ac >= 2)
	coll_tokey(x, av, ac-1, av+1, 1, s);
    else
	loud_messarg((t_pd *)x, s);
}

static void coll_nstore(t_coll *x, t_symbol *s, int ac, t_atom *av)
{
    if (ac >= 3)
    {
	t_collcommon *cc = x->x_common;
	t_collelem *ep;
	int numkey;
	if (av->a_type == A_FLOAT && av[1].a_type == A_SYMBOL)
	{
	    if (loud_checkint((t_pd *)x, av->a_w.w_float, &numkey, s))
	    {
		if (ep = collcommon_symkey(cc, av[1].a_w.w_symbol))
		    collcommon_remove(cc, ep);
		ep = collcommon_tonumkey(cc, numkey, ac-2, av+2, 1);
		ep->e_symkey = av[1].a_w.w_symbol;
	    }
	}
	else if (av->a_type == A_SYMBOL && av[1].a_type == A_FLOAT)
	{
	    if (loud_checkint((t_pd *)x, av[1].a_w.w_float, &numkey, s))
	    {
		if (ep = collcommon_numkey(cc, numkey))
		    collcommon_remove(cc, ep);
		ep = collcommon_tosymkey(cc, av->a_w.w_symbol, ac-2, av+2, 1);
		ep->e_hasnumkey = 1;
		ep->e_numkey = numkey;
	    }
	}
	else loud_messarg((t_pd *)x, s);
    }
    else loud_messarg((t_pd *)x, s);
}

static void coll_insert(t_coll *x, t_symbol *s, int ac, t_atom *av)
{
    if (ac >= 2 && av->a_type == A_FLOAT)
	coll_tokey(x, av, ac-1, av+1, 0, s);
    else
	loud_messarg((t_pd *)x, s);
}

static void coll_remove(t_coll *x, t_symbol *s, int ac, t_atom *av)
{
    if (ac)
    {
	t_collelem *ep;
	if (ep = coll_findkey(x, av, s))
	    collcommon_remove(x->x_common, ep);
    }
    else loud_messarg((t_pd *)x, s);
}

static void coll_delete(t_coll *x, t_symbol *s, int ac, t_atom *av)
{
    if (ac)
    {
	t_collelem *ep;
	if (ep = coll_findkey(x, av, s))
	{
	    if (av->a_type == A_FLOAT)
	    {
		int numkey = ep->e_numkey;
		t_collelem *next;
		for (next = ep->e_next; next; next = next->e_next)
		    if (next->e_hasnumkey && next->e_numkey > numkey)
			next->e_numkey--;
	    }
	    collcommon_remove(x->x_common, ep);
	}
    }
    else loud_messarg((t_pd *)x, s);
}

static void coll_assoc(t_coll *x, t_symbol *s, t_floatarg f)
{
    int numkey;
    if (loud_checkint((t_pd *)x, f, &numkey, gensym("assoc")))
    {
	t_collcommon *cc = x->x_common;
	t_collelem *ep1, *ep2;
	if ((ep1 = collcommon_numkey(cc, numkey)) &&
	    ep1->e_symkey != s)  /* LATER rethink */
	{
	    if (ep2 = collcommon_symkey(cc, s))
		collcommon_remove(cc, ep2);
	    collcommon_changesymkey(cc, ep1, s);
	}
    }
}
		
static void coll_deassoc(t_coll *x, t_symbol *s, t_floatarg f)
{
    int numkey;
    if (loud_checkint((t_pd *)x, f, &numkey, gensym("deassoc")))
    {
	t_collcommon *cc = x->x_common;
	t_collelem *ep;
	if (ep = collcommon_numkey(cc, numkey))
	    collcommon_changesymkey(cc, ep, 0);
    }
}

static void coll_subsym(t_coll *x, t_symbol *s1, t_symbol *s2)
{
    t_collelem *ep;
    if (s1 != s2 && (ep = collcommon_symkey(x->x_common, s2)))
	collcommon_changesymkey(x->x_common, ep, s1);
}

static void coll_renumber(t_coll *x, t_floatarg f)
{
    int startkey;
    if (loud_checkint((t_pd *)x, f, &startkey, gensym("renumber")))
	collcommon_renumber(x->x_common, startkey);
}

static void coll_merge(t_coll *x, t_symbol *s, int ac, t_atom *av)
{
    if (ac >= 2)
    {
	t_collcommon *cc = x->x_common;
	t_collelem *ep;
	if (av->a_type == A_FLOAT)
	{
	    int numkey;
	    if (loud_checkint((t_pd *)x, av->a_w.w_float, &numkey, s))
	    {
		if (ep = collcommon_numkey(cc, numkey))
		    collcommon_adddata(cc, ep, ac-1, av+1);
		else  /* LATER consider defining collcommon_toclosest() */
		    collcommon_tonumkey(cc, numkey, ac-1, av+1, 1);
	    }
	}
	else if (av->a_type == A_SYMBOL)
	{
	    if (ep = collcommon_symkey(cc, av->a_w.w_symbol))
		collcommon_adddata(cc, ep, ac-1, av+1);
	    else
	    {
		ep = collelem_new(ac-1, av+1, 0, av->a_w.w_symbol);
		collcommon_putafter(cc, ep, cc->c_last);
	    }
	}
	else loud_messarg((t_pd *)x, s);
    }
    else loud_messarg((t_pd *)x, s);
}

static void coll_sub(t_coll *x, t_symbol *s, int ac, t_atom *av)
{
    if (ac)
    {
	t_collelem *ep;
	if (ep = coll_findkey(x, av, s))
	{
	    t_collcommon *cc = x->x_common;
	    t_atom *key = av++;
	    ac--;
	    while (ac >= 2)
	    {
		if (av->a_type == A_FLOAT)
		{
		    int ndx;
		    if (loud_checkint((t_pd *)x, av->a_w.w_float, &ndx, 0)
			&& ndx >= 1 && ndx <= ep->e_size)
			ep->e_data[ndx-1] = av[1];
		}
		ac -= 2;
		av += 2;
	    }
	    if (s == gensym("sub"))
	    {
		coll_keyoutput(x, ep);
		if (!cc->c_selfmodified || (ep = coll_findkey(x, key, 0)))
		    coll_dooutput(x, ep->e_size, ep->e_data);
	    }
	}
    }
    else loud_messarg((t_pd *)x, s);
}

static void coll_sort(t_coll *x, t_floatarg f1, t_floatarg f2)
{
    int dir, ndx;
    if (loud_checkint((t_pd *)x, f1, &dir, gensym("sort")) &&
	loud_checkint((t_pd *)x, f2, &ndx, gensym("sort")))
	collcommon_sort(x->x_common, (dir < 0 ? 0 : 1),
			(ndx < 0 ? -1 : (ndx ? ndx - 1 : 0)));
}

static void coll_clear(t_coll *x)
{
    collcommon_clearall(x->x_common);
}

/* According to the refman, the data should be swapped, rather than the keys
   -- easy here, but apparently c74 people have chosen to avoid some effort
   needed in case of their implementation... */
static void coll_swap(t_coll *x, t_symbol *s, int ac, t_atom *av)
{
    if (ac == 2)
    {
	t_collelem *ep1, *ep2;
	if ((ep1 = coll_findkey(x, av, s)) &&
	    (ep2 = coll_findkey(x, av + 1, s)))
	    collcommon_swapkeys(x->x_common, ep1, ep2);
    }
    else loud_messarg((t_pd *)x, s);
}

/* CHECKED traversal direction change is consistent with the general rule:
   'next' always outputs e_next of a previous output, and 'prev' always
   outputs e_prev, whether preceded by 'prev', or by 'next'.  This is
   currently implemented by pre-updating of the head (which is inhibited
   if there was no previous output, i.e. after 'goto', 'end', or collection
   initialization).  CHECKME again. */

static void coll_next(t_coll *x)
{
    t_collcommon *cc = x->x_common;
    if (cc->c_headstate != COLL_HEADRESET &&
	cc->c_headstate != COLL_HEADDELETED)  /* asymmetric, LATER rethink */
    {
	if (cc->c_head)
	    cc->c_head = cc->c_head->e_next;
	if (!cc->c_head && !(cc->c_head = cc->c_first))  /* CHECKED wrapping */
	    return;
    }
    else if (!cc->c_head && !(cc->c_head = cc->c_first))
	return;
    cc->c_headstate = COLL_HEADNEXT;
    coll_keyoutput(x, cc->c_head);
    if (cc->c_head)
	coll_dooutput(x, cc->c_head->e_size, cc->c_head->e_data);
    else if (!cc->c_selfmodified)
	loudbug_bug("coll_next");  /* LATER rethink */
}

static void coll_prev(t_coll *x)
{
    t_collcommon *cc = x->x_common;
    if (cc->c_headstate != COLL_HEADRESET)
    {
	if (cc->c_head)
	    cc->c_head = cc->c_head->e_prev;
	if (!cc->c_head && !(cc->c_head = cc->c_last))  /* CHECKED wrapping */
	    return;
    }
    else if (!cc->c_head && !(cc->c_head = cc->c_first))
	return;
    cc->c_headstate = COLL_HEADPREV;
    coll_keyoutput(x, cc->c_head);
    if (cc->c_head)
	coll_dooutput(x, cc->c_head->e_size, cc->c_head->e_data);
    else if (!cc->c_selfmodified)
	loudbug_bug("coll_prev");  /* LATER rethink */
}

static void coll_end(t_coll *x)
{
    t_collcommon *cc = x->x_common;
    cc->c_head = cc->c_last;
    cc->c_headstate = COLL_HEADRESET;
}

static void coll_goto(t_coll *x, t_symbol *s, int ac, t_atom *av)
{
    if (ac)
    {
	t_collelem *ep = coll_findkey(x, av, s);
	if (ep)
	{
	    t_collcommon *cc = x->x_common;
	    cc->c_head = ep;
	    cc->c_headstate = COLL_HEADRESET;
	}
    }
    else loud_messarg((t_pd *)x, s);
}

static void coll_nth(t_coll *x, t_symbol *s, int ac, t_atom *av)
{
    if (ac >= 2 && av[1].a_type == A_FLOAT)
    {
	int ndx;
	t_collelem *ep;
	if (loud_checkint((t_pd *)x, av[1].a_w.w_float, &ndx, s) &&
	    (ep = coll_findkey(x, av, s)) &&
	    ep->e_size >= ndx)
	{
	    t_atom *ap = ep->e_data + --ndx;
	    if (ap->a_type == A_FLOAT)
		outlet_float(((t_object *)x)->ob_outlet, ap->a_w.w_float);
	    else if (ap->a_type == A_SYMBOL)
		outlet_symbol(((t_object *)x)->ob_outlet, ap->a_w.w_symbol);
	}
    }
    else loud_messarg((t_pd *)x, s);
}

static void coll_length(t_coll *x)
{
    t_collcommon *cc = x->x_common;
    t_collelem *ep = cc->c_first;
    int result = 0;
    while (ep) result++, ep = ep->e_next;
    outlet_float(((t_object *)x)->ob_outlet, result);
}

static void coll_min(t_coll *x, t_floatarg f)
{
    int ndx;
    if (loud_checkint((t_pd *)x, f, &ndx, gensym("min")))
    {
	t_collelem *found;
	if (ndx > 0)
	    ndx--;
	/* LATER consider complaining: */
	else if (ndx < 0)
	    return;  /* CHECKED silently rejected */
	/* else CHECKED silently defaults to 1 */
	if (found = coll_firsttyped(x, ndx, A_FLOAT))
	{
	    t_float result = found->e_data[ndx].a_w.w_float;
	    t_collelem *ep;
	    for (ep = found->e_next; ep; ep = ep->e_next)
	    {
		if (ep->e_size > ndx &&
		    ep->e_data[ndx].a_type == A_FLOAT &&
		    ep->e_data[ndx].a_w.w_float < result)
		{
		    found = ep;
		    result = ep->e_data[ndx].a_w.w_float;
		}
	    }
	    coll_keyoutput(x, found);
	    outlet_float(((t_object *)x)->ob_outlet, result);
	}
    }
}

static void coll_max(t_coll *x, t_floatarg f)
{
    int ndx;
    if (loud_checkint((t_pd *)x, f, &ndx, gensym("max")))
    {
	t_collelem *found;
	if (ndx > 0)
	    ndx--;
	/* LATER consider complaining: */
	else if (ndx < 0)
	    return;  /* CHECKED silently rejected */
	/* else CHECKED silently defaults to 1 */
	if (found = coll_firsttyped(x, ndx, A_FLOAT))
	{
	    t_float result = found->e_data[ndx].a_w.w_float;
	    t_collelem *ep;
	    for (ep = found->e_next; ep; ep = ep->e_next)
	    {
		if (ep->e_size > ndx &&
		    ep->e_data[ndx].a_type == A_FLOAT &&
		    ep->e_data[ndx].a_w.w_float > result)
		{
		    found = ep;
		    result = ep->e_data[ndx].a_w.w_float;
		}
	    }
	    coll_keyoutput(x, found);
	    outlet_float(((t_object *)x)->ob_outlet, result);
	}
    }
}

static void coll_refer(t_coll *x, t_symbol *s)
{
    if (!coll_rebind(x, s))
    {
	/* LATER consider complaining */
    }
}

static void coll_flags(t_coll *x, t_float f1, t_float f2)
{
    int i1;
    if (loud_checkint((t_pd *)x, f1, &i1, gensym("flags")))
    {
	t_collcommon *cc = x->x_common;
	cc->c_embedflag = (i1 != 0);
    }
}

static void coll_read(t_coll *x, t_symbol *s)
{
    t_collcommon *cc = x->x_common;
    if (s && s != &s_)
	collcommon_doread(cc, s, x->x_canvas);
    else
	hammerpanel_open(cc->c_filehandle, 0);
}

static void coll_write(t_coll *x, t_symbol *s)
{
    t_collcommon *cc = x->x_common;
    if (s && s != &s_)
	collcommon_dowrite(cc, s, x->x_canvas);
    else
	hammerpanel_save(cc->c_filehandle, 0, 0);  /* CHECKED no default name */
}

static void coll_readagain(t_coll *x)
{
    t_collcommon *cc = x->x_common;
    if (cc->c_filename)
	collcommon_doread(cc, 0, 0);
    else
	hammerpanel_open(cc->c_filehandle, 0);
}

static void coll_writeagain(t_coll *x)
{
    t_collcommon *cc = x->x_common;
    if (cc->c_filename)
	collcommon_dowrite(cc, 0, 0);
    else
	hammerpanel_save(cc->c_filehandle, 0, 0);  /* CHECKED no default name */
}

static void coll_filetype(t_coll *x, t_symbol *s)
{
    /* dummy */
}

static void coll_dump(t_coll *x)
{
    t_collcommon *cc = x->x_common;
    t_collelem *ep;
    for (ep = cc->c_first; ep; ep = ep->e_next)
    {
	coll_keyoutput(x, ep);
	if (cc->c_selfmodified)
	    break;
	coll_dooutput(x, ep->e_size, ep->e_data);
	/* FIXME dooutput() may invalidate ep as well as keyoutput()... */
    }
    outlet_bang(x->x_dumpbangout);
}

static void coll_open(t_coll *x)
{
    t_collcommon *cc = x->x_common;
    t_binbuf *bb = binbuf_new();
    int i, natoms, newline;
    t_atom *ap;
    char buf[MAXPDSTRING];
    hammereditor_open(cc->c_filehandle,
		      (x->x_name ? x->x_name->s_name : "Untitled"), "coll");
    collcommon_tobinbuf(cc, bb);
    natoms = binbuf_getnatom(bb);
    ap = binbuf_getvec(bb);
    newline = 1;
    while (natoms--)
    {
	char *ptr = buf;
    	if (ap->a_type != A_SEMI && ap->a_type != A_COMMA && !newline)
	    *ptr++ = ' ';
    	atom_string(ap, ptr, MAXPDSTRING);
    	if (ap->a_type == A_SEMI)
	{
	    strcat(buf, "\n");
	    newline = 1;
	}
	else newline = 0;
	hammereditor_append(cc->c_filehandle, buf);
	ap++;
    }
    hammereditor_setdirty(cc->c_filehandle, 0);
    binbuf_free(bb);
}

/* CHECKED if there was any editing, both close window and 'wclose'
   ask and replace the contents.  LATER debug. */
static void coll_wclose(t_coll *x)
{
    hammereditor_close(x->x_common->c_filehandle, 1);
}

static void coll_click(t_coll *x, t_floatarg xpos, t_floatarg ypos,
		       t_floatarg shift, t_floatarg ctrl, t_floatarg alt)
{
    coll_open(x);
}

#ifdef COLL_DEBUG
static void collelem_post(t_collelem *ep)
{
    if (ep->e_hasnumkey && ep->e_symkey)
	loudbug_startpost("%d %s:", ep->e_numkey, ep->e_symkey->s_name);
    else if (ep->e_hasnumkey)
	loudbug_startpost("%d:", ep->e_numkey);
    else if (ep->e_symkey)
	loudbug_startpost("%s:", ep->e_symkey->s_name);
    else loudbug_bug("collcommon_post");
    loudbug_postatom(ep->e_size, ep->e_data);
    loudbug_endpost();
}

static void collcommon_post(t_collcommon *cc)
{
    t_collelem *ep;
    for (ep = cc->c_first; ep; ep = ep->e_next) collelem_post(ep);
}

static void coll_debug(t_coll *x, t_floatarg f)
{
    t_collcommon *cc = coll_checkcommon(x);
    if (cc)
    {
	t_coll *x1 = cc->c_refs;
	t_collelem *ep, *last;
	int i = 0;
	while (x1) i++, x1 = x1->x_next;
	loudbug_post("refcount %d", i);
	for (ep = cc->c_first, last = 0; ep; ep = ep->e_next) last = ep;
	if (last != cc->c_last) loudbug_bug("coll_debug: last element");
	collcommon_post(cc);
    }
}
#endif

static void coll_separate(t_coll *x, t_floatarg f)
{
    int indx;
    t_collcommon *cc = x->x_common;
    if (loud_checkint((t_pd *)x, f, &indx, gensym("separate")))
    {
	t_collelem *ep;
	for (ep = cc->c_first; ep; ep = ep->e_next)
	    if (ep->e_hasnumkey && ep->e_numkey >= indx)
		ep->e_numkey += 1;
	collcommon_modified(cc, 0);
    }
}

static void coll_free(t_coll *x)
{
    hammerfile_free(x->x_filehandle);
    coll_unbind(x);
}

static void *coll_new(t_symbol *s)
{
    t_coll *x = (t_coll *)pd_new(coll_class);
    x->x_canvas = canvas_getcurrent();
    outlet_new((t_object *)x, &s_);
    x->x_keyout = outlet_new((t_object *)x, &s_);
    x->x_filebangout = outlet_new((t_object *)x, &s_bang);
    x->x_dumpbangout = outlet_new((t_object *)x, &s_bang);
    x->x_filehandle = hammerfile_new((t_pd *)x, coll_embedhook, 0, 0, 0);
    coll_bind(x, s);
    return (x);
}

void coll_setup(void)
{
    coll_class = class_new(gensym("coll"),
			   (t_newmethod)coll_new,
			   (t_method)coll_free,
			   sizeof(t_coll), 0, A_DEFSYM, 0);
    class_addbang(coll_class, coll_next);
    class_addfloat(coll_class, coll_float);
    class_addsymbol(coll_class, coll_symbol);
    class_addlist(coll_class, coll_list);
    class_addanything(coll_class, coll_anything);
    class_addmethod(coll_class, (t_method)coll_store,
		    gensym("store"), A_GIMME, 0);
    class_addmethod(coll_class, (t_method)coll_nstore,
		    gensym("nstore"), A_GIMME, 0);
    class_addmethod(coll_class, (t_method)coll_insert,
		    gensym("insert"), A_GIMME, 0);
    class_addmethod(coll_class, (t_method)coll_remove,
		    gensym("remove"), A_GIMME, 0);
    class_addmethod(coll_class, (t_method)coll_delete,
		    gensym("delete"), A_GIMME, 0);
    class_addmethod(coll_class, (t_method)coll_assoc,
		    gensym("assoc"), A_SYMBOL, A_FLOAT, 0);
    class_addmethod(coll_class, (t_method)coll_deassoc,
		    gensym("deassoc"), A_SYMBOL, A_FLOAT, 0);
    class_addmethod(coll_class, (t_method)coll_subsym,
		    gensym("subsym"), A_SYMBOL, A_SYMBOL, 0);
    class_addmethod(coll_class, (t_method)coll_renumber,
		    gensym("renumber"), A_DEFFLOAT, 0);
    class_addmethod(coll_class, (t_method)coll_merge,
		    gensym("merge"), A_GIMME, 0);
    class_addmethod(coll_class, (t_method)coll_sub,
		    gensym("sub"), A_GIMME, 0);
    class_addmethod(coll_class, (t_method)coll_sub,
		    gensym("nsub"), A_GIMME, 0);
    class_addmethod(coll_class, (t_method)coll_clear,
		    gensym("clear"), 0);
    class_addmethod(coll_class, (t_method)coll_sort,
		    gensym("sort"), A_FLOAT, A_DEFFLOAT, 0);
    class_addmethod(coll_class, (t_method)coll_swap,
		    gensym("swap"), A_GIMME, 0);
    class_addmethod(coll_class, (t_method)coll_next,
		    gensym("next"), 0);
    class_addmethod(coll_class, (t_method)coll_prev,
		    gensym("prev"), 0);
    class_addmethod(coll_class, (t_method)coll_end,
		    gensym("end"), 0);
    class_addmethod(coll_class, (t_method)coll_goto,
		    gensym("goto"), A_GIMME, 0);
    class_addmethod(coll_class, (t_method)coll_nth,
		    gensym("nth"), A_GIMME, 0);
    class_addmethod(coll_class, (t_method)coll_length,
		    gensym("length"), 0);
    class_addmethod(coll_class, (t_method)coll_min,
		    gensym("min"), A_DEFFLOAT, 0);
    class_addmethod(coll_class, (t_method)coll_max,
		    gensym("max"), A_DEFFLOAT, 0);
    class_addmethod(coll_class, (t_method)coll_refer,
		    gensym("refer"), A_SYMBOL, 0);
    class_addmethod(coll_class, (t_method)coll_flags,
		    gensym("flags"), A_FLOAT, A_FLOAT, 0);
    class_addmethod(coll_class, (t_method)coll_read,
		    gensym("read"), A_DEFSYM, 0);
    class_addmethod(coll_class, (t_method)coll_write,
		    gensym("write"), A_DEFSYM, 0);
    class_addmethod(coll_class, (t_method)coll_readagain,
		    gensym("readagain"), 0);
    class_addmethod(coll_class, (t_method)coll_writeagain,
		    gensym("writeagain"), 0);
    class_addmethod(coll_class, (t_method)coll_filetype,
		    gensym("filetype"), A_SYMBOL, 0);
    class_addmethod(coll_class, (t_method)coll_dump,
		    gensym("dump"), 0);
    class_addmethod(coll_class, (t_method)coll_open,
		    gensym("open"), 0);
    class_addmethod(coll_class, (t_method)coll_wclose,
		    gensym("wclose"), 0);
    class_addmethod(coll_class, (t_method)coll_click,
		    gensym("click"),
		    A_FLOAT, A_FLOAT, A_FLOAT, A_FLOAT, A_FLOAT, 0);
    class_addmethod(coll_class, (t_method)coll_separate,
		    gensym("separate"), A_FLOAT, 0);
#ifdef COLL_DEBUG
    class_addmethod(coll_class, (t_method)coll_debug,
		    gensym("debug"), A_DEFFLOAT, 0);
#endif
    hammerfile_setup(coll_class, 1);
    collcommon_class = class_new(gensym("coll"), 0, 0,
				 sizeof(t_collcommon), CLASS_PD, 0);
    /* this call is a nop (collcommon does not embed, and the hammerfile
       class itself has been already set up above), but it is better to
       have it around, just in case... */
    hammerfile_setup(collcommon_class, 0);
//    logpost(NULL, 4, "this is cyclone/coll %s, %dth %s build",
//	CYCLONE_VERSION, CYCLONE_BUILD, CYCLONE_RELEASE);
}
