/* Hey EMACS -*- linux-c -*- */
/* $Id$ */

/*  TiEmu - Tiemu Is an EMUlator
 *
 *  Copyright (c) 2000-2001, Thomas Corvazier, Romain Lievin
 *  Copyright (c) 2001-2003, Romain Lievin
 *  Copyright (c) 2003, Julien Blache
 *  Copyright (c) 2004, Romain Liévin
 *  Copyright (c) 2005, Romain Liévin
 *  Copyright (c) 2005, Julien Blache
 *  Copyright (c) 2006, Kevin Kofler
 *
 *  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., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
 */

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif				/*  */

#ifdef __WIN32__
#include <io.h>
#endif
#include <stdlib.h>
#include <string.h>
#include <gtk/gtk.h>
#include <glade/glade.h>
#include <gdk-pixbuf/gdk-pixbuf.h>

#if GTK_CHECK_VERSION(2,18,0)
#undef GTK_WIDGET_STATE
#define GTK_WIDGET_STATE(wid) (gtk_widget_get_state(wid))
#endif

#include "intl.h"
#include "paths.h"
#include "skinops.h"
#include "support.h"
#include "ti68k_int.h"
#include "struct.h"
#include "tie_error.h"
#include "calc.h"
#include "dbg_all.h"
#include "screenshot.h"
#include "keymap.h"
#include "logging.h"
#include "gscales.h"

#define ALLOW_RESIZE_WIN32

GtkWidget          *main_wnd = NULL;
gboolean           explicit_destroy = 0;
GtkWidget          *area = NULL;

SKIN_INFOS         skin_infos = { 0 };

extern GdkPixbuf*  lcd_mem;
extern GdkPixbuf*  lcd;
extern GdkPixmap*  pixmap;

extern Pc2TiKey*   kbd_keymap;
extern const char* skn_keymap;
extern const char  sknKey92[];
extern const char  sknKey89[];

extern uint32_t*   lcd_bytmap;

extern LCD_INFOS   li;
extern float       sf; // scaling factor

extern LCD_RECT    ls;
extern LCD_RECT    lr;
extern SKN_RECT    sr;
extern WND_RECT    wr;

static guint       tid = -1;

extern int         shot_cnt;
extern int         skip_cnt;

// part 1: set scale factor
static void set_scale(int view_mode)
{
	if(view_mode == VIEW_NORMAL)
	{
		options.scale = sf = 1.0;
	}
	else if(view_mode == VIEW_LARGE)
	{
		options.scale = sf = 2.0;
	}
	else if(view_mode == VIEW_FULL)
	{
		GdkScreen* screen = gdk_screen_get_default();
		gint sw = gdk_screen_get_width(screen);
		gint sh = gdk_screen_get_height(screen);

		sf = (float)sw / lr.w;
		sf = (float)sh / lr.h;
		//printf("%i %i %f\n", sw, lr.w, sf);
		//printf("%i %i %f\n", sh, lr.h, sf);

		options.scale = sf = (float)1.0;	// restricted to 3.0, too CPU intensive !
	}
}

// part 2: compute sizes
static void set_infos(void)	// set window & lcd sizes
{
	// LCD rectangle (source: skin)
	ls.x = (int)(sf * skin_infos.lcd_pos.left); 
	ls.y = (int)(sf * skin_infos.lcd_pos.top);
	ls.w = (int)(sf * tihw.lcd_w);
	ls.h = (int)(sf * tihw.lcd_h);

	// LCD rectangle (target: window)
	if(options.skin) 
	{
		lr.x = ls.x; 
		lr.y = ls.y;
	}
	else 
	{
		lr.x = 0;
		lr.y = 0;
	}  
	lr.w = (int)(sf * tihw.lcd_w);
	lr.h = (int)(sf * tihw.lcd_h);


	// SKN rectangle
	sr.x = sr.y = 0;
	sr.w = (int)(sf * skin_infos.width);
	sr.h = (int)(sf * skin_infos.height);

	// WND rectangle (= LCD or SKN depending on w/ or w/o skin)
	wr.x = wr.y = 0;
	if(options.skin)
	{
		wr.w = sr.w;
		wr.h = sr.h;
	}
	else
	{
		wr.w = lr.w;
		wr.h = lr.h;
	}

#if 0
	printf("LCD src: %3i %3i %3i %3i\n", ls.x, ls.y, ls.w, ls.h);
	printf("LCD dst: %3i %3i %3i %3i\n", lr.x, lr.y, lr.w, lr.h);
	printf("SKN    : %3i %3i %3i %3i\n", sr.x, sr.y, sr.w, sr.h);
	printf("WND    : %3i %3i %3i %3i\n", wr.x, wr.y, wr.w, wr.h);
#endif
}

// part 3: set changes on window
static void set_window(int full_redraw) 
{
	if(main_wnd->window == NULL)
		return;

	// resize window and drawing area
	if(full_redraw)
		gtk_window_resize(GTK_WINDOW(main_wnd), wr.w, wr.h);

#if defined(__WIN32__) && defined(ALLOW_RESIZE_WIN32)
	if(!full_redraw)
		gdk_window_resize(main_wnd->window, wr.w, wr.h);
#endif

	// reallocate backing pixmap
	if(pixmap != NULL)
	{
		// free current backing pixmap
		g_object_unref(pixmap);
		pixmap = NULL;

		// and allocate a new one
		pixmap = gdk_pixmap_new(main_wnd->window, wr.w, wr.h, -1);
		if(pixmap == NULL)
		{
			gchar *s = g_strdup_printf("unable to create backing pixmap.\n");
			tiemu_error(0, s);
			g_free(s);
			return;
		}
	}
}

static void set_constraints(void)
{
	// Allows resizing of window with a constant aspect ratio.
	// This is the right way as used under Linux. 
        // Does not work under Windows thus not enabled.

#if !defined(__WIN32__) || !defined(ALLOW_RESIZE_WIN32)
	if(1)
	{
		GdkGeometry geom = { -1 };
		GdkWindowHints mask = GDK_HINT_MIN_SIZE | GDK_HINT_ASPECT;
		double r = (float)wr.w / wr.h;

		geom.min_width = 100;
		geom.min_height = 100;

		geom.min_aspect = r;
		geom.max_aspect = r;
		gtk_window_set_geometry_hints(GTK_WINDOW(main_wnd), area, &geom, mask);

		//printf("set_constraints: %i %i %1.2f\n", wr.w, wr.h, r);
	}
#endif
}

// Main wnd by loading glade xml file or by executing glade generated code
gint display_main_wnd(void)
{
	GladeXML *xml;
	gchar *title;

	xml = glade_xml_new(tilp_paths_build_glade("calc-2.glade"), "calc_wnd", PACKAGE);
	if (!xml)
		g_error(_("%s: GUI loading failed!\n"), __FILE__);
	glade_xml_signal_autoconnect(xml);

	main_wnd = glade_xml_get_widget(xml, "calc_wnd");
	area = glade_xml_get_widget(xml, "drawingarea1");

	gtk_window_move(GTK_WINDOW(main_wnd), options3.calc.rect.x, options3.calc.rect.y);
	set_constraints();
	gtk_widget_realize(main_wnd);	// set drawing area valid

	// set window title (useful for TIGCC IDE for instance)
	// Note: lpWindowName is "TiEmu (%s)" and lpClassName is "gdkWindowToplevel"
	title = g_strdup_printf("TiEmu (%s)", ti68k_calctype_to_string(tihw.calc_type));
	gtk_window_set_title(GTK_WINDOW(main_wnd), title);
	g_free(title);

	return 0;
}

extern void on_exit_without_saving_state1_activate(GtkMenuItem* item, gpointer data);

GLADE_CB void on_calc_wnd_destroy (GtkObject *object, gpointer user_data)
{
	// Uninstall LCD refresh (to avoid earlier use of main_wnd by hid_lcd_update)
	g_source_remove(tid);

	// When GTK called this signal, the widget has already been destroy
	// thus set the pointer to a valid value, ie NULL .
	main_wnd = NULL;

	if(!explicit_destroy)
		on_exit_without_saving_state1_activate(NULL, NULL);
}

extern void redraw_skin(void);

GLADE_CB gboolean on_drawingarea1_configure_event (GtkWidget       *widget,
                                                   GdkEventConfigure *event,
                                                   gpointer         user_data)
{
	float factor;

	// compute scaling factor
	if(options.skin)
		factor = (float)event->width / (float)skin_infos.width;
	else
		factor = (float)event->width / (float)tihw.lcd_w;

#if 0
	printf("on_drawingarea1_configure_event:  x y w h = %i %i %i %i\n", 
		event->x, event->y, event->width, event->height);
	printf("on_drawingarea1_configure_event: f = %1.2f\n", factor);
#endif

	// if normal or large view then exits
	if((factor == 1.0) || (factor == 2.0))
		return FALSE;

	// set scaling factor
	options.scale = sf = factor;
	options.view = VIEW_CUSTOM;

	// compute sizes
	set_infos();

	// and set window size
	set_window(0);
	redraw_skin();

	return FALSE;
}

GLADE_CB gboolean on_drawingarea1_expose_event (GtkWidget       *widget,
                                                GdkEventExpose  *event,
                                                gpointer         user_data)
{
	gdk_draw_pixmap(widget->window,
	                widget->style->fg_gc[GTK_WIDGET_STATE (widget)],
	                pixmap,
	                event->area.x, event->area.y,
	                event->area.x, event->area.y,
	                event->area.width, event->area.height);

	return FALSE;
}

static int match_skin(int calc_type)
{
	SKIN_INFOS *sk = &skin_infos;
	int ok;
	gchar *skin_name, *s;

	s = g_strdup(ti68k_calctype_to_string(calc_type));
	skin_name = g_ascii_strdown(s, strlen(s));
	g_free(s);

	if(!strcmp(skin_name, "ti92+"))
		skin_name[4] = '\0';

#ifdef __IPAQ__
	s = g_strconcat("ipaq_", skin_name, NULL);
	g_free(skin_name);
	skin_name = s;
#endif

	// filename is "", load default skin
	if(!strcmp(g_basename(options.skin_file), ""))
	{
		g_free(options.skin_file);
		options.skin_file = g_strdup_printf("%s%s.skn", inst_paths.skin_dir, skin_name);
		g_free(skin_name);
		return -1;
	}

	// load skin header
	if(skin_read_header(sk, options.skin_file) == -1)
	{
		g_free(options.skin_file);
		options.skin_file = g_strdup_printf("%s%s.skn", inst_paths.skin_dir, skin_name);
		g_free(skin_name);
		return -1;
	}

	// is skin compatible
	switch(tihw.calc_type)
	{
		case TI92:
		case TI92p:
			ok = !strcmp((const char *)sk->calc, SKIN_TI92) || !strcmp((const char *)sk->calc, SKIN_TI92P);
			break;
		case TI89:
			ok = !strcmp((const char *)sk->calc, SKIN_TI89);
			break;
		case TI89t:
			ok = !strcmp((const char *)sk->calc, SKIN_TI89T);
			break;
		case V200:
			ok = !strcmp((const char *)sk->calc, SKIN_V200);
			break;
		default:
			ok = 0;
			break;
	}

	if(!ok)
	{
		g_free(options.skin_file);
		options.skin_file = g_strdup_printf("%s%s.skn", inst_paths.skin_dir, skin_name);

		//tiemu_error(0, _("skin incompatible with the current calc model. Falling back to default skin."));
		g_free(skin_name);
		return -1;
	}

	g_free(skin_name);
	return 0;
}

static int match_keymap(int calc_type)
{
	gchar *keys_name, *s;
	int ct, ok;

	s = g_strdup(ti68k_calctype_to_string(calc_type));
	keys_name = g_ascii_strdown(s, strlen(s));

	if(!strcmp(keys_name, "ti92+") || !strcmp(keys_name, "ti89t"))
		keys_name[4] = '\0';
	if(!strcmp(keys_name, "v200plt"))
		strcpy(keys_name, "ti92");

	// filename is "", load default keymap
	if(!strcmp(g_basename(options.keys_file), ""))
	{
		g_free(options.keys_file);
		options.keys_file = g_strdup_printf("%s%s.map", inst_paths.skin_dir, keys_name);
	}

	// load keymap header
	ct = keymap_read_header(options.keys_file);
	if(ct == -1)
	{
		g_free(options.keys_file);
		options.keys_file = g_strdup_printf("%s%s.map", inst_paths.skin_dir, keys_name);
		g_free(keys_name);
		return -1;
	}

	// is keymap compatible
	switch(tihw.calc_type)
	{
		case TI92:
		case TI92p:
		case V200:
			ok = (ct == TI92) || (ct == TI92p) || (ct == V200);
			break;
		case TI89:
		case TI89t:
			ok = (ct == TI89) || (ct == TI89t);
			break;
		default: 
			ok = 0;
			break;
	}

	if(!ok)
	{
		g_free(options.keys_file);
		options.keys_file = g_strdup_printf("%s%s.map", inst_paths.skin_dir, keys_name);

		//tiemu_error(0, _("keymap incompatible with the current calc model. Falling back to default keymap."));
		g_free(keys_name);
		return -1;
	}

	g_free(keys_name);
	return 0;
}

G_LOCK_EXTERN(lcd_flag);
extern volatile int lcd_flag;
extern volatile int debugger;

static gint hid_refresh (gpointer data)
{
	if(lcd_flag || (tihw.hw_type >= HW2))
	{
		// TI92+: jackycar, TI89: baballe
		hid_update_lcd();
		G_LOCK(lcd_flag);
		lcd_flag = 0;
		G_UNLOCK(lcd_flag);

		if(tihw.hw_type >= HW2)
			lcd_hook_hw2(TRUE);
	}

	return TRUE;
}

void compute_convtable(void);
void compute_grayscale(void);

extern void dnd_init(void);
extern void dnd_exit(void);

int hid_init(void)
{
	// Found a PC keyboard keymap
	match_keymap(tihw.calc_type);

	// Load kbd keymap
	if(keymap_load(options.keys_file) == -1)
	{
		gchar *s = g_strdup_printf("unable to load this keymap: <%s>\n", options.keys_file);
		tiemu_error(0, s);
		g_free(s);
		return -1;
	}

	// Found a skin
	match_skin(tihw.calc_type);

	// Load skin (2 parts)
	if(skin_load(&skin_infos, options.skin_file) == -1) 
	{
		gchar *s = g_strdup_printf("unable to load this skin: <%s>\n", options.skin_file);
		tiemu_error(0, s);
		g_free(s);
		return -1;
	}
  
	// Set skin keymap depending on calculator type
	switch(tihw.calc_type)
	{
		case TI92:
		case TI92p:
		case V200:
			skn_keymap = sknKey92;
			break;
		case TI89:
		case TI89t:
			skn_keymap = sknKey89;
			break;
		default:
		{
			gchar *s = g_strdup_printf("no skin found for this calc\n");
			tiemu_error(0, s);
			g_free(s);
			return -1;
		}
	}

	// Set window/LCD sizes
	sf = options.scale;
	set_scale(options.view);
	set_infos();

	// Allocate the TI screen buffer
	lcd_bytmap = (uint32_t *)malloc(LCDMEM_W * LCDMEM_H);

	// Allocate the lcd pixbuf
	lcd_mem = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, LCDMEM_W, LCDMEM_H);
	if(lcd_mem == NULL)
	{
		gchar *s = g_strdup_printf("unable to create LCD pixbuf.\n");
		tiemu_error(0, s);
		g_free(s);
		return -1;
	}

	// Used by TI89 (the LCD view is clipped from memory view)
	lcd = gdk_pixbuf_new_subpixbuf(lcd_mem, 0, 0, tihw.lcd_w, tihw.lcd_h);
    
	// Constants for LCD update (speed-up)
	li.n_channels = gdk_pixbuf_get_n_channels (lcd_mem);
	li.width = gdk_pixbuf_get_width (lcd_mem);
	li.height = gdk_pixbuf_get_height (lcd_mem);
	li.rowstride = gdk_pixbuf_get_rowstride (lcd_mem);
	li.pixels = gdk_pixbuf_get_pixels (lcd_mem);

	// Create main window
	display_main_wnd();

	// Allocate the backing pixmap (used for drawing and refresh)
	pixmap = gdk_pixmap_new(main_wnd->window, wr.w, wr.h, -1);
	if(pixmap == NULL)
	{
		gchar *s = g_strdup_printf("unable to create backing pixmap.\n");
		tiemu_error(0, s);
		g_free(s);
		return -1;
	}

	// Draw the skin and compute grayscale palette
	set_window(1);
	redraw_skin();
	compute_grayscale();

	// Init the planar/chunky conversion table for LCD
	compute_convtable();

	// Install LCD refresh: 100 FPS (10 ms)
	tid = g_timeout_add((params.lcd_rate == -1) ? 50 : params.lcd_rate, (GSourceFunc)hid_refresh, NULL);

	explicit_destroy = 0;
	gtk_widget_show(main_wnd);	// show wnd here

	if(options.view == VIEW_FULL)
		gdk_window_fullscreen(main_wnd->window);

	lcd_planes[0] = tihw.lcd_adr;
	lcd_planebufs[0] = &tihw.ram[tihw.lcd_adr];
	ngc = 1;
	lcd_changed = 1;

	dnd_init();

	return 0;
}

int hid_exit(void)
{
	// Uninstall LCD refresh
	g_source_remove(tid);

	// Release resources
	if(lcd_mem != NULL)
	{
		g_object_unref(lcd_mem);
		lcd_mem = NULL;
		g_object_unref(lcd);
		lcd = NULL;
	}

	if(pixmap != NULL)
	{
		g_object_unref(pixmap);
		pixmap = NULL;
	}

	// Destroy window
	if(main_wnd)
	{
		explicit_destroy = !0;
		gtk_widget_destroy(main_wnd);
	}

	return 0;
}

void hid_lcd_rate_set(void)
{
	g_source_remove(tid);

	tid = g_timeout_add((params.lcd_rate == -1) ? 50 : params.lcd_rate, (GSourceFunc)hid_refresh, NULL);
}

int hid_switch_with_skin(void)
{
	options.skin = 1;
	set_infos();
	set_constraints();
	set_window(1);
	redraw_skin();

	return 0;
}

int hid_switch_without_skin(void)
{
	options.skin = 0;
	set_infos();
	set_constraints();
	set_window(1);
	redraw_skin();

	return 0;
}

int hid_change_skin(const char *filename)
{
	int ret1, ret2;

	ret1 = hid_exit();
	ret2 = hid_init();

	return ret1 | ret2;
}

int hid_switch_fullscreen(void)
{
	if(options.view != VIEW_FULL)
	{
		set_scale(options.view = VIEW_FULL);
		set_infos();
		set_window(1);
		redraw_skin();
		gdk_window_fullscreen(main_wnd->window);
	}

	return 0;
}

int hid_switch_normal_view(void)
{
	if(options.view != VIEW_NORMAL)
	{
		set_scale(options.view = VIEW_NORMAL);
		set_infos();
		set_window(1);
		redraw_skin();
		gdk_window_unfullscreen(main_wnd->window);
	}

    return 0;
}

int hid_switch_large_view(void)
{
	if(options.view != VIEW_LARGE)
	{
		set_scale(options.view = VIEW_LARGE);
		set_infos();
		set_window(1);
		redraw_skin();
		gdk_window_unfullscreen(main_wnd->window);
	}

	return 0;
}

int  hid_screenshot_burst(void)
{
	shot_cnt = options2.shots;
	skip_cnt = options2.skips;

	return 0;
}

int  hid_screenshot_single(void)
{
	gchar *outfile;
	const gchar *ext = "";
	const gchar *type = "";

	GdkPixbuf *pixbuf = { 0 };
	gboolean result = FALSE;
	GError *error = NULL;

	switch(options2.format) 
	{
		case IMG_JPG: ext = "jpg"; type = "jpeg"; break;
		case IMG_PNG: ext = "png"; type = "png";  break;
		case IMG_ICO: ext = "ico"; type = "ico";  break;
 		case IMG_EPS: ext = "eps"; type = "eps";  break;
 		case IMG_PDF: ext = "pdf"; type = "pdf";  break;
		case IMG_BMP: ext = "bmp"; type = "bmp";  break;
		default: ext = "png"; type = "png";  break;
	}
  
	outfile = g_strdup_printf("%s%s%s%03i.%s", options2.folder, G_DIR_SEPARATOR_S,
	                          options2.file, options2.counter, ext);
	tiemu_info(_("screenshot to %s... "), outfile);

	if((options2.size == IMG_LCD) && (options2.type == IMG_BW)) 
	{
		// get pixbuf from TI memory (LCD buffer)
		pixbuf = hid_copy_lcd();
	} 
	else if((options2.size == IMG_LCD) && (options2.type == IMG_COL)) 
	{
		// get pixbuf from grayscale lcd
		pixbuf = gdk_pixbuf_copy(lcd);
	} 
	else if((options2.size == IMG_SKIN) && (options2.type == IMG_COL))
	{
		// get pixbuf from backing pixmap
		pixbuf = gdk_pixbuf_get_from_drawable(NULL, pixmap, NULL, 0, 0, 0, 0, wr.w, wr.h);
	}
	else
	{
		tiemu_warning(_("unsupported screenshot options combination, screenshot aborted."));
		return 0;
	}

	switch (options2.format)
	{
		case IMG_EPS:
			result = tiemu_screen_write_eps(outfile, pixbuf, &error);
			break;
		case IMG_PDF:
			result = tiemu_screen_write_pdf(outfile, pixbuf, &error);
			break;
		default:
			result = gdk_pixbuf_save(pixbuf, outfile, type, &error, NULL);
			break;
	}

	if(options2.clipboard)
	{
		GtkClipboard *clipboard;

		clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
		gtk_clipboard_set_image(clipboard, pixbuf);
	}

	if (result == FALSE) 
	{
		tiemu_warning(_("failed to save pixbuf file: %s: %s"), outfile, error->message);
		g_error_free(error);
	}
	g_object_unref(pixbuf);

	tiemu_info(_("done!"));
	options2.counter++;

	return 0;
}

GLADE_CB gboolean on_calc_wnd_window_state_event(GtkWidget *widget,
                                                 GdkEvent  *event,
                                                 gpointer   user_data)
{
    return FALSE;
}
