/*
 *   Pure Data Packet module for cellular automata
 *   Copyright (c) by Tom Schouten <tom@zwizwa.be>
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include "pdp_ca.h"
#include "pdp_internals.h"
#include <dlfcn.h>
#include <stdio.h>
#include <stdint.h>

t_class *pdp_ca_class;        // a cellular automaton processor: single input - single output
//t_class *pdp_ca2_class;     //                                 double input - single output
t_class *pdp_ca2image_class;   // converter from ca -> grey/yv12
t_class *pdp_image2ca_class;   // converter from grey/yv12 -> ca


// *********************** CA CLASS STUFF *********************


// this is defined in the makefile
// #define PDP_CA_RULES_LIB "/path/default.scafo"

#define PDP_CA_STACKSIZE 256
#define PDP_CA_MODE_1D 1
#define PDP_CA_MODE_2D 2

typedef struct pdp_ca_data_struct
{
    uint32_t env[2*4];
    uint32_t reg[2*4];
    uint32_t stack[2*PDP_CA_STACKSIZE];
    short int random_seed[4];
} t_pdp_ca_data;

typedef struct pdp_ca_struct
{
    t_object x_obj;
    t_float x_f;

    t_outlet *x_outlet0;
    int x_queue_id;

    /* double buffering data packets */
    int x_packet0;
    int x_packet1;

    /* some data on the ca_routine */
    void (*x_ca_routine)(void);
    void *x_ca_libhandle;
    char *x_ca_rulenames;
    int x_ca_nbrules;
    char ** x_ca_rulename;
    t_symbol *x_lastrule;
    
    /* nb of iterations */
    int x_iterations;

    /* shift ca on output */
    int x_horshift;
    int x_vershift;

    /* operation mode */
    int x_mode;
    int x_fullscreen1d;

    /* aligned vector data */
    t_pdp_ca_data *x_data;

    /* output packet type */
    t_symbol *x_packet_type;

} t_pdp_ca;


static inline void check_stack(t_pdp_ca *x, uint32_t *tos, uint32_t *env) {

    /* feeder saves stack pointer in env. */
    uint32_t *tos_out = *((uint32_t **)env);

    if (tos_out != tos) {
	if (tos_out > tos) post("pdp_ca: ERROR: stack underflow detected in ca routine");
	if (tos_out < tos) post("pdp_ca: ERROR: ca routine returned more than one item");
	x->x_ca_routine = 0;
	post("pdp_ca: rule disabled");
    }
}


/* mmx feeder routine */
unsigned long long scaf_feeder(void *tos, void *reg, void (*ca_rule)(void), void *env);


/* 1D: process from packet0 -> packet0 */
static void pdp_ca_process_ca_1D(t_pdp_ca *x)
{
    t_pdp *header = pdp_packet_header(x->x_packet0);
    uint32_t  *data   = (uint32_t *)pdp_packet_data  (x->x_packet0);

    int width  = pdp_type_ca_info(header)->width;
    int height = pdp_type_ca_info(header)->height;
    int i;

    uint32_t saved;

    /* load TOS in middle of buffer to limit the effect of stack errors */
    uint32_t *tos = &x->x_data->stack[2*(PDP_CA_STACKSIZE/2)];
    uint32_t *env = &x->x_data->env[0];
    uint32_t *reg = &x->x_data->reg[0];
    void *ca_routine = x->x_ca_routine;
    uint32_t rtos;

    /* double word width: number of uint32_ts per row  */
    int dwwidth = width >> 5;
    int currow = pdp_type_ca_info(header)->currow;

    unsigned long long result = 0;

    unsigned short temp;
    unsigned short *usdata;

    /* set destination row to 4th row from top (ca time horizon is 3 deep) */
    int dwrow0 = (((currow + height - 3) % height) * width) >> 5;
    int dwrow1 = (((currow + height - 2) % height) * width) >> 5;
    int dwrow2 = (((currow + height - 1) % height) * width) >> 5;
    int dwrow3 = (currow * width) >> 5;

    /* exit if there isn't a valid routine */
    if(!ca_routine) return;


    /* compute new row */
    for(i=0; i < (dwwidth-1) ; i+=1){
	env[0] = data[dwrow0 + i];
	env[1] = data[dwrow0 + i + 1];
	env[2] = data[dwrow1 + i];
	env[3] = data[dwrow1 + i + 1];
	env[4] = data[dwrow2 + i];
	env[5] = data[dwrow2 + i + 1];
	result = scaf_feeder(tos, reg, ca_routine, env);
	data[dwrow3 + i] = result & 0xffffffff;
    }
    // i == dwwidth-1
    
    /* compute last column in row */
    env[0] = data[dwrow0 + i];
    env[1] = data[dwrow0];
    env[2] = data[dwrow1 + i];
    env[3] = data[dwrow1];
    env[4] = data[dwrow2 + i];
    env[5] = data[dwrow2];
    result = scaf_feeder(tos, reg, ca_routine, env);
    data[dwrow3 + i] = result & 0xffffffff;


    /* undo the shift */
    usdata = (unsigned short *)(&data[dwrow3]);
    temp = usdata[(dwwidth*2)-1];
    for (i = (dwwidth*2 - 1); i > 0; i--){
	usdata[i] = usdata[i-1];
    }
    usdata[0] = temp;

    check_stack(x, tos, env);


    /* save current row */
    pdp_type_ca_info(header)->currow = (currow + 1) % height;

}


/* 2D: process from packet0 -> packet1 */
static void pdp_ca_process_ca_2D(t_pdp_ca *x)
{
    t_pdp *header0 = pdp_packet_header(x->x_packet0);
    t_pdp *header1 = pdp_packet_header(x->x_packet1);
    uint32_t  *data0   = (uint32_t *)pdp_packet_data  (x->x_packet0);
    uint32_t  *data1   = (uint32_t *)pdp_packet_data  (x->x_packet1);


    int width  = pdp_type_ca_info(header0)->width;
    int height = pdp_type_ca_info(header0)->height;
    int i,j;

    /* load TOS in middle of buffer to limit the effect of stack errors */
    uint32_t *tos = &x->x_data->stack[2*(PDP_CA_STACKSIZE/2)];
    uint32_t *env = &x->x_data->env[0];
    uint32_t *reg = &x->x_data->reg[0];
    void *ca_routine = x->x_ca_routine;
    uint32_t rtos;

    int offset = pdp_type_ca_info(header0)->offset;
    int xoffset = offset % width;
    int yoffset = offset / width;

    /* double word width: number of uint32_ts per row  */
    int dwwidth = width >> 5;

    unsigned long long result = 0;

    /* exit if there isn't a valid routine */
    if(!ca_routine) return;
    if(!header0) return;
    if(!header1) return;

    //post("pdp_ca: PRE offset: %d, xoffset: %d, yoffset: %d", offset, xoffset, yoffset);

    /* calculate new offset: lines shift up, rows shift left by 16 cells */
    xoffset = (xoffset + width - 16) % width;
    yoffset = (yoffset + height - 1) % height;

    offset =  yoffset * width + xoffset;

    //post("pdp_ca: PST offset: %d, xoffset: %d, yoffset: %d", offset, xoffset, yoffset);


    pdp_type_ca_info(header1)->offset = offset;


    for(j=0; j<dwwidth*(height - 2); j+=(dwwidth<<1)){
	for(i=0; i < (dwwidth-1) ; i+=1){
	    env[0] = data0[i + j];
	    env[1] = data0[i + j + 1];
	    env[2] = data0[i + j + dwwidth];
	    env[3] = data0[i + j + dwwidth + 1];
	    env[4] = data0[i + j + (dwwidth<<1)];
	    env[5] = data0[i + j + (dwwidth<<1) + 1];
	    env[6] = data0[i + j + (dwwidth<<1) + dwwidth];
	    env[7] = data0[i + j + (dwwidth<<1) + dwwidth + 1];
	    result = scaf_feeder(tos, reg, ca_routine, env);
	    data1[i + j] = result & 0xffffffff;
	    data1[i + j + dwwidth] = result >> 32;
	}
	// i == dwwidth-1

	env[0] = data0[i + j];
	env[1] = data0[j];
	env[2] = data0[i + j + dwwidth];
	env[3] = data0[j + dwwidth];
	env[4] = data0[i + j + (dwwidth<<1)];
	env[5] = data0[j + (dwwidth<<1)];
	env[6] = data0[i + j + (dwwidth<<1) + dwwidth];
	env[7] = data0[j + (dwwidth<<1) + dwwidth];
	result = scaf_feeder(tos, reg, ca_routine, env);
	data1[i + j] = result & 0xffffffff;
	data1[i + j + dwwidth] = result >> 32;
    }

    // j == dwwidth*(height - 2)
    for(i=0; i < (dwwidth-1) ; i+=1){
	env[0] = data0[i + j];
	env[1] = data0[i + j + 1];
	env[2] = data0[i + j + dwwidth];
	env[3] = data0[i + j + dwwidth + 1];
	env[4] = data0[i];
	env[5] = data0[i + 1];
	env[6] = data0[i + dwwidth];
	env[7] = data0[i + dwwidth + 1];
	result = scaf_feeder(tos, reg, ca_routine, env);
	data1[i + j] = result & 0xffffffff;
	data1[i + j + dwwidth] = result >> 32;
    }
    // j == dwwidth*(height - 2)
    // i == dwwidth-1
    env[0] = data0[i + j];
    env[1] = data0[j];
    env[2] = data0[i + j + dwwidth];
    env[3] = data0[j + dwwidth];
    env[4] = data0[i];
    env[5] = data0[0];
    env[6] = data0[i + dwwidth];
    env[7] = data0[dwwidth];
    result = scaf_feeder(tos, reg, ca_routine, env);
    data1[i + j] = result & 0xffffffff;
    data1[i + j + dwwidth] = result >> 32;

    check_stack(x, tos, env);

    return;
}


static void pdp_ca_swappackets(t_pdp_ca *x)
{
   /* swap packets */
   int packet = x->x_packet1;
   x->x_packet1 = x->x_packet0;
   x->x_packet0 = packet;
}





/* tick advance CA one timestep */
static void pdp_ca_bang_thread(t_pdp_ca *x)
{
   int encoding;
   int packet;
   int i;
   int iterations =  x->x_iterations;
  
   /* invariant: the two packets are allways valid and compatible 
      so a bang is allways possible. this means that in the pdp an 
      invalid packet needs to be converted to a valid one */

   if (-1 == x->x_packet0) pdp_post("warning: packet 0 invalid");
   if (-1 == x->x_packet1) pdp_post("warning: packet 1 invalid");

   if (PDP_CA_MODE_2D == x->x_mode){
       for(i=0; i < iterations; i++){
      
	   /* process form packet0 -> packet1 */
	   pdp_ca_process_ca_2D(x);

	   /* swap */
	   pdp_ca_swappackets(x);
       }
   }
   else if (PDP_CA_MODE_1D == x->x_mode){
       if (x->x_fullscreen1d){
	   t_pdp *header0 = pdp_packet_header(x->x_packet0);
	   pdp_type_ca_info(header0)->currow = 0;
	   pdp_type_ca_info(header0)->offset = 0;
	   iterations = pdp_type_ca_info(header0)->height;
       }
       for(i=0; i < iterations; i++){
 
	   pdp_ca_process_ca_1D(x);
       }
   }

}

static void pdp_ca_sendpacket(t_pdp_ca *x)
{

    /* adjust offset before sending */
    t_pdp *header0 = pdp_packet_header(x->x_packet0);
    int offset, width, height, xoffset, yoffset, horshift, vershift;

    if (!header0) return;

    offset = pdp_type_ca_info(header0)->offset;
    width  = pdp_type_ca_info(header0)->width;
    height = pdp_type_ca_info(header0)->height;
    xoffset = offset % width;
    yoffset = offset / width;
    horshift = x->x_horshift;
    vershift = x->x_vershift;

    horshift %= width;
    if (horshift < 0) horshift += width;
    vershift %= height;
    if (vershift < 0) vershift += height;

    xoffset = (xoffset + horshift) % width;
    yoffset = (yoffset + vershift) % height;
    offset =  yoffset * width + xoffset;

    pdp_type_ca_info(header0)->offset = offset;



    /* output the packet */
    outlet_pdp(x->x_outlet0, x->x_packet0);
}

static void pdp_ca_bang(t_pdp_ca *x)
{
    /* we don't use input packets for testing dropping here 
       but check the queue_id to see if processing is
       still going on */
    
    if (-1 == x->x_queue_id){
	pdp_queue_add(x, pdp_ca_bang_thread, pdp_ca_sendpacket, &x->x_queue_id);
    }

    else{
	pdp_control_notify_drop(-1);
    }
}


/* this method stores the packet into x->x_packet0 (the packet
   to be processed) if it is valid. x->x_packet1 is not compatible
   it is regenerated so that it is 
   
   in short, when this routine returns both packets are valid
   and compatible.
*/


static void pdp_ca_copy_rw_if_valid(t_pdp_ca *x, int packet)
{
    t_pdp *header  = pdp_packet_header(packet);
    t_pdp *header1 = pdp_packet_header(x->x_packet1);


    int grabpacket;
    int convertedpacket;

    /* check if header is valid */
    if (!header) return;

    if (PDP_CA != header->type) return;
    if (PDP_CA_STANDARD != pdp_type_ca_info(header)->encoding) return;


    /* packet is a ca, register it */
    pdp_packet_mark_unused(x->x_packet0);
    x->x_packet0 = pdp_packet_copy_rw(packet);


    /* make sure we have the right header */
    header = pdp_packet_header(x->x_packet0);


    /* make sure that the other packet is compatible */
    if ((pdp_type_ca_info(header1)->width != pdp_type_ca_info(header)->width) ||
	 (pdp_type_ca_info(header1)->height != pdp_type_ca_info(header)->height)) {

	/* if not, throw away and clone the new one */
	pdp_packet_mark_unused(x->x_packet1);
	x->x_packet1 = pdp_packet_clone_rw(x->x_packet0);
    }

    if (-1 == x->x_packet0) pdp_post("warning: packet 0 invalid");
    if (-1 == x->x_packet1) pdp_post("warning: packet 1 invalid");


};

/* hot packet inlet */
static void pdp_ca_input_0(t_pdp_ca *x, t_symbol *s, t_floatarg f)
{

    if (s == gensym("register_rw")){
	pdp_ca_copy_rw_if_valid(x, (int)f);
    }
    else if (s == gensym("process")){
	pdp_ca_bang(x);
    }


}

/* cold packet inlet */
static void pdp_ca_input_1(t_pdp_ca *x, t_symbol *s, t_floatarg f)
{

    if (s == gensym("register_rw"))
    {
	pdp_ca_copy_rw_if_valid(x, (int)f);
    }

}


static void pdp_ca_rule_string(t_pdp_ca *x, char *c)
{
    char tmp[256];
    void (*ca_routine)(void);


    /* check if we can find string */
    sprintf(tmp, "rule_%s", c);
    if (!(ca_routine = dlsym(x->x_ca_libhandle, tmp))){
	post("pdp_ca: can't fine ca rule %s (symbol: %s)", c, tmp);
	return;
    }
    /* ok, so store routine address */
    else{
	x->x_ca_routine = ca_routine;
	x->x_lastrule = gensym(c);
    }
}


static void pdp_ca_rule(t_pdp_ca *x, t_symbol *s)
{
    /* make sure lib is loaded */
    if (!x->x_ca_libhandle) return;
    
    /* set rule by name */
    pdp_ca_rule_string(x, s->s_name);
}

static void pdp_ca_rule_index(t_pdp_ca *x, t_float f)
{
    int i = (int)f;

    /* make sure lib is loaded */
    if (!x->x_ca_libhandle) return;

    /* check index */
    if (i<0) return;
    if (i>=x->x_ca_nbrules) return;

    /* set rule by index */
    pdp_ca_rule_string(x, x->x_ca_rulename[i]);

}


static void pdp_ca_close(t_pdp_ca *x)
{
    if (x->x_ca_libhandle){
	dlclose(x->x_ca_libhandle);
	x->x_ca_libhandle = 0;
	x->x_ca_routine = 0;
	if (x->x_ca_rulename){
	    free (x->x_ca_rulename);
	    x->x_ca_rulename = 0;
	}
    

    }
}


static void pdp_ca_printrules(t_pdp_ca *x)
{
    int i;

    if (!(x->x_ca_libhandle)) return;
    post("pdp_ca: found %d rules: ", x->x_ca_nbrules);
    for(i=0;i<x->x_ca_nbrules; i++) post("%3d: %s ", i, x->x_ca_rulename[i]);


}

/* open code library */
static void pdp_ca_openlib(t_pdp_ca *x, t_symbol *s)
{

    char *c;
    int words;

    /* close current lib, if one */
    pdp_ca_close(x);

    /* try to open new lib */
    if (!(x->x_ca_libhandle = dlopen(s->s_name, RTLD_NOW))){
	post("pdp_ca: can't open ca library %s\n%s", s->s_name, dlerror());
	x->x_ca_libhandle = 0;
	return;
    }

    /* scan for valid rules */
    if (!(x->x_ca_rulenames = (char *)dlsym(x->x_ca_libhandle, "rulenames"))){
	post("pdp_ca: ERROR: %s does not contain a name table. closing.", s->s_name);
        pdp_ca_close(x);
	return;
    }

    /* count rules */
    words = 0;
    for(c = (char *)x->x_ca_rulenames; *c;){
	words++;
	while(*c++);
    }
    x->x_ca_nbrules = words;
    x->x_ca_rulename = (char **)malloc(sizeof(char *) * words);

    /* build name array */
    words = 0;
    for(c = (char *)x->x_ca_rulenames; *c;){
	x->x_ca_rulename[words] = c;
	words++;
	while(*c++);
    }

    /* ok, we're done */
    post("pdp_ca: opened rule library %s", s->s_name ,x->x_ca_nbrules);

    /* print rule names */
    //pdp_ca_printrules(x);

    /* set last selected rule */
    pdp_ca_rule(x, x->x_lastrule);
  
    
}

/* compile source file and open resulting code library */
static void pdp_ca_opensrc(t_pdp_ca *x, t_symbol *s)
{
    #define TMPSIZE 1024
    char commandline[TMPSIZE];
    char library[TMPSIZE];
    int status;

    /*  setup compiler args */
    snprintf(library, TMPSIZE, "%so", s->s_name);
    snprintf(commandline, TMPSIZE, "scafc %s %s", s->s_name, library);



    /* call compiler */
    if (system(commandline)) 
    {
	post ("pdp_ca: error compiling %s", s->s_name);
    }
    else 
    {
	post("pdp_ca: compiled %s", s->s_name);
	pdp_ca_openlib(x, gensym(library));
    }
}

/* open a source file or a library, depending on extension */
static void pdp_ca_open(t_pdp_ca *x, t_symbol *s)
{
    char *name = s->s_name;
    char *end = name;
    while(*end) end++;
    if (end == name){
	post("pdp_ca: invalid file name");
	return;
    }
    /* if the name ends with 'o' assume it is a library */
    if (end[-1] == 'o'){
	pdp_ca_openlib(x, s);
    }
    /* otherwize, assume it is a source file */
    else{
	pdp_ca_opensrc(x, s);
    }

}

/* init the current packet with random noise */
static void pdp_ca_rand(t_pdp_ca *x){

    t_pdp *header = pdp_packet_header(x->x_packet0);
    short int *data = (short int *) pdp_packet_data(x->x_packet0);
    int i;

    int nbshortints = (pdp_type_ca_info(header)->width >> 4) * pdp_type_ca_info(header)->height;

    for(i=0; i<nbshortints; i++) 
	data[i] = random();
    
}


static void pdp_ca_newca(t_pdp_ca *x, t_float width, t_float height)
{
    /* delete old packets */
    pdp_packet_mark_unused(x->x_packet0);
    pdp_packet_mark_unused(x->x_packet1);


    /* create new packets */
    x->x_packet0 = pdp_packet_new_ca(PDP_CA, width, height);
    x->x_packet1 = pdp_packet_clone_rw(x->x_packet0);

}


static void pdp_ca_iterations(t_pdp_ca *x, t_float f)
{
    int i = (int)f;

    if (i < 0) i = 0;

    x->x_iterations = i;
}

static void pdp_ca_horshift16(t_pdp_ca *x, t_float f)
{
    x->x_horshift = 16 * (int)f;
}

static void pdp_ca_vershift(t_pdp_ca *x, t_float f)
{
    x->x_vershift = (int)f;
}

static void pdp_ca_set1d(t_pdp_ca *x)
{
    x->x_mode = PDP_CA_MODE_1D;
}

static void pdp_ca_set2d(t_pdp_ca *x)
{
    x->x_mode = PDP_CA_MODE_2D;
}

static void pdp_ca_fullscreen1d(t_pdp_ca *x, t_floatarg f)
{
    if (f == 0.0f) x->x_fullscreen1d = 0;
    if (f == 1.0f) x->x_fullscreen1d = 1;
}

static void pdp_ca_free(t_pdp_ca *x)
{
    pdp_packet_mark_unused(x->x_packet0);
    pdp_packet_mark_unused(x->x_packet1);
    pdp_ca_close(x);
    free(x->x_data);
}



void *pdp_ca_new(void)
{
    t_pdp_ca *x = (t_pdp_ca *)pd_new(pdp_ca_class);

    inlet_new(&x->x_obj, &x->x_obj.ob_pd, gensym("pdp"), gensym("pdp1"));
    inlet_new(&x->x_obj, &x->x_obj.ob_pd, gensym("float"), gensym("iterations"));

    x->x_outlet0 = outlet_new(&x->x_obj, &s_anything); 

    x->x_packet0 = -1;
    x->x_packet1 = -1;
    x->x_queue_id = -1;
    
    x->x_data = (t_pdp_ca_data *)malloc(sizeof(t_pdp_ca_data));
    x->x_ca_routine = 0;
    x->x_ca_libhandle = 0;
    x->x_ca_rulename = 0;

    x->x_horshift = 0;
    x->x_vershift = 0;

    pdp_ca_newca(x, 64, 64);
    pdp_ca_iterations(x, 1);
    pdp_ca_set2d(x);
    pdp_ca_fullscreen1d(x, 0);

    x->x_packet_type = gensym("grey");
    x->x_lastrule = gensym("gameoflife");
    pdp_ca_openlib(x, gensym(PDP_CA_RULES_LIB));

    return (void *)x;
}


// *********************** CA CONVERTER CLASSES STUFF *********************
// TODO: move this to a separate file later together with other converters (part of system?)

#define PDP_CA2IMAGE 1
#define PDP_IMAGE2CA 2

typedef struct pdp_ca_conv_struct
{
    t_object x_obj;
    t_float x_f;


    int x_threshold;

    int x_packet;

    t_outlet *x_outlet0;

    /* solve identity crisis */
    int x_whoami;

    /* output packet type */
    /* only greyscale for now */
    t_symbol *x_packet_type;

} t_pdp_ca_conv;

/* hot packet inlet */
static void pdp_ca_conv_input_0(t_pdp_ca_conv *x, t_symbol *s, t_floatarg f)
{
    int packet = -1;

    if (s == gensym("register_ro")){
	pdp_packet_mark_unused(x->x_packet);
	x->x_packet = pdp_packet_copy_ro((int)f);
	return;
    }
    else if (s == gensym("process")){
	switch(x->x_whoami){
	case PDP_CA2IMAGE:
	    packet = pdp_type_ca2grey(x->x_packet);
	    break;
	case PDP_IMAGE2CA:
	    packet = pdp_type_grey2ca(x->x_packet, x->x_threshold);
	    break;
	}
	
	/* throw away the original packet */
	pdp_packet_mark_unused(x->x_packet);
	x->x_packet = -1;


	/* pass the fresh packet */
	pdp_packet_pass_if_valid(x->x_outlet0, &packet);

	/* unregister the freshly created packet */
	//pdp_packet_mark_unused(packet);

	/* output if valid */
	//if (-1 != packet) outlet_pdp(x->x_outlet0, packet);
    }


}

void pdp_ca_conv_free(t_pdp_ca_conv *x) 
{
    pdp_packet_mark_unused(x->x_packet);
}


void pdp_image2ca_threshold(t_pdp_ca_conv *x, t_float f)
{
    f *= 0x8000;

    if (f < -0x7fff) f = -0x7fff;
    if (f > 0x7fff) f = 0x7fff;

    x->x_threshold = (short int)f;
}

void *pdp_ca2image_new(void)
{
    t_pdp_ca_conv *x = (t_pdp_ca_conv *)pd_new(pdp_ca2image_class);
    x->x_outlet0 = outlet_new(&x->x_obj, &s_anything); 
    x->x_packet_type = gensym("grey");
    x->x_packet = -1;
    x->x_whoami = PDP_CA2IMAGE;
    return (void *)x;
}

void *pdp_image2ca_new(void)
{
    t_pdp_ca_conv *x = (t_pdp_ca_conv *)pd_new(pdp_image2ca_class);
    inlet_new(&x->x_obj, &x->x_obj.ob_pd, gensym("float"), gensym("threshold"));
    x->x_outlet0 = outlet_new(&x->x_obj, &s_anything); 
    x->x_packet_type = gensym("grey");
    x->x_packet = -1;
    x->x_whoami = PDP_IMAGE2CA;
    x->x_threshold = 0x4000;
    return (void *)x;
}


// *********************** CLASS SETUP FUNCTIONS *********************

#ifdef __cplusplus
extern "C"
{
#endif



void pdp_ca2image_setup(void)
{
    pdp_ca2image_class = class_new(gensym("pdp_ca2image"), (t_newmethod)pdp_ca2image_new,
    	(t_method)pdp_ca_conv_free, sizeof(t_pdp_ca), 0, A_NULL);
    class_addmethod(pdp_ca2image_class, (t_method)pdp_ca_conv_input_0, gensym("pdp"),  A_SYMBOL, A_DEFFLOAT, A_NULL);
}

void pdp_image2ca_setup(void)
{
    pdp_image2ca_class = class_new(gensym("pdp_image2ca"), (t_newmethod)pdp_image2ca_new,
    	(t_method)pdp_ca_conv_free, sizeof(t_pdp_ca), 0, A_NULL);
    class_addmethod(pdp_image2ca_class, (t_method)pdp_ca_conv_input_0, gensym("pdp"),  A_SYMBOL, A_DEFFLOAT, A_NULL);
    class_addmethod(pdp_image2ca_class, (t_method)pdp_image2ca_threshold, gensym("threshold"),  A_FLOAT, A_NULL);
}

void pdp_ca_setup(void)
{


    pdp_ca_class = class_new(gensym("pdp_ca"), (t_newmethod)pdp_ca_new,
    	(t_method)pdp_ca_free, sizeof(t_pdp_ca), 0, A_NULL);

   
    class_addmethod(pdp_ca_class, (t_method)pdp_ca_iterations, gensym("iterations"), A_FLOAT, A_NULL);
    class_addmethod(pdp_ca_class, (t_method)pdp_ca_bang, gensym("bang"), A_NULL);
    class_addmethod(pdp_ca_class, (t_method)pdp_ca_printrules, gensym("rules"), A_NULL);
    class_addmethod(pdp_ca_class, (t_method)pdp_ca_rand, gensym("random"), A_NULL);
    class_addmethod(pdp_ca_class, (t_method)pdp_ca_newca, gensym("ca"), A_FLOAT, A_FLOAT, A_NULL);
    class_addmethod(pdp_ca_class, (t_method)pdp_ca_newca, gensym("dim"), A_FLOAT, A_FLOAT, A_NULL);
    class_addmethod(pdp_ca_class, (t_method)pdp_ca_horshift16, gensym("hshift16"), A_FLOAT, A_NULL);
    class_addmethod(pdp_ca_class, (t_method)pdp_ca_vershift, gensym("vshift"), A_FLOAT, A_NULL);
    class_addmethod(pdp_ca_class, (t_method)pdp_ca_close, gensym("close"), A_NULL);
    class_addmethod(pdp_ca_class, (t_method)pdp_ca_openlib, gensym("openlib"),  A_SYMBOL, A_NULL);
    class_addmethod(pdp_ca_class, (t_method)pdp_ca_opensrc, gensym("opensrc"),  A_SYMBOL, A_NULL);
    class_addmethod(pdp_ca_class, (t_method)pdp_ca_open, gensym("open"),  A_SYMBOL, A_NULL);
    class_addmethod(pdp_ca_class, (t_method)pdp_ca_rule, gensym("rule"),  A_SYMBOL, A_NULL);
    class_addmethod(pdp_ca_class, (t_method)pdp_ca_rule_index, gensym("ruleindex"),  A_FLOAT, A_NULL);
    class_addmethod(pdp_ca_class, (t_method)pdp_ca_input_0, gensym("pdp"),  A_SYMBOL, A_DEFFLOAT, A_NULL);
    class_addmethod(pdp_ca_class, (t_method)pdp_ca_input_1, gensym("pdp1"), A_SYMBOL, A_DEFFLOAT, A_NULL);
    class_addmethod(pdp_ca_class, (t_method)pdp_ca_set1d, gensym("1D"), A_NULL);
    class_addmethod(pdp_ca_class, (t_method)pdp_ca_set2d, gensym("2D"), A_NULL);
    class_addmethod(pdp_ca_class, (t_method)pdp_ca_fullscreen1d, gensym("fullscreen1D"), A_FLOAT, A_NULL);

}

#ifdef __cplusplus
}
#endif
