/**
 * aa.c version 1.0
 * A plugin that uses libaa (ftp://ftp.ta.jcu.cz/pub/aa) to save images as
 * ASCII.
 * NOTE: This plugin *requires* aalib 1.2 or later. Earlier versions will
 * not work.
 * Code copied from all over the GIMP source.
 * Tim Newsome <nuisance@cmu.edu>
 */

#include <aalib.h>
#include <string.h>
#include <libgimp/gimp.h>
#include <gtk/gtk.h>
#include <stdio.h>
#include <stdlib.h>

/* 
 * Declare some local functions.
 */
static void query(void);
static void run(char *name, int nparams, GParam * param, int *nreturn_vals,
								GParam ** return_vals);
static gint aa_savable(gint32 drawable_ID);
static gint save_aa(int output_type, char *filename, gint32 image,
										gint32 drawable);
static gint gimp2aa(gint32 image, gint32 drawable_ID, aa_context * context);
static gint type_dialog(int selected);
static void type_dialog_close_callback(GtkWidget *widget, gpointer data);
static void type_dialog_ok_callback (GtkWidget *widget, gpointer data);
static void type_dialog_toggle_update (GtkWidget *widget, gpointer data);
static void type_dialog_cancel_callback (GtkWidget *widget, gpointer data);

/* 
 * Some global variables.
 */

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

/**
 * Type the user selected. (Global for easier UI coding.
 */
static int selected_type = 0;


MAIN()
/**
 * Called by the GIMP to figure out what this plugin does.
 */
		 static void query()
{
	static GParamDef save_args[] =
	{
		{PARAM_INT32, "run_mode", "Interactive, non-interactive"},
		{PARAM_IMAGE, "image", "Input image"},
		{PARAM_DRAWABLE, "drawable", "Drawable to save"},
		{PARAM_STRING, "filename", "The name of the file to save the image in"},
		{PARAM_STRING, "raw_filename", "The name entered"},
		{PARAM_STRING, "file_type", "File type to use"}
	};
	static int nsave_args = sizeof(save_args) / sizeof(save_args[0]);

	gimp_install_procedure("file_aa_save",
												 "Saves files in various text formats",
												 "Saves files in various text formats",
												 "Tim Newsome <nuisance@cmu.edu>",
												 "Tim Newsome <nuisance@cmu.edu>",
												 "1997",
												 "<Save>/AA",
												 "GRAY*",		/* support grayscales */
												 PROC_PLUG_IN,
												 nsave_args, 0,
												 save_args, NULL);

	gimp_register_save_handler("file_aa_save", "ansi,txt,text,html", "");
}

/**
 * Searches aa_formats defined by aalib to find the index of the type
 * specified by string.
 * -1 means it wasn't found.
 */
static int get_type_from_string(char *string)
{
	int type = 0;
	aa_format **p = aa_formats;

	while (*p && strcmp((*p)->formatname, string)) {
		p++;
		type++;
	}

	if (*p == NULL)
		return -1;

	return type;
}

/**
 * Called by the GIMP to run the actual plugin.
 */
static void run(char *name, int nparams, GParam * param, int *nreturn_vals,
								GParam ** return_vals)
{
	static GParam values[2];
	GStatusType status = STATUS_SUCCESS;
	GRunModeType run_mode;
	int output_type = 0;
	static int last_type = 0;

	/* Set us up to return a status. */
	*nreturn_vals = 1;
	*return_vals = values;
	values[0].type = PARAM_STATUS;
	values[0].data.d_status = STATUS_CALLING_ERROR;

	if (!aa_savable(param[2].data.d_int32)) {
		values[0].data.d_status = STATUS_CALLING_ERROR;
		return;
	}
	run_mode = param[0].data.d_int32;

	switch (run_mode) {
	case RUN_INTERACTIVE:
		gimp_get_data("file_aa_save", &last_type);
		output_type = type_dialog(last_type);
		break;

	case RUN_NONINTERACTIVE:
		/*  Make sure all the arguments are there!  */
		if (nparams != 6)
			status = STATUS_CALLING_ERROR;
		else
			output_type = get_type_from_string(param[5].data.d_string);
		break;

	case RUN_WITH_LAST_VALS:
		gimp_get_data("file_aa_save", &last_type);
		output_type = last_type;
		break;

	default:
		break;
	}

	if (output_type < 0) {
		status = STATUS_CALLING_ERROR;
		return;
	}

	if (save_aa(output_type, param[3].data.d_string, param[1].data.d_int32,
							param[2].data.d_int32))
		values[0].data.d_status = STATUS_EXECUTION_ERROR;
	else
		values[0].data.d_status = STATUS_SUCCESS;

	last_type = output_type;
	gimp_set_data("file_aa_save", &last_type, sizeof(last_type));
}

/**
 * The actual save function. What it's all about.
 * The image type has to be GRAY.
 */
static gint save_aa(int output_type, char *filename, gint32 image,
										gint32 drawable_ID)
{
	aa_savedata savedata =
	{NULL, NULL};
	aa_context *context = NULL;
	aa_format format;
	GDrawable *drawable = NULL;

	/*fprintf(stderr, "save %s\n", filename); */

	drawable = gimp_drawable_get(drawable_ID);
	memcpy(&format, aa_formats[output_type], sizeof(format));
	format.width = drawable->width / 2;
	format.height = drawable->height / 2;

	/*fprintf(stderr, "save_aa %i x %i\n", format.width, format.height); */

	/* Get a libaa context which will save its output to filename. */
	savedata.name = filename;
	savedata.format = &format;

	context = aa_init(&save_d, &aa_defparams, &savedata);
	if (context == NULL)
		return 1;

	gimp2aa(image, drawable_ID, context);
	aa_flush(context);
	aa_close(context);

	/*fprintf(stderr, "Success!\n"); */

	return 0;
}

static gint gimp2aa(gint32 image, gint32 drawable_ID, aa_context * context)
{
	int width, height, x, y;
	guchar *buffer;
	GDrawable *drawable = NULL;
	GPixelRgn pixel_rgn;
	aa_renderparams *renderparams = NULL;
	int bpp;

	width = aa_imgwidth(context);
	height = aa_imgheight(context);
	/*fprintf(stderr, "gimp2aa %i x %i\n", width, height); */

	drawable = gimp_drawable_get(drawable_ID);

	bpp = drawable->bpp;
	buffer = g_new(guchar, width * bpp);
	if (buffer == NULL)
		return 1;

	gimp_pixel_rgn_init(&pixel_rgn, drawable, 0, 0, drawable->width,
											drawable->height, FALSE, FALSE);

	for (y = 0; y < height; y++) {
		gimp_pixel_rgn_get_row(&pixel_rgn, buffer, 0, y, width);
		for (x = 0; x < width; x++) {
			/* Just copy one byte. If it's indexed that's all we need. Otherwise
			 * it'll be the most significant one. */
			aa_putpixel(context, x, y, buffer[x * bpp]);
		}
	}

	renderparams = aa_getrenderparams();
	renderparams->dither = AA_FLOYD_S;
	aa_render(context, renderparams, 0, 0, aa_scrwidth(context),
						aa_scrheight(context));

	return 0;
}

static gint aa_savable(gint32 drawable_ID)
{
	GDrawableType drawable_type;

	drawable_type = gimp_drawable_type(drawable_ID);

	if (drawable_type != GRAY_IMAGE && drawable_type != GRAYA_IMAGE)
		return 0;

	return 1;
}

/* 
 * User Interface dialog thingie.
 */

static gint type_dialog(int selected) {
	GtkWidget *dlg;
	GtkWidget *button;
	GtkWidget *toggle;
	GtkWidget *frame;
	GtkWidget *toggle_vbox;
	GSList *group;
	gchar **argv;
	gint argc;

	argc = 1;
	argv = g_new(gchar *, 1);
	argv[0] = g_strdup("save");

	gtk_init(&argc, &argv);
	gtk_rc_parse(gimp_gtkrc());

	/* Create the actual window. */
	dlg = gtk_dialog_new();
	gtk_window_set_title(GTK_WINDOW(dlg), "Save as text");
	gtk_window_position(GTK_WINDOW(dlg), GTK_WIN_POS_MOUSE);
	gtk_signal_connect(GTK_OBJECT(dlg), "destroy",
										 (GtkSignalFunc) type_dialog_close_callback, NULL);

	/*  Action area  */
	button = gtk_button_new_with_label("OK");
	GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
	gtk_signal_connect(GTK_OBJECT(button), "clicked",
										 (GtkSignalFunc) type_dialog_ok_callback, dlg);
	gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dlg)->action_area), button,
										 TRUE, TRUE, 0);
	gtk_widget_grab_default(button);
	gtk_widget_show(button);

	button = gtk_button_new_with_label("Cancel");
	GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
	gtk_signal_connect_object(GTK_OBJECT(button), "clicked",
											 (GtkSignalFunc) type_dialog_cancel_callback,
											 GTK_OBJECT(dlg));
	gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dlg)->action_area), button,
										 TRUE, TRUE, 0);
	gtk_widget_show(button);

	/*  file save type  */
	frame = gtk_frame_new("Data Formatting");
	gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_ETCHED_IN);
	gtk_container_border_width(GTK_CONTAINER(frame), 10);
	gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dlg)->vbox), frame, FALSE, TRUE, 0);
	toggle_vbox = gtk_vbox_new(FALSE, 5);
	gtk_container_border_width(GTK_CONTAINER(toggle_vbox), 5);
	gtk_container_add(GTK_CONTAINER(frame), toggle_vbox);

	group = NULL;
	{
		aa_format **p = aa_formats;
		int current = 0;

		while (*p != NULL) {
			toggle = gtk_radio_button_new_with_label(group, (*p)->formatname);
			group = gtk_radio_button_group(GTK_RADIO_BUTTON(toggle));
  		gtk_box_pack_start (GTK_BOX (toggle_vbox), toggle, FALSE, FALSE, 0);
			gtk_signal_connect(GTK_OBJECT(toggle), "toggled",
												 (GtkSignalFunc) type_dialog_toggle_update,
												 (*p)->formatname);
			if (current == selected)
				gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle), 1);
			else
				gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle), 0);

			gtk_widget_show(toggle);
			p++;
			current++;
		}
	}

	gtk_widget_show(toggle_vbox);
	gtk_widget_show(frame);

	gtk_widget_show(dlg);

	gtk_main();
	gdk_flush();

	return selected_type;
}

/*
 * Callbacks for the dialog.
 */

static void type_dialog_close_callback(GtkWidget *widget, gpointer data) {
  gtk_main_quit();
}

static void type_dialog_ok_callback (GtkWidget *widget, gpointer   data) {
  gtk_widget_destroy (GTK_WIDGET (data));
}

static void type_dialog_cancel_callback (GtkWidget *widget, gpointer   data) {
	selected_type = -1;
  gtk_widget_destroy (GTK_WIDGET (data));
}

static void type_dialog_toggle_update (GtkWidget *widget, gpointer data) {
	selected_type = get_type_from_string((char *)data);
}
