/*
 * new_template.c
 *
 * MathMap
 *
 * Copyright (C) 2002-2008 Mark Probst
 *
 * 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.
 */

/*
 * $$g -> GIMP ? 1 : 0
 * $$m -> mathmap code
 * $$p -> USER_CURVE_POINTS
 * $$q -> USER_GRADIENT_POINTS
 * $$xy_decls         -> declarations for xy-constant variables
 * $$xy_code          -> code for xy-constant variables
 * $$x_decls          -> declarations for x-constant variables
 * $$x_code           -> code for x-constant variables
 * $$y_decls          -> declarations for y-constant variables
 * $$y_code           -> code for y-constant variables
 * $$opmacros_h       -> full name of opmacros.h file
 * $$max_debug_tuples -> MAX_DEBUG_TUPLES
 */

#include <stdlib.h>
#include <math.h>
#include <complex.h>

#if !$g
#define OPENSTEP
#endif

#define IN_COMPILED_CODE

#include "$include/opmacros.h"
#include "$include/pools.h"

#ifndef MIN
#define MIN(a,b)         (((a)<(b))?(a):(b))
#endif
#ifndef MAX
#define MAX(a,b)         (((a)<(b))?(b):(a))
#endif

#define M_PI		3.14159265358979323846	/* pi */

#define EDGE_BEHAVIOUR_COLOR          1
#define EDGE_BEHAVIOUR_WRAP           2
#define EDGE_BEHAVIOUR_REFLECT        3
#define EDGE_BEHAVIOUR_ROTATE         4


#define USERVAL_IMAGE       7


#define NUM_FLOATMAP_CHANNELS	4


#define USER_CURVE_POINTS             $p
#define USER_GRADIENT_POINTS          $q

#define MAX_DEBUG_TUPLES              $max_debug_tuples

typedef struct
{
    int number;
    int length;
    float data[];
} tuple_t;


typedef unsigned int color_t;

#define MAKE_RGBA_COLOR(r,g,b,a)            ((((color_t)(r))<<24)|(((color_t)(g))<<16)|(((color_t)(b))<<8)|((color_t)(a)))
#define RED(c)                              ((c)>>24)
#define GREEN(c)                            (((c)>>16)&0xff)
#define BLUE(c)                             (((c)>>8)&0xff)
#define ALPHA(c)                            ((c)&0xff)

#define IMAGE_DRAWABLE		1
#define IMAGE_CLOSURE		2
#define IMAGE_FLOATMAP		3
#define IMAGE_RESIZE		4


struct _mathmap_invocation_t;
struct _mathmap_frame_t;
struct _mathmap_slice_t;
struct _userval_t;
struct _image_t;

typedef void (*init_frame_func_t) (struct _mathmap_frame_t*);
typedef void (*init_slice_func_t) (struct _mathmap_slice_t*);
typedef void (*calc_lines_func_t) (struct _mathmap_slice_t*, int, int, void*, int);

typedef float* (*filter_func_t) (struct _mathmap_invocation_t*,
				 struct _image_t*,
				 float, float, float,
				 pools_t*);


typedef struct
{
    double r, g, b, a;
} GimpRGB;

typedef struct _GtkWidget GtkWidget;

typedef struct
{
    float *values;
} curve_t;

typedef struct
{
    color_t *values;
} gradient_t;

typedef struct _userval_t
{
    int type;

    union
    {
	int int_const;
	float float_const;
	float bool_const;
	struct _image_t *image;
	curve_t *curve;
	gradient_t *gradient;

	struct
	{
#ifndef OPENSTEP
	    GimpRGB button_value;
#endif
	    color_t value;
	} color;
    } v;

#ifndef OPENSTEP
    GtkWidget *widget;
#endif
} userval_t;


struct _input_drawable_t;
struct _mathfuncs_t;

typedef struct _image_t
{
    int type;
    int pixel_width;
    int pixel_height;
    union
    {
	struct _input_drawable_t *drawable;
	struct {
	    struct _mathfuncs_t *funcs;
	    filter_func_t func;
	    pools_t *pools;
	    void *xy_vars;
	    userval_t args[];
	} closure;
	struct {
	    float ax;
	    float bx;
	    float ay;
	    float by;
	    float *data;
	} floatmap;
	struct {
	    struct _image_t *original;
	    float x_factor;
	    float y_factor;
	} resize;
    } v;
} image_t;


typedef struct _filter_t filter_t;
typedef struct _interpreter_insn_t interpreter_insn_t;
typedef struct _GArray GArray;
typedef void* initfunc_t;

typedef struct _mathmap_t
{
    filter_t *filters;
    filter_t *current_filter;	/* only valid during parsing */
    filter_t *main_filter;

    unsigned int flags;

    initfunc_t initfunc;
    void *module_info;

    interpreter_insn_t *interpreter_insns;
    // FIXME: This should be in the invocation!
    GArray *interpreter_values;

    struct _mathmap_t *next;
} mathmap_t;


typedef struct _mathfuncs_t
{
    /* new_template.c.in depends on the order of this struct! */
    init_frame_func_t init_frame;
    init_slice_func_t init_slice;
    calc_lines_func_t calc_lines;
} mathfuncs_t;


typedef struct _mathmap_invocation_t
{
    mathmap_t *mathmap;

    userval_t *uservals;

    int antialiasing;
    int supersampling;

    int output_bpp;

    int edge_behaviour_x, edge_behaviour_y;
    color_t edge_color_x, edge_color_y;

    /* These are in pixel coordinates: */
    int img_width, img_height;
    int render_width, render_height;

    /* These are in virtual coordinates: */
    float image_R;		/* FIXME: remove and calculate in
				   filter code */

    int row_stride;

    unsigned char * volatile rows_finished;

    mathfuncs_t mathfuncs;

    int do_debug;
    int num_debug_tuples;
    tuple_t *debug_tuples[MAX_DEBUG_TUPLES];

    int interpreter_ip;
    color_t interpreter_output_color;
} mathmap_invocation_t;

typedef struct _mathmap_frame_t
{
    mathmap_invocation_t *invocation;

    mathfuncs_t *mathfuncs;
    userval_t *arguments;

    int frame_render_width, frame_render_height;

    int current_frame;
    float current_t;

    void *xy_vars;
    pools_t pools;
} mathmap_frame_t;

typedef struct _mathmap_slice_t
{
    mathmap_frame_t *frame;

    float sampling_offset_x, sampling_offset_y;
    int region_x, region_y, region_width, region_height;

    void *y_vars;
    pools_t pools;
} mathmap_slice_t;


color_t get_orig_val_pixel (struct _mathmap_invocation_t *invocation, float x, float y, struct _image_t *image, int frame);
color_t get_orig_val_intersample_pixel (struct _mathmap_invocation_t *invocation, float x, float y, struct _image_t *image, int frame);

float* get_floatmap_pixel (struct _mathmap_invocation_t *invocation, struct _image_t *image, float x, float y, float frame);

struct _image_t* render_image (struct _mathmap_invocation_t *invocation, struct _image_t *image, int width, int height, pools_t *pools, int force);


image_t* make_resize_image (image_t *image, float x_factor, float y_factor, pools_t *pools);


float noise (float x, float y, float z);


struct _gsl_vector;
typedef struct _gsl_vector gsl_vector;
struct _gsl_matrix;
typedef struct _gsl_matrix gsl_matrix;

gsl_matrix * gsl_matrix_alloc (const size_t n1, const size_t n2);
void gsl_matrix_free (gsl_matrix * m);
void gsl_matrix_set(gsl_matrix * m, const size_t i, const size_t j, const double x);

gsl_vector *gsl_vector_alloc (const size_t n);
void gsl_vector_free (gsl_vector * v);
double gsl_vector_get (const gsl_vector * v, const size_t i);
void gsl_vector_set (gsl_vector * v, const size_t i, double x);

int gsl_linalg_HH_solve (gsl_matrix * A, const gsl_vector * b, gsl_vector * x);

#define GSL_PREC_SINGLE		1

double gsl_sf_ellint_Kcomp (double k, unsigned int mode);
double gsl_sf_ellint_Ecomp (double k, unsigned int mode);

double gsl_sf_ellint_F (double phi, double k, unsigned int mode);
double gsl_sf_ellint_E (double phi, double k, unsigned int mode);
double gsl_sf_ellint_P (double phi, double k, double n, unsigned int mode);
double gsl_sf_ellint_D (double phi, double k, double n, unsigned int mode);

double gsl_sf_ellint_RC (double x, double y, unsigned int mode);
double gsl_sf_ellint_RD (double x, double y, double z, unsigned int mode);
double gsl_sf_ellint_RF (double x, double y, double z, unsigned int mode);
double gsl_sf_ellint_RJ (double x, double y, double z, double p, unsigned int mode);

int gsl_sf_elljac_e (double u, double m, double *sn, double *cn, double *dn);

complex float cgamma (complex float z);

double gsl_sf_beta (double a, double b);

extern void save_debug_tuples (mathmap_invocation_t *invocation, int row, int col);

#define DECLARE_NATIVE_FILTER(name)	extern image_t* name (mathmap_invocation_t*, userval_t*, pools_t*)
$native_filter_decls

$filter_begin
static void
calc_lines_$name (mathmap_slice_t *slice, int first_row, int last_row, void *q, int floatmap);

static void
init_frame_$name (mathmap_frame_t *mmframe);

static void
init_slice_$name (mathmap_slice_t *slice);

static float*
filter_$name (mathmap_invocation_t *invocation, image_t *closure, float x, float y, float t, pools_t *pools);

static mathfuncs_t mathfuncs_$name;
$filter_end

#undef ARG
#define ARG(i)			(arguments[(i)])

$filter_begin
typedef struct
{
    $xy_decls
} xy_const_vars_t_$name;

typedef struct
{
    $y_decls
} y_const_vars_t_$name;

static void
calc_lines_$name (mathmap_slice_t *slice, int first_row, int last_row, void *q, int floatmap)
{
    mathmap_frame_t *mmframe = slice->frame;
    mathmap_invocation_t *invocation = mmframe->invocation;
    color_t (*get_orig_val_pixel_func) (mathmap_invocation_t*, float, float, image_t*, int);
    int row, col;
    float t = mmframe->current_t;
    float R = invocation->image_R;
    int __canvasPixelW = invocation->img_width;
    int __canvasPixelH = invocation->img_height;
    int __renderPixelW = invocation->render_width;
    int __renderPixelH = invocation->render_height;
    float sampling_offset_x = slice->sampling_offset_x, sampling_offset_y = slice->sampling_offset_y;
    int origin_x = slice->region_x, origin_y = slice->region_y;
    int frame = mmframe->current_frame;
    int output_bpp = invocation->output_bpp;
    int is_bw = output_bpp == 1 || output_bpp == 2;
    int need_alpha = output_bpp == 2 || output_bpp == 4;
    int alpha_index = output_bpp - 1;
    xy_const_vars_t_$name *xy_vars = mmframe->xy_vars;
    pools_t pixel_pools;
    pools_t *pools;
    int region_x = slice->region_x;
    int frame_render_width = mmframe->frame_render_width;
    int frame_render_height = mmframe->frame_render_height;
    userval_t *arguments = mmframe->arguments;

    init_pools(&pixel_pools);

    first_row = MAX(0, first_row);
    last_row = MIN(last_row, slice->region_y + slice->region_height);

    if (invocation->antialiasing)
	get_orig_val_pixel_func = get_orig_val_intersample_pixel;
    else
	get_orig_val_pixel_func = get_orig_val_pixel;

    for (row = first_row - slice->region_y; row < last_row - slice->region_y; ++row)
    {
	float y = CALC_VIRTUAL_Y(row + slice->region_y, frame_render_height, sampling_offset_y);
	unsigned char *p = q;
	float *fp = q;

	pools = &slice->pools;

	$x_decls

	$x_code

	pools = &pixel_pools;

	for (col = 0; col < slice->region_width; ++col)
	{
	    y_const_vars_t_$name *y_vars = &((y_const_vars_t_$name*)slice->y_vars)[col];
	    float x = CALC_VIRTUAL_X(col + region_x, frame_render_width, sampling_offset_x);
	    float *return_tuple;

	    if (invocation->do_debug)
		invocation->num_debug_tuples = 0;

	    reset_pools(pools);

	    {
		$m
	    }

	    if (floatmap)
	    {
		int i;

		for (i = 0; i < NUM_FLOATMAP_CHANNELS; ++i)
		    fp[i] = return_tuple[i];
	    }
	    else
	    {
		if (is_bw)
		    p[0] = (TUPLE_RED(return_tuple) * 0.299
			    + TUPLE_GREEN(return_tuple) * 0.587
			    + TUPLE_BLUE(return_tuple) * 0.114) * 255.0;
		else
		{
		    p[0] = TUPLE_RED(return_tuple) * 255.0;
		    p[1] = TUPLE_GREEN(return_tuple) * 255.0;
		    p[2] = TUPLE_BLUE(return_tuple) * 255.0;
		}
		if (need_alpha)
		    p[alpha_index] = TUPLE_ALPHA(return_tuple) * 255.0;
	    }

	    if (invocation->do_debug)
		save_debug_tuples(invocation, row, col);

	    p += output_bpp;
	    fp += NUM_FLOATMAP_CHANNELS;
	}

	if (floatmap)
	    q = (float*)q + frame_render_width * NUM_FLOATMAP_CHANNELS;
	else
	    q = (unsigned char*)q + invocation->row_stride;

	if (!invocation->supersampling)
	    invocation->rows_finished[row] = 1;
    }

    free_pools(&pixel_pools);
}

static void
init_frame_$name (mathmap_frame_t *mmframe)
{
    mathmap_invocation_t *invocation = mmframe->invocation;
    xy_const_vars_t_$name *xy_vars;
    color_t (*get_orig_val_pixel_func) (mathmap_invocation_t*, float, float, image_t*, int);
    int frame = mmframe->current_frame;
    float t = mmframe->current_t;
    int __canvasPixelW = invocation->img_width;
    int __canvasPixelH = invocation->img_height;
    int __renderPixelW = invocation->render_width;
    int __renderPixelH = invocation->render_height;
    float R = invocation->image_R;
    pools_t *pools = &mmframe->pools;
    userval_t *arguments = mmframe->arguments;

    if (invocation->antialiasing)
	get_orig_val_pixel_func = get_orig_val_intersample_pixel;
    else
	get_orig_val_pixel_func = get_orig_val_pixel;

    mmframe->xy_vars = xy_vars = (xy_const_vars_t_$name*)pools_alloc(pools, sizeof(xy_const_vars_t_$name));

    {
	$xy_code
    }
}

static void
init_slice_$name (mathmap_slice_t *slice)
{
    mathmap_frame_t *mmframe = slice->frame;
    mathmap_invocation_t *invocation = mmframe->invocation;
    color_t (*get_orig_val_pixel_func) (mathmap_invocation_t*, float, float, image_t*, int);
    int frame = mmframe->current_frame;
    float t = mmframe->current_t;
    int __canvasPixelW = invocation->img_width;
    int __canvasPixelH = invocation->img_height;
    int __renderPixelW = invocation->render_width;
    int __renderPixelH = invocation->render_height;
    float R = invocation->image_R;
    pools_t *pools = &slice->pools;
    userval_t *arguments = mmframe->arguments;

    if (invocation->antialiasing)
	get_orig_val_pixel_func = get_orig_val_intersample_pixel;
    else
	get_orig_val_pixel_func = get_orig_val_pixel;

    slice->y_vars = (y_const_vars_t_$name*)pools_alloc(pools, sizeof(y_const_vars_t_$name) * slice->region_width);

    {
	xy_const_vars_t_$name *xy_vars = mmframe->xy_vars;
	int col;

	for (col = 0; col < slice->region_width; ++col)
	{
	    y_const_vars_t_$name *y_vars = &((y_const_vars_t_$name*)slice->y_vars)[col];
	    float x = CALC_VIRTUAL_X(col + slice->region_x, mmframe->frame_render_width, slice->sampling_offset_x);

	    {
		$y_code
	    }
	}
    }
}

static float*
filter_$name (mathmap_invocation_t *invocation, image_t *closure, float x, float y, float t, pools_t *pools)
{
    color_t (*get_orig_val_pixel_func) (mathmap_invocation_t*, float, float, image_t*, int);
    int frame = 0;
    int __canvasPixelW = invocation->img_width;
    int __canvasPixelH = invocation->img_height;
    int __renderPixelW = invocation->render_width;
    int __renderPixelH = invocation->render_height;
    float R = invocation->image_R;
    float *return_tuple;
    xy_const_vars_t_$name *xy_vars;
    y_const_vars_t_$name _y_vars;
    y_const_vars_t_$name *y_vars = &_y_vars;
    userval_t *arguments = closure->v.closure.args;

    if (invocation->antialiasing)
	get_orig_val_pixel_func = get_orig_val_intersample_pixel;
    else
	get_orig_val_pixel_func = get_orig_val_pixel;

    if (closure->v.closure.xy_vars == 0)
    {
	pools_t *pools = closure->v.closure.pools;

	xy_vars = closure->v.closure.xy_vars = pools_alloc(pools, sizeof(xy_const_vars_t_$name));

	{
	    $xy_code
	}
    }
    else
	xy_vars = closure->v.closure.xy_vars;

    {
	$y_code
    }
    {
	$x_decls

	$x_code

	{
	    $m
	}
    }

    return return_tuple;
}
$filter_end

mathfuncs_t
mathmapinit (mathmap_invocation_t *invocation)
{
$filter_begin
    mathfuncs_$name.init_frame = &init_frame_$name;
    mathfuncs_$name.init_slice = &init_slice_$name;
    mathfuncs_$name.calc_lines = &calc_lines_$name;
$filter_end

    return mathfuncs_$filter_name;
}
