/* $Id: e2p_upgrade.c 2681 2013-08-13 12:11:19Z tpgww $

Copyright (C) 2005-2013 tooar <tooar@emelfm2.net>

This file is part of emelFM2.
emelFM2 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, or (at your option)
any later version.

emelFM2 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 emelFM2; see the file GPL. If not, see http://www.gnu.org/licenses.
*/

/**
@file plugins/e2p_upgrade.c
@brief plugin for updating config files when a new emelFM2 version so requires

This file contains functions that help upgrading the default configuration
data file to the current version. Note that upgrading may also be needed for
imported config data - refer to the config plugin
*/

#include "emelfm2.h"
#include <string.h>
#include "e2_plugins.h"
#include "e2_option.h"
#include "e2_output.h"
#include "e2_dialog.h"
#include "e2_task.h"
#include "e2_complete.h"

static gboolean cancelled = FALSE;

static gchar *default_msg =
	N_("Configuration arrangements for this version %s of %s are considerably "
	"different from those of old versions. To reliably ensure access to the "
	"program's current features, it is best to start with fresh settings.\n"
	"If you proceed, the superseded configuration files in\n %s will have '.save' "
	"appended to their names.\nFeel free to delete them."
	);
#if 0
static gchar *option_msg =
	N _ ("Several default configuration settings of this version %s of %s"
	" are different from those of recent versions (see changelog).\n"
	"If you click OK, those settings will be updated where possible.\n"
	"Or else you can Cancel, and later, via the configuration dialog, manually"
	"change individual settings, or change all settings to current defaults."
	);
#endif //0

static void _e2p_upgrade_reload (gboolean read)
{
	guint i;
	gpointer *walker;
	//prevent attempts to clean non-existent backup data (dunno how the pointers get bad)
	for (i = 0, walker = options_array->pdata; i < options_array->len; i++, walker++)
	{
		E2_OptionSet *set;
		set = *walker;
		if (set->type == E2_OPTION_TYPE_TREE)
			set->ex.tree.def.tree_strings = NULL;
	}
	e2_option_clear_data ();	//clear current option values
	e2_option_default_register ();//install defaults
	e2_option_date_style (); //customise date-format option
	if (read)
		e2_option_file_read ();
}

//CHECKME assumes BGL is closed, native file only
static void _e2p_upgrade_backup (gchar *file)
{
	gchar *cfg_file = g_strdup_printf ("%s"G_DIR_SEPARATOR_S"%s",
		e2_cl_options.config_dir, file);
	gchar *local = F_FILENAME_TO_LOCALE (cfg_file);
#ifdef E2_VFS
	VPATH ddata = { local, NULL };
	if (e2_fs_access (&ddata, F_OK E2_ERR_NONE()) == 0)	//traverse link, if any
#else
	if (e2_fs_access (local, F_OK E2_ERR_NONE()) == 0)	//traverse link, if any
#endif
	{
		gchar *saved_file = g_strconcat (local, ".save", NULL);
		gdk_threads_leave ();	//downstream errors invoke local mutex locking
#ifdef E2_VFS
		VPATH sdata = { saved_file, NULL };
		e2_task_backend_rename (&ddata, &sdata);
#else
		e2_task_backend_rename (local, saved_file);
#endif
		gdk_threads_enter ();
		g_free (saved_file);
	}
	g_free (cfg_file);
	F_FREE (local, cfg_file);
}

static gboolean _e2p_upgrade_run (const gchar *command, const gchar *cfg_path)
{
	if (system (command) == 0)
		cancelled = FALSE;
	else
	{
		cancelled = TRUE;
		printd (DEBUG, "failed command '%s'", command);
		gchar *revert = g_strconcat ("mv -f ", cfg_path, ".save ", cfg_path, NULL);
		gint success = system (revert);
		if (success != 0)
			printd (DEBUG, "failed reversion command '%s'", revert);
		g_free (revert);
	}
	return !cancelled;
}
//expects BGL closed
static gint _e2p_upgrade_dialog (gchar *msg)
{
	//main button structs not created yet
	E2_Button yes_btn =
		{ _("_Apply"),
#ifdef E2_IMAGECACHE
	//FIXME gtk images not yet available
		NULL,
#else
		GTK_STOCK_YES,
#endif
		NULL, E2_BTN_DEFAULT, E2_BTN_DEFAULT, GTK_RESPONSE_YES };
	E2_Button no_btn =
		{ _("_Cancel"),
#ifdef E2_IMAGECACHE
	//FIXME gtk images not yet available
		NULL,
#else
		GTK_STOCK_NO,
#endif
		NULL, E2_BTN_DEFAULT, E2_BTN_DEFAULT, GTK_RESPONSE_NO };

	GtkWidget *dialog = e2_dialog_create (
#ifdef E2_IMAGECACHE
	//FIXME gtk images not yet available
		NULL,
#else
		GTK_STOCK_DIALOG_INFO,
#endif
		msg, _("update information"), DEFAULT_RESPONSE_CB, NULL);
	e2_dialog_show (dialog, NULL, 0,
		&yes_btn, &no_btn, NULL);
//	gint choice = e2_dialog_run_simple (dialog, NULL);
	//not a local loop, as main-window (for fake event) & options not yet available
	gint choice = gtk_dialog_run (GTK_DIALOG (dialog));
	gtk_widget_destroy (dialog);
	cancelled = (choice != GTK_RESPONSE_YES);
	return choice;
}
//#if 0
static gchar *_e2p_upgrade_get_sed (void)
{
	gchar *sed = g_find_program_in_path ("sed");
	if (sed == NULL)
	{
		//FIXME should always warn the user about this
		printd (ERROR, "can't find 'sed' so i can't upgrade the config file");
		cancelled = TRUE;
	}
	return sed;
}
//#endif
#ifdef E2_VERSIONDOCS
static void _e2p_upgrade_numbers (void)
{
	gchar *sed = _e2p_upgrade_get_sed ();
	if (sed != NULL)
	{
		gchar *cfg_file = g_build_filename (e2_cl_options.config_dir,
				default_config_file, NULL);
		gchar *local = F_FILENAME_TO_LOCALE (cfg_file);
		gchar *localtmp = e2_utils_get_tempname (local);
		gchar *command = g_strconcat ("cp -f ", local, " ", localtmp, ".save;",
			sed, " -e '1s/", app.cfgfile_version, "/"VERSION RELEASE"/'",
				 " -e '2,$s/0\\.[0-9]\\.[0-9]/"VERSION"/'",
				 " ",localtmp,".save >",local,NULL);
		if (!_e2p_upgrade_run (command, localtmp))
			printd (WARN, "failed to execute command to upgrade config version no's");
		g_free (sed);
		g_free (cfg_file);
		F_FREE (local, cfg_file);
		g_free (localtmp);
		g_free (command);
	}
	else
		cancelled = TRUE;
}
#endif

#ifdef E2_POLKIT
/**
@brief upgrade su-related strings
@return TRUE if the upgrade was done
*/
static gboolean _e2p_upgrade_su (void)
{
	gchar *sed = _e2p_upgrade_get_sed ();
	if (sed != NULL)
	{
		const gchar *prompt1 = _("Enter command:");
		const gchar *prompt2 = _("Done. Press enter ");
		//commandbar item
		gchar *oldstr1 = g_strconcat("\\|xterm\\|-e 'su -c \\\"%\\{\\(root-commands\\)@",prompt1,"\\}\\\";echo -n \\\"",prompt2,"\\\";read'",NULL);
		gchar *newstr1 = g_strconcat("|pkexec|%{(root-commands)@",prompt1,"}",NULL);
		//alias
		//2nd (.*) around "\2" so this can work (and be inserted in the replacement string)
		gchar *oldstr2 = g_strconcat("\\|xterm -e sh -c 'su -c \\\"(.*)\\\";echo -n \\\"",prompt2,"\\\";read'",NULL);
		const gchar *newstr2 = "|pkexec \\2";

		gchar *cfg_file = g_build_filename (e2_cl_options.config_dir,
			default_config_file, NULL);
		gchar *local = F_FILENAME_TO_LOCALE (cfg_file);
		gchar *command = g_strconcat ("cp -f ", local, " ", local, ".save;",
		sed,
			" -r"	//support back-referencing
			" -e \"s~(.*)",oldstr1,"~\\1",newstr1,"~\"",
			" -e \"s~(.*)",oldstr2,"~\\1",newstr2,"~\"",
			" ",local,".save >",local,NULL);
		gboolean success = _e2p_upgrade_run (command, local);
		g_free (oldstr1);
		g_free (newstr1);
		g_free (oldstr2);
		g_free (cfg_file);
		F_FREE (local, cfg_file);
		g_free (command);
		g_free (sed);
		if (success)
			return TRUE;
		printd (WARN, "failed to execute command to do su-upgrade");
	}
	cancelled = TRUE;
	return FALSE;
}
#endif

static void _e2p_upgrade_pre_0_1 (void)
{
	gchar *msg = g_strdup_printf (gettext (default_msg), VERSION, PROGNAME,
		e2_cl_options.config_dir);
	gint choice = _e2p_upgrade_dialog (msg);
	g_free (msg);

	if (choice == GTK_RESPONSE_YES)
	{
		_e2p_upgrade_backup ("config");
//		_e2p_upgrade_backup ("cache");  this one is ok to leave as is
		_e2p_upgrade_backup ("filetypes");
		_e2p_upgrade_backup ("plugins");
		_e2p_upgrade_backup ("settings");

		e2_option_clear_data ();
		e2_option_default_register ();
		e2_option_date_style (); //customise date-format option
	}
	else
		exit (1);
}

static void _e2p_upgrade_0_4_1 (void)
{
	//get rid of outdated cache flags for find plugin, which hasn't been loaded yet
	e2_cache_clean1 ("find-plugin-flags");
}

//returns TRUE if defaults are applied
static gboolean _e2p_upgrade_0_4_5 (void)
{	//one dialog only !
	gchar *msg = g_strdup_printf (gettext (default_msg), VERSION, PROGNAME,
		e2_cl_options.config_dir);
	gint choice = _e2p_upgrade_dialog (msg);
	g_free (msg);

	if (choice == GTK_RESPONSE_YES)
	{
		_e2p_upgrade_backup (default_config_file);
		_e2p_upgrade_reload (FALSE); //no re-read
		e2_option_file_write (NULL); //save updated config file
		return TRUE;
	}
	return FALSE;
}

static void _e2p_upgrade_0_5_1 (void)
{
	gchar *sed = _e2p_upgrade_get_sed ();
	if (sed != NULL)
	{
		//keybinding to add
		gchar *oldstr1 = g_strconcat("\t\t\t|<Control>i|false|",_A(7),".",_A(60),"|",NULL);
		gchar *newstr1 = g_strconcat("\t\t\t|<Control>d|false|",_A(7),".",_A(83),"|",NULL);

		gchar *cfg_file = g_build_filename (e2_cl_options.config_dir,
			default_config_file, NULL);
		gchar *local = F_FILENAME_TO_LOCALE (cfg_file);
		gchar *command = g_strconcat ("cp -f ", local, " ", local, ".save;",
		sed,
#ifndef E2_VERSIONDOCS
			" -e '1s/", app.cfgfile_version, "/", VERSION RELEASE, "/'",
#endif
			" -e '/",oldstr1,"$/a\\\n",newstr1,"'",
			" ",local,".save >",local,NULL);
		if (!_e2p_upgrade_run (command, local))
			printd (WARN, "failed to execute command to do upgrade 0.5.1");
		g_free (oldstr1);
		g_free (newstr1);
		g_free (cfg_file);
		F_FREE (local, cfg_file);
		g_free (command);
		g_free (sed);
	}
	else
		cancelled = !_e2p_upgrade_0_4_5 ();	//suggest default
}

static void _e2p_upgrade_0_5_1_1 (void)
{
	gchar *sed = _e2p_upgrade_get_sed ();
	if (sed != NULL)
	{
		//keybindings to change
		gchar *oldstr1 = g_strconcat(_A(10),".",_A(33),"|*,1",NULL);
		gchar *newstr1 = g_strconcat("!",_A(10),".",_A(33),"|1,*",NULL);
		gchar *oldstr2 = g_strconcat(_A(10),".",_A(33),"|*,0",NULL);
		gchar *newstr2 = g_strconcat("!",_A(10),".",_A(33),"|0,*",NULL);
		gchar *oldstr3 = g_strconcat(_A(14),".",_A(33),"|*,1",NULL);
		gchar *newstr3 = g_strconcat("!",_A(14),".",_A(33),"|0,*",NULL);
		gchar *oldstr4 = g_strconcat(_A(14),".",_A(33),"|*,0",NULL);
		gchar *newstr4 = g_strconcat("!",_A(14),".",_A(33),"|1,*",NULL);

		gchar *cfg_file = g_build_filename (e2_cl_options.config_dir,
			default_config_file, NULL);
		gchar *local = F_FILENAME_TO_LOCALE (cfg_file);
		gchar *command = g_strconcat ("cp -f ", local, " ", local, ".save;",
		sed,
#ifndef E2_VERSIONDOCS
			" -e '1s/", app.cfgfile_version, "/", VERSION RELEASE, "/'",
#endif
			" -e 's/",oldstr1,"/",newstr1,"/'",
			" -e 's/",oldstr2,"/",newstr2,"/'",
			" -e 's/",oldstr3,"/",newstr3,"/'",
			" -e 's/",oldstr4,"/",newstr4,"/'",
			" ",local,".save >",local,NULL);
		if (!_e2p_upgrade_run (command, local))
			printd (WARN, "failed to execute command to do upgrade 0.5.1.1");
		g_free (oldstr1);
		g_free (newstr1);
		g_free (oldstr2);
		g_free (newstr2);
		g_free (oldstr3);
		g_free (newstr3);
		g_free (oldstr4);
		g_free (newstr4);
		g_free (cfg_file);
		F_FREE (local, cfg_file);
		g_free (command);
		g_free (sed);
	}
	else
		cancelled = !_e2p_upgrade_0_4_5 ();	//suggest default
}

static void _e2p_upgrade_0_6_0 (void)
{
	//update key-shortcut translations
	E2_OptionSet *set = e2_option_get ("keybindings");
	if (set->ex.tree.synced) //this set's data processed already
	{
		GtkTreeIter iter;
		if (gtk_tree_model_get_iter_first (set->ex.tree.model, &iter))
			e2_keybinding_localise (set->ex.tree.model, &iter);
	}
	//else FIXME
}

static void _e2p_upgrade_0_7_2 (void)
{
	gchar *sed = _e2p_upgrade_get_sed ();
	if (sed != NULL)
	{
		//context menu item add
		gchar *oldstr1 = g_strconcat("\t",_("_Edit bookmarks.."),"|gtk-preferences|false|false|",_A(3),".",_C(1),"|",NULL);
		gchar *newstr1 = g_strconcat(_("_History"),"|gtk-jump-to|false|false|",_A(8),".",_A(28),"|",NULL);

		gchar *cfg_file = g_build_filename (e2_cl_options.config_dir,
			default_config_file, NULL);
		gchar *local = F_FILENAME_TO_LOCALE (cfg_file);
		gchar *command = g_strconcat ("cp -f ", local, " ", local, ".save;",
		sed,
#ifndef E2_VERSIONDOCS
			" -e '1s/", app.cfgfile_version, "/", VERSION RELEASE, "/'",
#endif
			" -e '/",oldstr1,"$/a\\\n",newstr1,"'",
			" ",local,".save >",local,NULL);
		if (!_e2p_upgrade_run (command, local))
			printd (WARN, "failed to execute command to do upgrade 0.7.2");
		g_free (oldstr1);
		g_free (newstr1);
		g_free (cfg_file);
		F_FREE (local, cfg_file);
		g_free (command);
		g_free (sed);
	}
	else
		cancelled = !_e2p_upgrade_0_4_5 ();	//suggest default
}

static void _e2p_upgrade_0_8_2 (void)
{
	gchar *sed = _e2p_upgrade_get_sed ();
	if (sed != NULL)
	{
		//context menu item add
		gchar *oldstr1 = g_strconcat("||false|false|",_A(6),".",_A(23),"|",NULL);
		gchar *newstr1 = g_strconcat("||false|false|",_A(6),".",_A(26),"|",NULL);
		gchar *cfg_file = g_build_filename (e2_cl_options.config_dir,
			default_config_file, NULL);
		gchar *local = F_FILENAME_TO_LOCALE (cfg_file);
		gchar *command = g_strconcat ("cp -f ", local, " ", local, ".save;",
		sed,
#ifndef E2_VERSIONDOCS
			" -e '1s/", app.cfgfile_version, "/", VERSION RELEASE, "/'",
#endif
			" -e '/",oldstr1,"$/a\\\n",newstr1,"'",
			" ",local,".save >",local,NULL);
		if (!_e2p_upgrade_run (command, local))
			printd (WARN, "failed to execute command to do upgrade 0.7.2");
		g_free (oldstr1);
		g_free (newstr1);
		g_free (cfg_file);
		F_FREE (local, cfg_file);
		g_free (command);
		g_free (sed);
	}
	else
		cancelled = !_e2p_upgrade_0_4_5 ();	//suggest default
}

static void _e2p_upgrade_0_9_0 (void)
{
	gchar *sed = _e2p_upgrade_get_sed ();
	if (sed != NULL)
	{
		//change & add keybindings
		//oldstr1 is duplicated, only the first needs fix, see restriction in sed command
		gchar *oldstr1 = g_strconcat("\t\t\t|<Control>Delete|false|",_A(1),".",_A(36),"|",NULL);
		//NOTE escaped \n to get a valid newline
		gchar *newstr1 = g_strconcat("\t\t\t|<Control>Delete|false|",_A(5),".",_A(36),"|\\n",
			"\t\t\t|<Shift>Insert|false|",_A(5),".",_A(32),"|",NULL);
		gchar *oldstr2 = g_strconcat("\t\t\t|<Control>k|false|",_A(5),".",_A(37),"|",NULL);
		gchar *newstr2 = g_strconcat("\t\t\t|<Alt>Delete|false|",_A(5),".",_A(44),"|",NULL);
		//see comment above re oldstr1, applies to oldstr3 too
		gchar *oldstr3 = g_strconcat("\t\t\t|<Alt>Delete|false|",_A(1),".",_A(37),"|",NULL);
		gchar *newstr3 = g_strconcat("\t\t\t|<Shift><Alt>Delete|false|",_A(5),".",_A(37),"|",NULL);
		gchar *oldstr4 = g_strconcat("\t\t\t|<Alt>Delete|false|",_A(1),".",_A(37),"|",NULL);
		gchar *newstr4 = g_strconcat("\t\t\t|<Alt>Delete|false|",_A(1),".",_A(44),"|\\n",
			"\t\t\t|<Shift><Alt>Delete|false|",_A(1),".",_A(37),"|",NULL);
#ifdef E2_MOUSECUSTOM
		//change buttonbindings
		gchar *oldstr5 = g_strconcat("\t\t\t|<Control>2|false|false|",_A(13),".",_A(54),"|",NULL);
		gchar *newstr5 = g_strconcat("\t\t\t|<Control>2|false|false|",_A(13),".",_A(67),"|",NULL);
		gchar *oldstr6 = g_strconcat("\t\t\t|<Alt>2|false|false|",_A(13),".",_A(67),"|",NULL);
		gchar *newstr6 = g_strconcat("\t\t\t|<Alt>2|false|false|",_A(13),".",_A(54),"|",NULL);
#endif
		gchar *cfg_file = g_build_filename (e2_cl_options.config_dir,
			default_config_file, NULL);
		gchar *local = F_FILENAME_TO_LOCALE (cfg_file);
		gchar *command = g_strconcat ("cp -f ", local, " ", local, ".save;",
		sed,
#ifndef E2_VERSIONDOCS
			" -e '1s/", app.cfgfile_version, "/", VERSION RELEASE, "/'",
#endif
			" -e '0,/",oldstr1,"/s/",oldstr1,"/",newstr1,"/'",
			" -e 's/",oldstr2,"/",newstr2,"/'",
			" -e '0,/",oldstr3,"/s/",oldstr3,"/",newstr3,"/'",
			" -e 's/",oldstr4,"/",newstr4,"/'",
#ifdef E2_MOUSECUSTOM
			" -e 's/",oldstr5,"/",newstr5,"/'",
			" -e 's/",oldstr6,"/",newstr6,"/'",
#endif
			" ",local,".save > ",local,NULL);
		if (!_e2p_upgrade_run (command, local))
			printd (WARN, "failed to execute command to do upgrade 0.9.0");
		g_free (oldstr1);
		g_free (newstr1);
		g_free (oldstr2);
		g_free (newstr2);
		g_free (oldstr3);
		g_free (newstr3);
		g_free (oldstr4);
		g_free (newstr4);
#ifdef E2_MOUSECUSTOM
		g_free (oldstr5);
		g_free (newstr5);
		g_free (oldstr6);
		g_free (newstr6);
#endif
		g_free (cfg_file);
		F_FREE (local, cfg_file);
		g_free (command);
		g_free (sed);
	}
	else
		cancelled = !_e2p_upgrade_0_4_5 ();	//suggest default
}

gboolean init_plugin (Plugin *p)
{
#define ANAME "uprade"
	p->signature = ANAME VERSION;
	if (p->action == NULL)
	{
		if (strcmp (app.cfgfile_version,"0.1") < 0)
			//this one's major - dump the lot and start afresh
			_e2p_upgrade_pre_0_1();
		else
		{
//NOTE ensure that config plugin uses this same definition
#define OLDEST_UPGRADE "0.4.1.3"
			if (strcmp (app.cfgfile_version,"0.5.0") < 0)
			{
				if (!_e2p_upgrade_0_4_5 ())	//suggest default
					exit(1);
				_e2p_upgrade_0_4_1 ();	//change the find-plugin ABI
			}
			else
			{
#ifdef E2_VERSIONDOCS
				_e2p_upgrade_numbers ();	//always update docs version etc
#endif
#ifdef E2_POLKIT
				//this #define can be changed at any time
				cancelled = !_e2p_upgrade_su ();
#else
				cancelled = TRUE;
#endif
				if (strcmp (app.cfgfile_version,"0.5.1") < 0)
				{
					cancelled = FALSE; //may be changed downstream
					_e2p_upgrade_0_5_1 ();
				}
				if (strcmp (app.cfgfile_version,"0.5.1.1") < 0)
				{
					cancelled = FALSE;
					_e2p_upgrade_0_5_1_1 ();
				}
				if (strcmp (app.cfgfile_version,"0.7.2") < 0)
				{
					cancelled = FALSE;
					_e2p_upgrade_0_7_2 ();
				}
				if (strcmp (app.cfgfile_version,"0.8.2") < 0)
				{
					cancelled = FALSE;
					_e2p_upgrade_0_8_2 ();
				}
				if (0) //WAIT until realease, for this >> strcmp (app.cfgfile_version,"0.9.0") < 0)
				{
					cancelled = FALSE;
					_e2p_upgrade_0_9_0 ();
				}
			}
			if (!cancelled)
				_e2p_upgrade_reload (TRUE);	//implement all updates

			//after any config-file-based updates, work on stored data ...

			if (strcmp (app.cfgfile_version,"0.6.0") < 0)
				_e2p_upgrade_0_6_0 ();

		}
		return TRUE;
	}
	return FALSE;
}

gboolean clean_plugin (Plugin *p)
{
	return TRUE;
}
