/*
 * Written 1997,2003 Jens Ch. Restemeier <jrestemeier@currantbun.com>
 * 
 * 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.
 *
 */

/* History:
 * 0.1 first release
 * 0.2 fixed some bugs, new gui
 * 0.3 renamed files, support for *.txt source, fixed some formulas, 
 *     deactivated PDB-interface, new preview options  
 * 0.4 new gui, reactivated PDB interface, new FF-manager, *.ffl libraries
 * 0.5 bugfixes, "About" page, some cleanup
 * 0.5a Gimp 1.1 fixes by Pedro Kaempf
 * 0.6 Gimp 1.2 fixes
 * 0.7 Patch from Artur Polaczynski to support sorting in the filter manager
 * 0.8 Compile fixes for GTK 2.0 and Gimp 1.3
 */

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
#include <ctype.h>
#include <string.h>

#include "gtk/gtk.h"
#include "libgimp/gimp.h"

#include "uf_interface.h"
#include "uf_file.h"
#include "uf_eval.h"
#include "uf_gui.h"
#include "uf_main.h"

#ifdef GIMP_VERSION

#else
#define GimpRunMode GRunModeType
#endif

s_plug_in_data plug_in_data;

int sel_x1, sel_y1, sel_x2, sel_y2;
int img_height, img_width, img_bpp, img_has_alpha, img_has_selection;

int tile_width, tile_height;
int read_tile_col, read_tile_row;
GimpTile *read_tile;

GimpDrawable *drawable, *selection_drawable;
gint32 drawable_id, selection_id, image_id;

/*
 * Draw function
 */

inline int g_min(int a, int b) 
{
	return (a<b) ? a : b;
}

inline int g_max(int a, int b)
{
	return (a>b) ? a : b;
}

int drawable_src(struct s_envir *e, int xx, int yy, int zz)
{
        int    col, row;
        int    coloff, rowoff;
        guchar *p;

	xx+=sel_x1; yy+=sel_y1;

        if (xx < 0) xx=0; 
        if (xx >= img_width) xx=img_width-1;
        if (yy < 0) yy=0;
        if (yy >= img_height) yy=img_height-1;

        col    = xx / tile_width;
        coloff = xx % tile_width;
       	row    = yy / tile_height;
        rowoff = yy % tile_height;
        if ((col != read_tile_col) || (row != read_tile_row) || (read_tile == NULL)) {
        	if (read_tile != NULL) gimp_tile_unref(read_tile, FALSE);
		read_tile = gimp_drawable_get_tile(drawable, FALSE, row, col);
	        gimp_tile_ref(read_tile);
	        read_tile_col = col;
	        read_tile_row = row;
	}
	p = read_tile->data + img_bpp * (read_tile->ewidth * rowoff + coloff);

	return p[zz];
}

/*
 * Format of preview data:
 * r(ed), g(reen), b(lue), a(lpha), m(ask)
 */
unsigned char *prev_data;
int prev_x1, prev_y1, prev_x2, prev_y2, prev_size, prev_scale;

void init_preview()
{
	prev_size=MIN(PREV_MAX, MIN(img_height, img_width));
	prev_data=(unsigned char *)malloc(prev_size*prev_size*5);
	prev_x1=MIN(sel_x1, img_width-prev_size);
	prev_y1=MIN(sel_y1, img_height-prev_size);
	prev_scale=1;
	prev_x2=prev_x1+(prev_size*prev_scale);
	prev_y2=prev_y1+(prev_size*prev_scale);
}

void done_preview()
{
	free(prev_data);
}

void build_preview()
{
        int    col, row;
        int    coloff, rowoff;
        guchar *p;
	int xp, yp, i;
	prev_x2=prev_x1+(prev_size*prev_scale);
	prev_y2=prev_y1+(prev_size*prev_scale);
	for (yp=0; yp<prev_size; yp++) {
		for (xp=0; xp<prev_size; xp++) {
			int xx, yy;
			xx=prev_x1+(((prev_x2-prev_x1)*xp)/prev_size);
			yy=prev_y1+(((prev_y2-prev_y1)*yp)/prev_size);

		        if (xx < 0) xx=0; 
		        if (xx >= img_width) xx=img_width-1;
		        if (yy < 0) yy=0;
		        if (yy >= img_height) yy=img_height-1;

		        col    = xx / tile_width;
		        coloff = xx % tile_width;
		       	row    = yy / tile_height;
		        rowoff = yy % tile_height;
		        if ((col != read_tile_col) || (row != read_tile_row) || (read_tile == NULL)) {
		        	if (read_tile != NULL) gimp_tile_unref(read_tile, FALSE);
				read_tile = gimp_drawable_get_tile(drawable, FALSE, row, col);
			        gimp_tile_ref(read_tile);
			        read_tile_col = col;
			        read_tile_row = row;
			}
			p = read_tile->data + img_bpp * (read_tile->ewidth * rowoff + coloff);

			for (i=0; i<4; i++) {
				if (i<img_bpp) {
					prev_data[(prev_size*yp+xp)*5+i]=p[i];
				} else {
					prev_data[(prev_size*yp+xp)*5+i]=0;
				}
			}
		}
	}
	gimp_tile_unref(read_tile, FALSE);
	read_tile=NULL;
	if (img_has_selection) {
		for (yp=0; yp<prev_size; yp++) {
			for (xp=0; xp<prev_size; xp++) {
				int xx, yy;
				xx=prev_x1+(((prev_x2-prev_x1)*xp)/prev_size);
				yy=prev_y1+(((prev_y2-prev_y1)*yp)/prev_size);

			        if (xx < 0) xx=0; 
			        if (xx >= img_width) xx=img_width-1;
			        if (yy < 0) yy=0;
		        	if (yy >= img_height) yy=img_height-1;

		 		col    = xx / tile_width;
		       		coloff = xx % tile_width;
		       		row    = yy / tile_height;
		        	rowoff = yy % tile_height;
		        	if ((col != read_tile_col) || (row != read_tile_row) || (read_tile == NULL)) {
		        		if (read_tile != NULL) gimp_tile_unref(read_tile, FALSE);
					read_tile = gimp_drawable_get_tile(selection_drawable, FALSE, row, col);
				        gimp_tile_ref(read_tile);
				        read_tile_col = col;
				        read_tile_row = row;
				}
				p = read_tile->data + (read_tile->ewidth * rowoff + coloff);

				prev_data[(prev_size*yp+xp)*5+4]=p[0];
			}
		}
		gimp_tile_unref(read_tile, FALSE);
		read_tile=NULL;
	} else {
		for (yp=0; yp<prev_size; yp++) {
			for (xp=0; xp<prev_size; xp++) {
				int xx, yy;
				xx=prev_x1+(((prev_x2-prev_x1)*xp)/prev_size);
				yy=prev_y1+(((prev_y2-prev_y1)*yp)/prev_size);
				if ((xx>=sel_x1) && (xx<=sel_x2) && (yy>=sel_y1) && (yy<=sel_y2)) {
					prev_data[(prev_size*yp+xp)*5+4]=255;
				} else {
					prev_data[(prev_size*yp+xp)*5+4]=0;
				}
			}
		}
	}
}

int preview_src(struct s_envir *e, int xx, int yy, int zz)
{
	if ((xx<prev_x1) || (xx>prev_x1+(prev_size*prev_scale)) || (yy<prev_y1) || (yy>(prev_size*prev_scale))) {
		return drawable_src(e, xx, yy, zz);
	}
	return prev_data[(((xx-prev_x1)/prev_scale) + ((yy-prev_y1)/prev_scale)*prev_size)*5+zz];
}

void apply_to_drawable()
{
	int k;
	int write_col, write_row;
	s_uf_tree *uf_tree[4];
	s_envir e;
	GimpPixelRgn imagePR;
	guchar *row_data;
	gimp_progress_init ("Applying filter...");
          	
	/* translate formula into tree */
	for (k=0; k<4; k++) {
		uf_tree[k]=get_uf_tree(data.source[k]);
		if (uf_tree[k]==NULL) return; 
	}

	for (k=0; k<8; k++) 
		e.value[k]=data.control_value[k];

        e.X=img_width; 
        e.Y=img_height; 
	e.M=sqrt(e.X*e.X+e.Y*e.Y)/2;
        e.Z=img_bpp;
        e.src=drawable_src;

	row_data=(guchar *)malloc((sel_x2-sel_x1)*img_bpp);
	gimp_pixel_rgn_init (&imagePR, drawable, 0,0, img_width, img_height, TRUE, TRUE);
                        
	for (write_row=sel_y1; write_row<sel_y2; write_row++) {
		e.y=write_row;
        	for (write_col=sel_x1; write_col<sel_x2; write_col++) {
			guchar *c; 
			e.x=write_col;
			calc_envir(&e);
			
			c=row_data+((write_col-sel_x1)*img_bpp);
			for (k=0; k<img_bpp;k++) {
				e.z=k;
				if (k<4) {
					c[k]=calc(&e, uf_tree[k]);
				} else {
					c[k]=0;
				}
			}
		}
		gimp_pixel_rgn_set_row(&imagePR, row_data, sel_x1, write_row, sel_x2-sel_x1);
                                
		if ((write_row % 5) == 0) 
			gimp_progress_update((gfloat)(write_row-sel_y1)/(gfloat)(sel_y2-sel_y1));
	}
	free(row_data);

	/* destroy tree */
	for (k=0; k<4; k++) {
		free_tree(uf_tree[k]);
	}
	gimp_drawable_flush(drawable);
	gimp_drawable_merge_shadow (drawable_id, TRUE);
	gimp_drawable_update (drawable_id, sel_x1, sel_y1, (sel_x2 - sel_x1), (sel_y2 - sel_y1));
}

void apply_to_preview(GtkWidget *preview)
{
	int i,j,k;
	guchar *buf;
	s_uf_tree *uf_tree[4];
	s_envir e;

	buf=(guchar *)malloc(prev_size*3);

	for (i=0; i<4; i++) {
		uf_tree[i]=get_uf_tree(data.source[i]);
	}

	for (k=0; k<8; k++) 
		e.value[k]=data.control_value[k];

        e.X=img_width; 
        e.Y=img_height; 
	e.M=sqrt(e.X*e.X+e.Y*e.Y)/2;
        e.Z=img_bpp;
        e.src=preview_src;
	for (i = 0; i < prev_size; i++) {
		for (j=0; j < prev_size; j++) {
			int col[4];
			e.x=prev_x1+(j*prev_scale); e.y=prev_y1+(i*prev_scale);
			calc_envir(&e);

			for (k=0; k<4; k++) {
				e.z=k;
				col[k]=calc(&e, uf_tree[k]);
			}

			if (prev_data[(prev_size*i+j)*5+4]==0) {
				/* 
				 * mark unmasked area 
				 */
				if ((i%10)==(j%10)) {
					buf[j*3+0]=255-prev_data[(prev_size*i+j)*5+0];
					buf[j*3+1]=255-prev_data[(prev_size*i+j)*5+1];
					buf[j*3+2]=255-prev_data[(prev_size*i+j)*5+2];
				} else {
					buf[j*3+0]=(int)(prev_data[(prev_size*i+j)*5+0]*9)/10;
					buf[j*3+1]=(int)(prev_data[(prev_size*i+j)*5+1]*9)/10;
					buf[j*3+2]=(int)(prev_data[(prev_size*i+j)*5+2]*9)/10;
				}
			} else {
				/* 
				 * show masked normal, but with shaded alpha 
				 */
				buf[j*3+0]=((col[0]*col[3])+(((((i%16)<8)^((j%16)<8)) ? 32 : 224)*(255-col[3])))/255;
				buf[j*3+1]=((col[1]*col[3])+(((((i%16)<8)^((j%16)<8)) ? 32 : 224)*(255-col[3])))/255;
				buf[j*3+2]=((col[2]*col[3])+(((((i%16)<8)^((j%16)<8)) ? 32 : 224)*(255-col[3])))/255;
			}
		}
		gtk_preview_draw_row (GTK_PREVIEW (preview), buf, 0, i, prev_size);
	}

	for (i=0; i<4; i++) {
		if (uf_tree[i]!=NULL) 
			free_tree(uf_tree[i]);
	}
	
	free(buf);
	gtk_widget_draw(preview, NULL);
	gdk_flush();
} 

/*
 * GIMP interface
 */

/*
 * parameters:
 */

GimpParamDef args[] = {
	{ GIMP_PDB_INT32, "run_mode", "Interactive, non-interactive" },
	{ GIMP_PDB_IMAGE, "image", "Input image" },
	{ GIMP_PDB_DRAWABLE, "drawable", "Input drawable" },
	{ GIMP_PDB_INT32, "number_sliders", "Number of values for slider (max 8)" }, 
	{ GIMP_PDB_INT32ARRAY, "slider_values", "values for the 8 sliders" },
	{ GIMP_PDB_INT32, "number_functions", "either 1, 3 or 4" },
	{ GIMP_PDB_STRINGARRAY, "function", "function for channel" },
};

int        nargs        = sizeof(args) / sizeof(args[0]);

/*
 * return values:
 */

GimpParamDef	*return_vals  = NULL;
int		nreturn_vals = 0;
GimpParam	values[1];

void query(void)
{
	char pdb_name[256];
	char menu_path[256];

	sprintf(pdb_name, "plug_in_user_filter");
	sprintf(menu_path, "<Image>/Filters/User Filter");

        gimp_install_procedure(pdb_name,
                               "User Filter",
                               "You can write new filters using this tool.",
				"Jens Ch. Restemeier",
				"Copyright by Jens Ch. Restemeier",
                               PLUG_IN_VERSION,
                               menu_path,
                               "RGB*,GRAY*",
                               GIMP_PLUGIN,
                               nargs,
                               nreturn_vals,
                               args,
                               return_vals);
}

void run(char *name, int nparams, GimpParam *param, int *nreturn_vals, GimpParam **return_vals)
{
        GimpRunMode	run_mode;
        GimpPDBStatusType status;

        status   = GIMP_PDB_SUCCESS;
        run_mode = param[0].data.d_int32;

        *nreturn_vals = 1;
        *return_vals  = values;

        image_id    = param[1].data.d_image;
	drawable_id = param[2].data.d_drawable;
	
        drawable = gimp_drawable_get(drawable_id);

        img_width         = gimp_drawable_width(drawable_id);
        img_height        = gimp_drawable_height(drawable_id);
        img_bpp           = gimp_drawable_bpp(drawable_id);
        img_has_alpha     = gimp_drawable_has_alpha(drawable_id);
	img_has_selection = gimp_drawable_mask_bounds(drawable_id, &sel_x1, &sel_y1, &sel_x2, &sel_y2);

	tile_width = gimp_tile_width ();
	tile_height = gimp_tile_height ();

	read_tile_col = read_tile_row = 0;

	/*
	 * Set the tile cache size 
	 */
	gimp_tile_cache_ntiles(2 * (drawable->width + gimp_tile_width() - 1) / gimp_tile_width());

	/*
	 * get selection
	 */
	if (img_has_selection) {
		selection_id=gimp_image_get_selection(image_id);
		selection_drawable=gimp_drawable_get(selection_id);
	}

	/*
	 * init data to default values, in case gimp_get_data fails 
	 */
	default_filter(&plug_in_data.data);
	/* 
	 * Possibly retrieve data from last run
	 */
	strcpy(plug_in_data.last_filename,"");
	gimp_get_data(name, &plug_in_data);
	memcpy(&data, &plug_in_data.data, sizeof(s_filter_data));

        switch (run_mode) {
                case GIMP_RUN_INTERACTIVE: {
                        /*
                         * Get information from the dialog 
                         */
			build_preview(sel_x1, sel_y1, sel_x2, sel_y2);
			init_preview();
			build_preview();
			if (uf_gui()) {
				apply_to_drawable();                        
				gimp_displays_flush();
 			} else {
 				status = GIMP_PDB_EXECUTION_ERROR;
 			}
 			done_preview();
 			break;
		}
                case GIMP_RUN_NONINTERACTIVE: {
                	/*
                	 * Get information from parameters
                	 */
			if (nparams != nargs) {
				status = GIMP_PDB_CALLING_ERROR;
			} else {
				int i;
				/* make an empty filter */
				default_filter(&data); 
				/* get slider values */
				if (param[3].data.d_int32 <= 8) {
					for (i=0; i<param[3].data.d_int32; i++) {
						data.control_value[i]=param[4].data.d_int32array[i];
					}
				}
				/* get formulas */
				if (param[5].data.d_int32 <=4) {
					for (i=0; i<param[5].data.d_int32; i++) {
						strcpy(data.source[i],param[6].data.d_stringarray[i]);
					}
				}
				apply_to_drawable();
			}
			break;
		}
                case GIMP_RUN_WITH_LAST_VALS: {
                        /*
                         * Use values from last run/default values
                         */
                        apply_to_drawable();
			gimp_displays_flush();
                        break;
        	}               
		default: {
			status = GIMP_PDB_EXECUTION_ERROR;
			break;
		}
        } 

	if (status==GIMP_PDB_SUCCESS) {
		memcpy(&plug_in_data.data, &data, sizeof(s_filter_data));
	}
	gimp_set_data(name, &plug_in_data, sizeof(plug_in_data));

	values[0].type=GIMP_PDB_STATUS;
        values[0].data.d_status = status;

	/* 
	 * The p_src() function ref's one tile. Unref it, if it's still present !
	 */
	if (read_tile!=NULL) 
		gimp_tile_unref(read_tile, FALSE);
	gimp_drawable_detach(drawable);
}

GimpPlugInInfo PLUG_IN_INFO = {
	NULL,   /* init_proc */
	NULL,   /* quit_proc */
	query,  /* query_proc */
	run     /* run_proc */
};

MAIN()
