/*
 * TilEm II
 *
 * Copyright (c) 2010-2011 Thibault Duponchelle
 *
 * 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 <string.h>
#include <errno.h>
#include <gtk/gtk.h>
#include <glib/gstdio.h>
#include <ticalcs.h>
#include <ticonv.h>
#include <tilem.h>
#include <tilemdb.h>
#include <scancodes.h>

#include "gui.h"
#include "disasmview.h"
#include "memmodel.h"
#include "files.h"
#include "filedlg.h"
#include "msgbox.h"
#include "fixedtreeview.h"

static GtkTreeModel* fill_varlist(TilemReceiveDialog *rcvdialog);
TilemReceiveDialog* create_receive_menu(TilemCalcEmulator *emu);

/* Columns */
enum
{
	COL_ENTRY = 0,
	COL_SLOT_STR,
	COL_NAME_STR,
	COL_TYPE_STR,
	COL_SIZE_STR,
	COL_SIZE,
  	NUM_COLS
};

#define RESPONSE_REFRESH 1

/* Prompt to overwrite a list of files. */
static gboolean prompt_overwrite(GtkWindow *win, const char *dirname,
                                 char **filenames)
{
	int i;
	char *dname;
	GString *conflicts = NULL;
	int nconflicts = 0;
	GtkWidget *dlg, *btn;
	int response;

	for (i = 0; filenames[i]; i++) {
		if (g_file_test(filenames[i], G_FILE_TEST_EXISTS)) {
			if (conflicts)
				g_string_append_c(conflicts, '\n');
			else
				conflicts = g_string_new(NULL);
			dname = g_filename_display_basename(filenames[i]);
			g_string_append(conflicts, dname);
			g_free(dname);
			nconflicts++;
		}
	}

	if (!conflicts)
		return TRUE;

	dname = g_filename_display_basename(dirname);

	dlg = gtk_message_dialog_new
		(win, GTK_DIALOG_MODAL, GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
		 (nconflicts == 1
		  ? "Replace existing file?"
		  : "Replace existing files?"));

	gtk_message_dialog_format_secondary_text
		(GTK_MESSAGE_DIALOG(dlg),
		 (nconflicts == 1
		  ? "The file \"%2$s\" already exists in \"%1$s\"."
		  "  Replacing it will overwrite its contents."
		  : "The following files already exist in \"%s\"."
		  "  Replacing them will overwrite their contents:\n%s"),
		 dname, conflicts->str);

	g_free(dname);
	g_string_free(conflicts, TRUE);

	gtk_dialog_add_button(GTK_DIALOG(dlg),
	                      GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);

	btn = gtk_button_new_with_mnemonic("_Replace");
	gtk_button_set_image(GTK_BUTTON(btn),
	                     gtk_image_new_from_stock(GTK_STOCK_SAVE,
	                                              GTK_ICON_SIZE_BUTTON));
	gtk_widget_show(btn);
	gtk_dialog_add_action_widget(GTK_DIALOG(dlg), btn,
	                             GTK_RESPONSE_ACCEPT);

	gtk_dialog_set_alternative_button_order(GTK_DIALOG(dlg),
	                                        GTK_RESPONSE_ACCEPT,
	                                        GTK_RESPONSE_CANCEL,
	                                        -1);

	response = gtk_dialog_run(GTK_DIALOG(dlg));
	gtk_widget_destroy(dlg);
	return (response == GTK_RESPONSE_ACCEPT);
}

/* #### SIGNALS CALLBACK #### */

/* Prompt to save a single file. */
static gboolean prompt_save_single(TilemReceiveDialog *rcvdialog, TilemVarEntry *tve)
{
	char *dir, *default_filename, *default_filename_r, *filename, *pattern;

	default_filename = get_default_filename(tve);
	default_filename_r = utf8_to_restricted_utf8(default_filename);
	g_free(default_filename);

	tilem_config_get("download", "receivefile_recentdir/f", &dir, NULL);	
	if (!dir) dir = g_get_current_dir();

	pattern = g_strconcat("*.", tve->file_ext, NULL);

	filename = prompt_save_file("Save File", GTK_WINDOW(rcvdialog->window),
	                            default_filename_r, dir,
	                            tve->filetype_desc, pattern,
	                            "All files", "*",
	                            NULL);
	g_free(default_filename_r);
	g_free(pattern);
	g_free(dir);

	if (!filename)
		return FALSE;

	dir = g_path_get_dirname(filename);
	tilem_config_set("download", "receivefile_recentdir/f", dir, NULL);
	g_free(dir);

	tilem_link_receive_file(rcvdialog->emu, tve, filename);
	g_free(filename);
	return TRUE;
}

/* Prompt to save a list of variables as a group file. */
static gboolean prompt_save_group(TilemReceiveDialog *rcvdialog, GList *rows)
{
	char *dir, *default_filename, *pattern_desc, *pattern, *filename, *fext;
	int tfmodel;
	gboolean can_group = TRUE;
	const char *model_str;
	GList *l;
	GtkTreePath *path;
	GtkTreeIter iter;
	TilemVarEntry *tve;
	GSList *velist = NULL;

	tilem_config_get("download", "receivefile_recentdir/f", &dir, NULL);	
	if (!dir) dir = g_get_current_dir();

	for (l = rows; l; l = l->next) {
		path = (GtkTreePath*) l->data;
		gtk_tree_model_get_iter(rcvdialog->model, &iter, path);
		gtk_tree_model_get(rcvdialog->model, &iter, COL_ENTRY, &tve, -1);
		velist = g_slist_prepend(velist, tve);
		if (!tve->can_group)
			can_group = FALSE;
	}

	velist = g_slist_reverse(velist);

	tfmodel = get_calc_model(rcvdialog->emu->calc);

	fext = g_ascii_strdown(tifiles_fext_of_group(tfmodel), -1);
	pattern = g_strconcat("*.", fext, NULL);
	default_filename = g_strdup_printf("untitled.%s",
	                                   (can_group ? fext : "tig"));
	g_free(fext);

	model_str = tifiles_model_to_string(tfmodel);
	pattern_desc = g_strdup_printf("%s group files", model_str);

	filename = prompt_save_file("Save File", GTK_WINDOW(rcvdialog->window),
	                            default_filename, dir,
	                            pattern_desc, (can_group ? pattern : ""),
	                            "TIGroup files", "*.tig",
	                            "All files", "*",
	                            NULL);

	g_free(default_filename);
	g_free(dir);
	g_free(pattern_desc);
	g_free(pattern);

	if (!filename) {
		g_slist_free(velist);
		return FALSE;
	}

	dir = g_path_get_dirname(filename);
	tilem_config_set("download",
	                 "receivefile_recentdir/f", dir,
	                 "save_as_group/b", TRUE,
	                 NULL);
	g_free(dir);

	tilem_link_receive_group(rcvdialog->emu, velist, filename);

	g_free(filename);
	g_slist_free(velist);
	return TRUE;
}

/* Prompt to save a list of files.  Input is a list of GtkTreePaths */
static gboolean prompt_save_multiple(TilemReceiveDialog *rcvdialog, GList *rows)
{
	char *dir, *dir_selected, *default_filename, *default_filename_f;
	GList *l;
	GtkTreePath *path;
	GtkTreeIter iter;
	TilemVarEntry *tve, **vars;
	char **names;
	gboolean is_81, use_group;
	int i;

	is_81 = (rcvdialog->emu->calc->hw.model_id == TILEM_CALC_TI81);
	use_group = gtk_toggle_button_get_active
		(GTK_TOGGLE_BUTTON(rcvdialog->group_rb));

	if (use_group && !is_81)
		return prompt_save_group(rcvdialog, rows);

	tilem_config_get("download", "receivefile_recentdir/f", &dir, NULL);	
	if (!dir) dir = g_get_current_dir();

	dir_selected = prompt_select_dir("Save Files to Directory",
	                                 GTK_WINDOW(rcvdialog->window),
	                                 dir);
	g_free(dir);

	if (!dir_selected)
		return FALSE;

	tilem_config_set("download",
	                 "receivefile_recentdir/f", dir_selected,
	                 "save_as_group/b", use_group,
	                 NULL);

	vars = g_new(TilemVarEntry *, g_list_length(rows) + 1);
	names = g_new(char *, g_list_length(rows) + 1);

	for (l = rows, i = 0; l; l = l->next, i++) {
		path = (GtkTreePath*) l->data;
		gtk_tree_model_get_iter(rcvdialog->model, &iter, path);
		gtk_tree_model_get(rcvdialog->model, &iter, COL_ENTRY, &tve, -1);

		vars[i] = tve;

		default_filename = get_default_filename(tve);
		default_filename_f = utf8_to_filename(default_filename);
		names[i] = g_build_filename(dir_selected,
		                            default_filename_f, NULL);
		g_free(default_filename);
		g_free(default_filename_f);
	}

	vars[i] = NULL;
	names[i] = NULL;

	if (!prompt_overwrite(GTK_WINDOW(rcvdialog->window),
	                      dir_selected, names)) {
		g_free(vars);
		g_strfreev(names);
		g_free(dir_selected);
		return FALSE;
	}

	for (i = 0; vars[i]; i++)
		tilem_link_receive_file(rcvdialog->emu, vars[i], names[i]);

	g_free(vars);
	g_strfreev(names);
	g_free(dir_selected);
	return TRUE;
}

/* Event called on Send button click. Get the selected var/app and save it. */
static gboolean prompt_save(TilemReceiveDialog *rcvdialog)
{
	TilemVarEntry *tve;		/* Variable entry */
	GtkTreeSelection* selection = NULL; /* GtkTreeSelection */
	GtkTreeModel *model;
	GtkTreeIter iter;
	GList *rows, *l;
	GtkTreePath *path;
	gboolean status;

	/* Get the selected entry */
	selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(rcvdialog->treeview));
	rows = gtk_tree_selection_get_selected_rows(selection, &model);

	if (!rows)
		return FALSE;

	if (!rows->next) {
		path = (GtkTreePath*) rows->data;
		gtk_tree_model_get_iter(model, &iter, path);
		gtk_tree_model_get(model, &iter, COL_ENTRY, &tve, -1);
		status = prompt_save_single(rcvdialog, tve);
	}
	else {
		status = prompt_save_multiple(rcvdialog, rows);
	}

	for (l = rows; l; l = l->next)
		gtk_tree_path_free(l->data);
	g_list_free(rows);
	return status;
}

/* Dialog response button clicked */
static void dialog_response(GtkDialog *dlg, gint response, gpointer data)
{
	TilemReceiveDialog* rcvdialog = (TilemReceiveDialog*) data;

	switch (response) {
	case RESPONSE_REFRESH:
		if (!rcvdialog->refresh_pending) {
			rcvdialog->refresh_pending = TRUE;
			tilem_link_get_dirlist(rcvdialog->emu);
		}
		break;

	case GTK_RESPONSE_ACCEPT:
		if (!prompt_save(rcvdialog))
			break;
	default:
		gtk_widget_hide(GTK_WIDGET(dlg));
	}
}

/* Selection changed */
static void selection_changed(GtkTreeSelection *sel, gpointer data)
{
	TilemReceiveDialog* rcvdialog = data;
	int n = gtk_tree_selection_count_selected_rows(sel);

	gtk_dialog_set_response_sensitive(GTK_DIALOG(rcvdialog->window),
	                                  GTK_RESPONSE_ACCEPT, (n > 0));
	gtk_widget_set_sensitive(rcvdialog->mode_box, (n > 1));
}

/* Row activated in tree view */
static void row_activated(G_GNUC_UNUSED GtkTreeView *treeview,
                          G_GNUC_UNUSED GtkTreePath *path,
                          G_GNUC_UNUSED GtkTreeViewColumn *col,
                          gpointer data)
{
	TilemReceiveDialog* rcvdialog = (TilemReceiveDialog*) data;
	gtk_dialog_response(GTK_DIALOG(rcvdialog->window), GTK_RESPONSE_ACCEPT);
}

/* #### WIDGET CREATION #### */

/* Create a new scrolled window with sensible default settings. */
static GtkWidget *new_scrolled_window(GtkWidget *contents)
{
        GtkWidget *sw; 
        sw = gtk_scrolled_window_new(NULL, NULL);
        gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
                                       GTK_POLICY_NEVER,
                                       GTK_POLICY_AUTOMATIC);
        gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw),
                                            GTK_SHADOW_IN);
        gtk_container_add(GTK_CONTAINER(sw), contents);
        return sw;
}

/* Create the (empty) GtkTreeView to show the vars list */
static GtkWidget *create_varlist(TilemReceiveDialog *rcvdialog)
{
	GtkCellRenderer   *renderer;
	GtkWidget         *treeview;
	GtkTreeSelection  *sel;
	GtkTreeViewColumn *c1, *c2, *c3, *c4;
	gboolean           is_81;

	g_return_val_if_fail(rcvdialog->emu != NULL, NULL);
	g_return_val_if_fail(rcvdialog->emu->calc != NULL, NULL);

	is_81 = (rcvdialog->emu->calc->hw.model_id == TILEM_CALC_TI81);

	/* Create the stack list tree view and set title invisible */
	treeview = gtk_tree_view_new();
	gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(treeview), TRUE);
	gtk_tree_view_set_headers_clickable(GTK_TREE_VIEW(treeview), TRUE);
	gtk_tree_view_set_fixed_height_mode(GTK_TREE_VIEW(treeview), TRUE);
	
	/* Allow multiple selection */
	sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
	gtk_tree_selection_set_mode(sel, GTK_SELECTION_MULTIPLE);

	/* Create the columns */
	renderer = gtk_cell_renderer_text_new();

	if (is_81) {
		c1 = gtk_tree_view_column_new_with_attributes
			("Slot", renderer, "text", COL_SLOT_STR, NULL);

		gtk_tree_view_column_set_sizing(c1, GTK_TREE_VIEW_COLUMN_FIXED);
		gtk_tree_view_column_set_sort_column_id(c1, COL_SLOT_STR);
		gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), c1);
	}

	c2 = gtk_tree_view_column_new_with_attributes
		("Name", renderer, "text", COL_NAME_STR, NULL);

	gtk_tree_view_column_set_sizing(c2, GTK_TREE_VIEW_COLUMN_FIXED);
	gtk_tree_view_column_set_sort_column_id(c2, COL_NAME_STR);
	gtk_tree_view_column_set_expand(c2, TRUE);
	gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), c2);

	if (!is_81) {
		c3 = gtk_tree_view_column_new_with_attributes
			("Type", renderer, "text", COL_TYPE_STR, NULL);
		
		gtk_tree_view_column_set_sizing(c3, GTK_TREE_VIEW_COLUMN_FIXED);
		gtk_tree_view_column_set_sort_column_id(c3, COL_TYPE_STR);
		gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), c3);
	}

	renderer = gtk_cell_renderer_text_new();
	g_object_set(renderer, "xalign", 1.0, NULL);
	c4 = gtk_tree_view_column_new_with_attributes
		("Size", renderer, "text", COL_SIZE_STR, NULL);

	gtk_tree_view_column_set_sizing(c4, GTK_TREE_VIEW_COLUMN_FIXED);
	gtk_tree_view_column_set_sort_column_id(c4, COL_SIZE);
	gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), c4);

	g_signal_connect(sel, "changed",
	                 G_CALLBACK(selection_changed), rcvdialog);

	g_signal_connect(treeview, "row-activated",
	                 G_CALLBACK(row_activated), rcvdialog);

	return treeview;
}

/* Fill the list of vars. In fact, add all vars from list to a GtkListStore */
static GtkTreeModel* fill_varlist(TilemReceiveDialog *rcvdialog)
{
	GSList *l;
	TilemVarEntry *tve;
	GtkListStore *store;
	GtkTreeIter iter;
	char *size_str;

	store = gtk_list_store_new(6,
	                           G_TYPE_POINTER,
	                           G_TYPE_STRING,
	                           G_TYPE_STRING,
	                           G_TYPE_STRING,
	                           G_TYPE_STRING,
	                           G_TYPE_INT);

	for (l = rcvdialog->vars; l; l = l->next) {
		tve = l->data;
		gtk_list_store_append(store, &iter);
#ifdef G_OS_WIN32
		size_str = g_strdup_printf("%d", tve->size);
#else
		size_str = g_strdup_printf("%'d", tve->size);
#endif
		gtk_list_store_set(store, &iter,
		                   COL_ENTRY, tve,
		                   COL_SLOT_STR, tve->slot_str,
		                   COL_NAME_STR, tve->name_str,
		                   COL_TYPE_STR, tve->type_str,
		                   COL_SIZE_STR, size_str,
		                   COL_SIZE, tve->size,
		                   -1);
		g_free(size_str);
	}

	return GTK_TREE_MODEL(store);
}

/* Create a new menu for receiving vars. */
/* Previous allocated and filled varlist is needed */
TilemReceiveDialog* tilem_receive_dialog_new(TilemCalcEmulator *emu)
{
	TilemReceiveDialog* rcvdialog = g_slice_new0(TilemReceiveDialog);
	GtkWidget *scroll, *btn, *vbox, *lbl, *rb, *vbox2;
	int defheight = 300;
	gboolean is_81;
	gboolean use_group;

	g_return_val_if_fail(emu != NULL, NULL);
	g_return_val_if_fail(emu->ewin != NULL, NULL);
	g_return_val_if_fail(emu->calc != NULL, NULL);

	rcvdialog->emu = emu;
	emu->rcvdlg = rcvdialog;

	is_81 = (emu->calc->hw.model_id == TILEM_CALC_TI81);

	rcvdialog->window = gtk_dialog_new();
	gtk_window_set_transient_for(GTK_WINDOW(rcvdialog->window),
	                             GTK_WINDOW(emu->ewin->window));

	gtk_window_set_title(GTK_WINDOW(rcvdialog->window), "Receive File");

	btn = gtk_dialog_add_button(GTK_DIALOG(rcvdialog->window),
	                            GTK_STOCK_REFRESH, RESPONSE_REFRESH);

	if (is_81)
		gtk_widget_hide(btn);

	gtk_dialog_add_button(GTK_DIALOG(rcvdialog->window),
	                      GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
	gtk_dialog_add_button(GTK_DIALOG(rcvdialog->window),
	                      GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT);

	gtk_dialog_set_default_response(GTK_DIALOG(rcvdialog->window),
	                                GTK_RESPONSE_ACCEPT);
	gtk_dialog_set_alternative_button_order(GTK_DIALOG(rcvdialog->window),
	                                        RESPONSE_REFRESH,
	                                        GTK_RESPONSE_ACCEPT,
	                                        GTK_RESPONSE_CANCEL,
	                                        -1);

	/* Set the size of the dialog */
	gtk_window_set_default_size(GTK_WINDOW(rcvdialog->window), -1, defheight);
	
	/* Create and fill tree view */
	rcvdialog->treeview = create_varlist(rcvdialog);

	/* Allow scrolling the list because we can't know how many vars the calc contains */
	scroll = new_scrolled_window(rcvdialog->treeview);

	vbox = gtk_vbox_new(FALSE, 6);
	gtk_container_set_border_width(GTK_CONTAINER(vbox), 6);
	gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(scroll), TRUE, TRUE, 0);

	rcvdialog->mode_box = gtk_hbox_new(FALSE, 6);
	lbl = gtk_label_new("Save as:");
	gtk_box_pack_start(GTK_BOX(rcvdialog->mode_box), lbl, FALSE, FALSE, 0);

	rb = gtk_radio_button_new_with_mnemonic(NULL, "S_eparate files");
	gtk_box_pack_start(GTK_BOX(rcvdialog->mode_box), rb, FALSE, FALSE, 0);
	rcvdialog->multiple_rb = rb;

	rb = gtk_radio_button_new_with_mnemonic_from_widget
		(GTK_RADIO_BUTTON(rb), "_Group file");
	gtk_box_pack_start(GTK_BOX(rcvdialog->mode_box), rb, FALSE, FALSE, 0);
	rcvdialog->group_rb = rb;

	tilem_config_get("download", "save_as_group/b=1", &use_group, NULL);
	if (use_group)
		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(rb), TRUE);

	if (is_81)
		gtk_widget_set_no_show_all(rcvdialog->mode_box, TRUE);

	gtk_box_pack_start(GTK_BOX(vbox), rcvdialog->mode_box, FALSE, FALSE, 0);
	vbox2 = gtk_dialog_get_content_area(GTK_DIALOG(rcvdialog->window));
	gtk_box_pack_start(GTK_BOX(vbox2), vbox, TRUE, TRUE, 0);

	/* Signals callback */
	g_signal_connect(rcvdialog->window, "response",
	                 G_CALLBACK(dialog_response), rcvdialog);
	g_signal_connect(rcvdialog->window, "delete-event",
	                 G_CALLBACK(gtk_widget_hide_on_delete), NULL);
	
	gtk_widget_show_all(vbox);

	return rcvdialog;
}

/* Destroy a TilemReceiveDialog */
void tilem_receive_dialog_free(TilemReceiveDialog *rcvdialog)
{
	GSList *l;

	g_return_if_fail(rcvdialog != NULL);

	gtk_widget_destroy(rcvdialog->window);

	for (l = rcvdialog->vars; l; l = l->next)
		tilem_var_entry_free(l->data);
	g_slist_free(rcvdialog->vars);

	g_slice_free(TilemReceiveDialog, rcvdialog);
}

void tilem_receive_dialog_update(TilemReceiveDialog *rcvdialog, GSList *varlist)
{
	GSList *l;

	g_return_if_fail(rcvdialog != NULL);

	rcvdialog->refresh_pending = FALSE;

	for (l = rcvdialog->vars; l; l = l->next)
		tilem_var_entry_free(l->data);
	g_slist_free(rcvdialog->vars);

	rcvdialog->vars = varlist;
	rcvdialog->model = fill_varlist(rcvdialog);
	gtk_tree_view_set_model(GTK_TREE_VIEW(rcvdialog->treeview), rcvdialog->model);

	fixed_tree_view_init(rcvdialog->treeview, 0,
	                     COL_SLOT_STR, "PrgmM ",
	                     COL_NAME_STR, "MMMMMMMMM ",
	                     COL_TYPE_STR, "MMMMMM ",
	                     COL_SIZE_STR, "00,000,000",
	                     -1);

	gtk_widget_grab_focus(rcvdialog->treeview);
	gtk_window_present(GTK_WINDOW(rcvdialog->window));
}

/* Popup the receive window */
/* This is the entry point */
void popup_receive_menu(TilemEmulatorWindow *ewin)
{
	g_return_if_fail(ewin != NULL);
	g_return_if_fail(ewin->emu != NULL);
	g_return_if_fail(ewin->emu->calc != NULL);

	if (ewin->emu->rcvdlg && ewin->emu->rcvdlg->refresh_pending)
		return;

	/* TI-81 takes no time to refresh, so do it automatically */
	if (!ewin->emu->rcvdlg
	    || ewin->emu->calc->hw.model_id == TILEM_CALC_TI81) {
		tilem_link_get_dirlist(ewin->emu);
	}
	else {
		gtk_widget_grab_focus(ewin->emu->rcvdlg->treeview);
		gtk_window_present(GTK_WINDOW(ewin->emu->rcvdlg->window));
	}
}
