/*
 * TilEm II
 *
 * Copyright (c) 2010-2011 Thibault Duponchelle
 * Copyright (c) 2010-2012 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 <stdlib.h>
#include <gtk/gtk.h>
#include <glib.h>
#include <ticalcs.h>
#include <tilem.h>

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

/* CMD LINE OPTIONS */
static gchar* cl_romfile = NULL;
static gchar* cl_skinfile = NULL;
static gchar* cl_model = NULL;
static gchar* cl_statefile = NULL;
static gchar** cl_files_to_load = NULL;
static gboolean cl_skinless_flag = FALSE;
static gboolean cl_reset_flag = FALSE;
static gchar* cl_getvar = NULL;
static gchar* cl_macro_to_run = NULL;
static gboolean cl_debug_flag = FALSE;
static gboolean cl_normalspeed_flag = FALSE;
static gboolean cl_fullspeed_flag = FALSE;


static GOptionEntry entries[] =
{
	{ "rom", 'r', 0, G_OPTION_ARG_FILENAME, &cl_romfile, "The rom file to run", "FILE" },
	{ "skin", 'k', 0, G_OPTION_ARG_FILENAME, &cl_skinfile, "The skin file to use", "FILE" },
	{ "model", 'm', 0, G_OPTION_ARG_STRING, &cl_model, "The model to use", "NAME" },
	{ "state-file", 's', 0, G_OPTION_ARG_FILENAME, &cl_statefile, "The state-file to use", "FILE" },
	{ "without-skin", 'l', 0, G_OPTION_ARG_NONE, &cl_skinless_flag, "Start in skinless mode", NULL },
	{ "reset", 0, 0, G_OPTION_ARG_NONE, &cl_reset_flag, "Reset the calc at startup", NULL },
	{ "get-var", 0, 0, G_OPTION_ARG_STRING, &cl_getvar, "Get a var at startup", "FILE" },
	{ "play-macro", 'p', 0, G_OPTION_ARG_FILENAME, &cl_macro_to_run, "Run this macro at startup", "FILE" },
	{ "debug", 'd', 0, G_OPTION_ARG_NONE, &cl_debug_flag, "Launch debugger", NULL },
	{ "normal-speed", 0, 0, G_OPTION_ARG_NONE, &cl_normalspeed_flag, "Run at normal speed", NULL },
	{ "full-speed", 0, 0, G_OPTION_ARG_NONE, &cl_fullspeed_flag, "Run at maximum speed", NULL },
	{ G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &cl_files_to_load, NULL, "FILE" },
	{ 0, 0, 0, 0, 0, 0, 0 }
};


/* #########  MAIN  ######### */

/* Order of preference for automatic model selection. */
static const char model_search_order[] =
	{ TILEM_CALC_TI81,
	  TILEM_CALC_TI73,
	  TILEM_CALC_TI82,
	  TILEM_CALC_TI83,
	  TILEM_CALC_TI76,
	  TILEM_CALC_TI84P_SE,
	  TILEM_CALC_TI84P,
	  TILEM_CALC_TI83P_SE,
	  TILEM_CALC_TI83P,
	  TILEM_CALC_TI84P_NSPIRE,
	  TILEM_CALC_TI85,
	  TILEM_CALC_TI86, 0 };

/* Check if given calc model should be used for these file types. */
static gboolean check_file_types(int calc_model,
                                 const int *file_models,
                                 int nfiles)
{
	/* Only choose a calc model if it supports all of the given
	   file types, and at least one of the files is of the calc's
	   "preferred" type.  This means if we have a mixture of 82Ps
	   and 83Ps, we can use either a TI-83 or TI-76.fr ROM image,
	   but not a TI-83 Plus. */

	gboolean preferred = FALSE;
	int i;

	calc_model = model_to_base_model(calc_model);

	for (i = 0; i < nfiles; i++) {
		if (file_models[i] == calc_model)
			preferred = TRUE;
		else if (!model_supports_file(calc_model, file_models[i]))
			return FALSE;
	}

	return preferred;
}

static void load_initial_rom(TilemCalcEmulator *emu,
                             const char *cmdline_rom_name,
                             const char *cmdline_state_name,
                             char **cmdline_files,
                             int model)
{
	GError *err = NULL;
	char *modelname;
	int nfiles, *file_models, i;

	/* If a ROM file is specified on the command line, use that
	   (and no other) */

	if (cmdline_rom_name) {
		if (tilem_calc_emulator_load_state(emu, cmdline_rom_name,
		                                   cmdline_state_name,
		                                   model, &err))
			return;
		else if (!err)
			exit(0);
		else {
			g_printerr("%s\n", err->message);
			exit(1);
		}
	}

	/* Choose model by file names */

	if (!model && cmdline_files) {
		nfiles = g_strv_length(cmdline_files);
		file_models = g_new(int, nfiles);

		/* determine model for each filename */
		for (i = 0; i < nfiles; i++)
			file_models[i] = file_to_model(cmdline_files[i]);

		/* iterate over all known models... */
		for (i = 0; model_search_order[i]; i++) {
			model = model_search_order[i];

			/* check if this model supports the named files */
			if (!check_file_types(model, file_models, nfiles))
				continue;

			/* try to load model, but no error message if
			   no ROM is present in config */
			if (tilem_calc_emulator_load_state(emu, NULL, NULL,
			                                   model, &err)) {
				g_free(file_models);
				return;
			}
			else if (!err)
				exit(0);
			else if (!g_error_matches(err, TILEM_EMULATOR_ERROR,
			                          TILEM_EMULATOR_ERROR_NO_ROM)) {
				messagebox01(NULL, GTK_MESSAGE_ERROR,
				             "Unable to load calculator state",
				             "%s", err->message);
			}
			g_clear_error(&err);
		}

		g_free(file_models);
		model = 0;
	}

	/* If no model specified on command line (either explicitly or
	   implicitly), then choose the most recently used model */

	if (!model && !cmdline_files) {
		tilem_config_get("recent", "last_model/s", &modelname, NULL);
		if (modelname)
			model = name_to_model(modelname);
	}

	/* Try to load the most recently used ROM for chosen model */

	if (model) {
		if (tilem_calc_emulator_load_state(emu, NULL, NULL,
		                                   model, &err))
			return;
		else if (!err)
			exit(0);
		else {
			messagebox01(NULL, GTK_MESSAGE_ERROR,
			             "Unable to load calculator state",
			             "%s", err->message);
			g_clear_error(&err);
		}
	}

	/* Prompt user for a ROM file */

	while (!emu->calc) {
		if (!tilem_calc_emulator_prompt_open_rom(emu))
			exit(0);
	}
}

int main(int argc, char **argv)
{
	TilemCalcEmulator* emu;
	char *menurc_path;
	GOptionContext *context;
	GError *error = NULL;
	int model = 0;

	g_thread_init(NULL);
	gtk_init(&argc, &argv);
	set_program_path(argv[0]);
	g_set_application_name("TilEm");

	menurc_path = get_shared_file_path("menurc", NULL);
	if (menurc_path)
		gtk_accel_map_load(menurc_path);
	g_free(menurc_path);

	init_custom_icons();
	gtk_window_set_default_icon_name("tilem");

	emu = tilem_calc_emulator_new();

	context = g_option_context_new(NULL);
	g_option_context_add_main_entries(context, entries, NULL);
	g_option_context_add_group(context, gtk_get_option_group(TRUE));
	if (!g_option_context_parse(context, &argc, &argv, &error))
	{
		g_printerr("%s: %s\n", g_get_prgname(), error->message);
		exit (1);
	}

	if (cl_model) {
		model = name_to_model(cl_model);
		if (!model) {
			g_printerr("%s: unknown model %s\n",
			           g_get_prgname(), cl_model);
			return 1;
		}
	}

	load_initial_rom(emu, cl_romfile, cl_statefile, cl_files_to_load, model);

	emu->ewin = tilem_emulator_window_new(emu);

	if (cl_skinless_flag)
		tilem_emulator_window_set_skin_disabled(emu->ewin, TRUE);
	else if (cl_skinfile) {
		tilem_emulator_window_set_skin(emu->ewin, cl_skinfile);
		tilem_emulator_window_set_skin_disabled(emu->ewin, FALSE);
	}

	gtk_widget_show(emu->ewin->window);

	ticables_library_init();
	tifiles_library_init();
	ticalcs_library_init();

	if (cl_reset_flag)
		tilem_calc_emulator_reset(emu);

	if (cl_fullspeed_flag)
		tilem_calc_emulator_set_limit_speed(emu, FALSE);
	else if (cl_normalspeed_flag)
		tilem_calc_emulator_set_limit_speed(emu, TRUE);

	if (cl_files_to_load)
		load_files_cmdline(emu->ewin, cl_files_to_load);
	if (cl_macro_to_run)
		tilem_macro_load(emu, cl_macro_to_run);
	if (cl_getvar)
		tilem_link_receive_matching(emu, cl_getvar, ".");

	if (cl_debug_flag)
		launch_debugger(emu->ewin);
	else
		tilem_calc_emulator_run(emu);

	g_signal_connect(emu->ewin->window, "destroy",
	                 G_CALLBACK(gtk_main_quit), NULL);

	gtk_main();

	tilem_calc_emulator_pause(emu);

	tilem_emulator_window_free(emu->ewin);
	tilem_calc_emulator_free(emu);

	menurc_path = get_config_file_path("menurc", NULL);
	gtk_accel_map_save(menurc_path);
	g_free(menurc_path);

	ticables_library_exit();
	tifiles_library_exit();
	ticalcs_library_exit();

	return 0;
}
