/*
 * TilEm II
 *
 * Copyright (c) 2010-2011 Thibault Duponchelle 
 * Copyright (c) 2010-2011 Benjamin Moody
 *
 * 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 3 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, see <http://www.gnu.org/licenses/>.
 */

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

#include <stdio.h>
#include <string.h>
#include <gtk/gtk.h>
#include <ticalcs.h>
#include <tilem.h>

#include "gui.h"
#include "msgbox.h"
#include "files.h"

/* Get the associated calculator key name */
static int calc_key_from_name(const TilemCalc *calc, const char *name)
{
	int i;

	for (i = 0; i < 64; i++)
		if (calc->hw.keynames[i]
		    && !strcmp(calc->hw.keynames[i], name))
			return i + 1;

	/* kludge: accept aliases for a few keys */
	for (i = 0; i < 64; i++) {
		if (!calc->hw.keynames[i])
			continue;

		if (!strcmp(name, "Matrix")
		    && !strcmp(calc->hw.keynames[i], "Apps"))
			return i + 1;
		if (!strcmp(name, "Apps")
		    && !strcmp(calc->hw.keynames[i], "AppsMenu"))
			return i + 1;
		if (!strcmp(name, "List")
		    && !strcmp(calc->hw.keynames[i], "StatEd"))
			return i + 1;
		if (!strcmp(name, "Power")
		    && !strcmp(calc->hw.keynames[i], "Expon"))
			return i + 1;
		if (!strcmp(name, "Stat")
		    && !strcmp(calc->hw.keynames[i], "Table"))
			return i + 1;
	}

	return 0;
}

/* Parse a line of the group (model) in the keybindings file */
static gboolean parse_binding(TilemKeyBinding *kb,
                              const char *pckeys, const char *tikeys,
                              const TilemCalc *calc)
{
	const char *p;
	char *s;
	int n, k;

	kb->modifiers = 0;
	kb->keysym = 0;
	kb->nscancodes = 0;
	kb->scancodes = NULL;

	/* Parse modifiers */

	while ((p = strchr(pckeys, '+'))) {
		s = g_strndup(pckeys, p - pckeys);
		g_strstrip(s);
		if (!g_ascii_strcasecmp(s, "ctrl")
		    || !g_ascii_strcasecmp(s, "control"))
			kb->modifiers |= GDK_CONTROL_MASK;
		else if (!g_ascii_strcasecmp(s, "shift"))
			kb->modifiers |= GDK_SHIFT_MASK;
		else if (!g_ascii_strcasecmp(s, "alt")
		         || !g_ascii_strcasecmp(s, "mod1"))
			kb->modifiers |= GDK_MOD1_MASK;
		else if (!g_ascii_strcasecmp(s, "mod2"))
			kb->modifiers |= GDK_MOD2_MASK;
		else if (!g_ascii_strcasecmp(s, "mod3"))
			kb->modifiers |= GDK_MOD3_MASK;
		else if (!g_ascii_strcasecmp(s, "mod4"))
			kb->modifiers |= GDK_MOD4_MASK;
		else if (!g_ascii_strcasecmp(s, "mod5"))
			kb->modifiers |= GDK_MOD5_MASK;
		else if (!g_ascii_strcasecmp(s, "lock")
		         || !g_ascii_strcasecmp(s, "capslock"))
			kb->modifiers |= GDK_LOCK_MASK;
		else {
			g_free(s);
			return FALSE;
		}
		g_free(s);
		pckeys = p + 1;
	}

	/* Parse keysym */

	s = g_strstrip(g_strdup(pckeys));
	kb->keysym = gdk_keyval_from_name(s);
	g_free(s);
	if (!kb->keysym)
		return FALSE;

	/* Parse calculator keys */

	/* FIXME: allow combinations of simultaneous keys (separated
	   by '+'); current TilemKeyBinding struct doesn't provide for
	   this */

	n = 0;
	do {
		if ((p = strchr(tikeys, ',')))
			s = g_strndup(tikeys, p - tikeys);
		else
			s = g_strdup(tikeys);
		g_strstrip(s);

		k = calc_key_from_name(calc, s);
		g_free(s);

		if (!k) {
			g_free(kb->scancodes);
			kb->scancodes = NULL;
			return FALSE;
		}

		kb->nscancodes++;
		if (kb->nscancodes >= n) {
			n = kb->nscancodes * 2;
			kb->scancodes = g_renew(byte, kb->scancodes, n);
		}
		kb->scancodes[kb->nscancodes - 1] = k;

		tikeys = (p ? p + 1 : NULL);
	} while (tikeys);

	return TRUE;
}

/* Parse a group (model) in the keybindings file */
static void parse_binding_group(TilemCalcEmulator *emu, GKeyFile *gkf,
                                const char *group, int maxdepth)
{
	gchar **keys, **groups;
	char *k, *v;
	int i, n;

	keys = g_key_file_get_keys(gkf, group, NULL, NULL);
	if (!keys) {
		printf("no bindings for %s\n", group);
		return;
	}

	for (i = 0; keys[i]; i++)
		;

	n = emu->nkeybindings;
	emu->keybindings = g_renew(TilemKeyBinding, emu->keybindings, n + i);

	for(i = 0; keys[i]; i++) {
		k = keys[i];
		if (!strcmp(k, "INHERIT"))
			continue;

		v = g_key_file_get_value(gkf, group, k, NULL);
		if (!v)
			continue;

		if (parse_binding(&emu->keybindings[n], k, v, emu->calc))
			n++;
		else
			g_printerr("syntax error in key bindings: '%s=%s'\n",
			           k, v);
		g_free(v);
	}

	emu->nkeybindings = n;

	g_strfreev(keys);

	/* Include all bindings from groups marked as INHERIT */

	if (maxdepth == 0)
		return;

	groups = g_key_file_get_string_list(gkf, group, "INHERIT",
	                                    NULL, NULL);
	for (i = 0; groups && groups[i]; i++)
		parse_binding_group(emu, gkf, groups[i], maxdepth - 1);
	g_strfreev(groups);

}

/* Init the keybindings struct and open the keybindings file */
void tilem_keybindings_init(TilemCalcEmulator *emu, const char *model)
{
	char *kfname = get_shared_file_path("keybindings.ini", NULL);
	char *dname;
	GKeyFile *gkf;
	GError *err = NULL;

	g_return_if_fail(emu != NULL);
	g_return_if_fail(emu != NULL);
	g_return_if_fail(emu->calc != NULL);

	if (kfname == NULL) {
		messagebox00(NULL, GTK_MESSAGE_ERROR,
		             "Unable to load key bindings",
		             "The file keybindings.ini could not be found."
		             "  TilEm may not have been installed correctly.");
		return;
	}

	gkf = g_key_file_new();
	if (!g_key_file_load_from_file(gkf, kfname, 0, &err)) {
		dname = g_filename_display_name(kfname);
		messagebox02(NULL, GTK_MESSAGE_ERROR,
		             "Unable to load key bindings",
		             "An error occurred while reading %s: %s",
		             dname, err->message);
		g_error_free(err);
		g_free(dname);
		g_free(kfname);
		return;
	}

	g_free(emu->keybindings);
	emu->keybindings = NULL;
	emu->nkeybindings = 0;

	parse_binding_group(emu, gkf, model, 5);

	g_key_file_free(gkf);
	g_free(kfname);
}
