/***************************************************************************
                          options.c  -  description
                             -------------------
    begin                : Sun Mar 16 2003
    copyright            : (C) 2003 by Axel C, Tim-Philipp Müller
    email                : axel at banzais dot org, t.i.m at orange dot net
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

#include <glib.h>

#include "icons.h"
#include "linkhandler.h"
#include "options.h"
#include "stats.h"

#ifdef G_OS_UNIX
# include <limits.h>
# include <unistd.h>
# ifdef HAVE_SYS_SYSLIMITS_H
#  include <sys/syslimits.h>
# endif
#endif

#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>

/* Mingw is missing declaration of NAME_MAX */
/* so here is small hack */
#ifdef G_OS_WIN32
# ifndef NAME_MAX
#  define NAME_MAX FILENAME_MAX
# endif
#endif


typedef union
{
   gint     def_int;
/* gfloat   def_float; */
   gchar   *def_str;
} DefaultValue;


typedef struct
{
	gchar        *name;
	GType         type;
	OptNum        num;
	gpointer      val;
	DefaultValue  def;
	GList        *notify_handlers;
} GuiOption;



#define add_str_option(name,index,defaultvalue)    { name, G_TYPE_STRING,  index, NULL, {def_str: defaultvalue }, NULL }

#define add_int_option(name,index,defaultvalue)    { name, G_TYPE_INT,     index, NULL, {def_int: defaultvalue }, NULL }

#define add_bool_option(name,index,defaultvalue)   { name, G_TYPE_BOOLEAN, index, NULL, {def_int: defaultvalue }, NULL }



static GuiOption  options[] =
{
	add_bool_option ("downloads_verify_cancel", OPT_GUI_DOWNLOADS_VERIFY_CANCEL, TRUE),

	add_bool_option ("verify_exit", OPT_GUI_VERIFY_EXIT, TRUE),

	add_bool_option ("shutdown_core_on_exit", OPT_GUI_SHUTDOWN_CORE_ON_EXIT, FALSE),

	add_bool_option ("connect_dialog_make_values_default", OPT_GUI_CONNECT_DIALOG_MAKE_VALUES_DEFAULT, TRUE),

	add_bool_option ("spawn_core_on_startup_if_needed", OPT_GUI_SPAWN_CORE_ON_STARTUP_IF_NEEDED, FALSE),

	add_bool_option ("auto_connect_to_core_on_startup", OPT_GUI_AUTO_CONNECT_TO_CORE_ON_STARTUP, FALSE),

	add_bool_option ("search_show_own_files", OPT_GUI_SEARCH_SHOW_OWN_FILES, FALSE),

	add_int_option ("servers_rotate_interval", OPT_GUI_SERVERS_ROTATE_INTERVAL, 0),

	add_bool_option ("servers_fetch_new_list_when_too_few", OPT_GUI_SERVERS_FETCH_NEW_LIST_WHEN_TOO_FEW, TRUE),

	add_bool_option ("servers_clear_list_before_fetching_new_list", OPT_GUI_SERVERS_CLEAR_LIST_BEFORE_FETCHING_NEW_LIST, FALSE),

	add_bool_option ("download_ed2k_link_immediately", OPT_GUI_DOWNLOAD_ED2K_LINK_IMMEDIATELY, TRUE),

	add_bool_option ("restart_crashed_core", OPT_GUI_RESTART_CRASHED_CORE, FALSE),

	add_bool_option ("servers_disconnect_if_firewalled", OPT_GUI_SERVERS_DISCONNECT_IF_FIREWALLED, FALSE),

	add_bool_option ("downloads_hide_junk", OPT_GUI_DOWNLOADS_HIDE_JUNK, FALSE),

	add_bool_option ("downloads_keep_extension_when_hiding_junk", OPT_GUI_DOWNLOADS_KEEP_EXTENSION_WHEN_HIDING_JUNK, TRUE),

	add_bool_option ("downloads_show_transfered_in_percent", OPT_GUI_DOWNLOADS_SHOW_TRANSFERED_IN_PERCENT, FALSE),

	add_bool_option ("ignore_double_clicks", OPT_GUI_IGNORE_DOUBLE_CLICKS, FALSE),

	add_bool_option ("search_show_blacklisted_items", OPT_GUI_SEARCH_SHOW_BLACKLISTED_ITEMS, FALSE),

	add_bool_option ("servers_ignore_queue_deadlocks", OPT_GUI_SERVERS_IGNORE_QUEUE_DEADLOCKS, TRUE),

	add_bool_option ("no_green", OPT_GUI_NO_GREEN, FALSE),

	add_bool_option ("slow_gui_core_connection", OPT_GUI_SLOW_GUI_CORE_CONNECTION, FALSE),

	add_bool_option ("downloads_no_insuff_disk_space_pause", OPT_GUI_DOWNLOADS_NO_INSUFFICIENT_DISK_SPACE_PAUSE, FALSE),

	add_int_option ("status_window_clear_interval", OPT_GUI_STATUS_WINDOW_CLEAR_INTERVAL, 0),

	add_bool_option ("log_have_timestamp", OPT_GUI_LOG_HAVE_TIMESTAMP, TRUE),

	add_bool_option ("no_links_to_logfile", OPT_GUI_NO_LINKS_TO_LOGFILE, FALSE),

	add_bool_option ("downloads_blue_progressbar", OPT_GUI_DOWNLOADS_BLUE_PROGRESSBAR, FALSE),

	add_bool_option ("downloads_blacklist_on_download", OPT_GUI_DOWNLOADS_BLACKLIST_ON_DOWNLOAD, FALSE),

	add_bool_option ("downloads_no_active_download_cancel", OPT_GUI_NO_ACTIVE_DOWNLOAD_CANCEL, TRUE),

	add_bool_option ("no_app_icon", OPT_GUI_NO_APP_ICON, FALSE),

	add_bool_option ("downloads_unselect_all_after_action", OPT_GUI_DOWNLOADS_UNSELECT_ALL_AFTER_ACTION, FALSE),

	add_bool_option ("downloads_single_selection_mode", OPT_GUI_DOWNLOADS_SINGLE_SELECTION_MODE, FALSE),

	add_bool_option ("servers_unselect_all_after_action", OPT_GUI_SERVERS_UNSELECT_ALL_AFTER_ACTION, FALSE),

	add_bool_option ("servers_single_selection_mode", OPT_GUI_SERVERS_SINGLE_SELECTION_MODE, FALSE),

	add_int_option ("search_notebook_tab_pos", OPT_GUI_SEARCH_NOTEBOOK_TAB_POS, GTK_POS_LEFT ),

	add_int_option ("main_notebook_tab_pos", OPT_GUI_MAIN_NOTEBOOK_TAB_POS, GTK_POS_TOP ),

	add_bool_option ("main_notebook_hide_tab_text_left_right", OPT_GUI_MAIN_NOTEBOOK_HIDE_TAB_TEXT_LEFT_RIGHT, TRUE),

	add_int_option ("linkhandler_action_if_no_gui_running", OPT_GUI_LINKHANDLER_ACTION_IF_NO_GUI_RUNNING, LINKHANDLER_IF_NO_GUI_ASK ),

	add_bool_option ("linkhandler_add_paused_default", OPT_GUI_LINKHANDLER_ADD_PAUSED_DEFAULT, FALSE ),

	add_str_option ("default_core_host", OPT_GUI_DEFAULT_CORE_HOST, "localhost"),

	add_int_option ("default_core_port", OPT_GUI_DEFAULT_CORE_PORT, 4663),

	add_str_option ("default_core_user", OPT_GUI_DEFAULT_CORE_USER, ""),

	add_str_option ("default_core_pass", OPT_GUI_DEFAULT_CORE_PASS, ""),

	add_str_option ("default_core_binary_path", OPT_GUI_DEFAULT_CORE_BINARY_PATH, ""),

	add_str_option ("default_core_cookie", OPT_GUI_DEFAULT_CORE_COOKIE, ""),
	
	add_bool_option ("default_remote_connect", OPT_GUI_DEFAULT_REMOTE_CONNECT, FALSE),
	
	add_str_option ("serverlist_url_1", OPT_GUI_SERVERLIST_URL_1, "http://www.edonkey.com/server.met"),

	add_str_option ("serverlist_url_2", OPT_GUI_SERVERLIST_URL_2, "http://www.edonkey.com/server.met"),

	add_bool_option ("changes_from_040_to_050_shown", OPT_GUI_CHANGES_FROM_040_TO_050_SHOWN, FALSE),

	add_int_option ("downloads_min_free_disk_space", OPT_GUI_DOWNLOADS_MIN_FREE_DISKSPACE, 3),

	add_bool_option ("servers_auto_remove_invalid", OPT_GUI_SERVERS_AUTO_REMOVE_INVALID, TRUE),

	add_str_option ("exec_on_complete_command", OPT_GUI_EXEC_ON_COMPLETE_COMMAND, ""),

	add_str_option ("icon_theme", OPT_GUI_ICON_THEME, "default"),

	add_int_option ("max_logfile_size", OPT_GUI_MAX_LOGFILE_SIZE, 1000),

	add_bool_option ("toolbar_show_search_options", OPT_GUI_TOOLBAR_SHOW_SEARCH_OPTIONS, FALSE),

	add_str_option ("column_widths_downloads",    OPT_GUI_COLUMN_WIDTHS_DOWNLOADS,    "200;80;80;100;70;70;70;70;70;70;70;70;70;70;70;70"),
	add_str_option ("column_widths_uploads",      OPT_GUI_COLUMN_WIDTHS_UPLOADS,      "280;65;200"),
	add_str_option ("column_widths_upload_stats", OPT_GUI_COLUMN_WIDTHS_UPLOAD_STATS, "90;90;500;220"),
	add_str_option ("column_widths_search",       OPT_GUI_COLUMN_WIDTHS_SEARCH,       "30;130;70;70;70;70;70;70;70;70;70;70;70;70;70;70"),
	add_str_option ("column_widths_servers",      OPT_GUI_COLUMN_WIDTHS_SERVERS,      "250;120;90;50;50;50;50;50;50;50;50;0;0;0;0;0"),
	add_str_option ("column_widths_shared_files", OPT_GUI_COLUMN_WIDTHS_SHARED_FILES, "250;90;70;90;70"),

	add_int_option ("sort_type_downloads",    OPT_GUI_SORT_TYPE_DOWNLOADS,    GTK_SORT_DESCENDING),
	add_int_option ("sort_type_uploads",      OPT_GUI_SORT_TYPE_UPLOADS,      GTK_SORT_DESCENDING),
	add_int_option ("sort_type_upload_stats", OPT_GUI_SORT_TYPE_UPLOAD_STATS, GTK_SORT_DESCENDING),
	add_int_option ("sort_type_search",       OPT_GUI_SORT_TYPE_SEARCH,       GTK_SORT_DESCENDING),
	add_int_option ("sort_type_servers",      OPT_GUI_SORT_TYPE_SERVERS,      GTK_SORT_DESCENDING),
	add_int_option ("sort_type_shared_files", OPT_GUI_SORT_TYPE_SHARED_FILES, GTK_SORT_DESCENDING),
	add_int_option ("sort_type_shared_dirs",  OPT_GUI_SORT_TYPE_SHARED_DIRS,  GTK_SORT_DESCENDING),

	add_int_option ("sort_col_downloads",     OPT_GUI_SORT_COL_DOWNLOADS,    0),
	add_int_option ("sort_col_uploads",       OPT_GUI_SORT_COL_UPLOADS,      0),
	add_int_option ("sort_col_upload_stats",  OPT_GUI_SORT_COL_UPLOAD_STATS, 0),
	add_int_option ("sort_col_search",        OPT_GUI_SORT_COL_SEARCH,       0),
	add_int_option ("sort_col_servers",       OPT_GUI_SORT_COL_SERVERS,      0),
	add_int_option ("sort_col_shared_files",  OPT_GUI_SORT_COL_SHARED_FILES, 0),
	add_int_option ("sort_col_shared_dirs",   OPT_GUI_SORT_COL_SHARED_DIRS,  0),

	add_int_option ("pane_pos_uploads_page",  OPT_GUI_PANE_POS_UPLOADS_PAGE, 300),
	add_int_option ("pane_pos_shared_page",   OPT_GUI_PANE_POS_SHARED_PAGE,  200),

	add_int_option ("window_pos_x",           OPT_GUI_WINDOW_POS_X,   16),
	add_int_option ("window_pos_y",           OPT_GUI_WINDOW_POS_Y,   16),
	add_bool_option("window_pos_allow_negative_values", OPT_GUI_WINDOW_POS_ALLOW_NEGATIVE_VALUES, TRUE),
	add_int_option ("window_width",           OPT_GUI_WINDOW_WIDTH,  640),
	add_int_option ("window_height",          OPT_GUI_WINDOW_HEIGHT, 480),

	add_bool_option("window_save_pos_and_size", OPT_GUI_WINDOW_SAVE_POS_AND_SIZE, TRUE),

	add_int_option ("stats_timeline_active",    OPT_GUI_STATS_TIMELINE_ACTIVE, STATS_TIMELINE_LAST_3MINS),
	add_int_option ("stats_timeline_mode",      OPT_GUI_STATS_TIMELINE_MODE,   STATS_TIMELINE_DISPLAYMODE_BARS),
	add_int_option ("stats_up_wrong",           OPT_GUI_STATS_UP_WRONG,   1024),
	add_int_option ("stats_up_crop",            OPT_GUI_STATS_UP_CROP,     150),
	add_int_option ("stats_down_wrong",         OPT_GUI_STATS_DOWN_WRONG, 2048),
	add_int_option ("stats_down_crop",          OPT_GUI_STATS_DOWN_CROP,   150),

	add_int_option ("max_connections_seen",     OPT_GUI_MAX_CONNECTIONS_SEEN,0),

	add_bool_option ("downloads_filenames_bold", OPT_GUI_DOWNLOADS_FILENAMES_BOLD, TRUE),
	add_bool_option ("search_filenames_bold",    OPT_GUI_SEARCH_FILENAMES_BOLD,    TRUE),

	add_bool_option ("set_rules_hint_on_lists",   OPT_GUI_SET_RULES_HINT_ON_LISTS,  TRUE),

	add_bool_option ("show_icons_in_shared_files_list",  OPT_GUI_SHOW_ICONS_IN_SHARED_FILES_LIST,  TRUE),
	add_bool_option ("show_icons_in_search_list",        OPT_GUI_SHOW_ICONS_IN_SEARCH_LIST,        TRUE),

	add_bool_option ("use_applet_if_supported",          OPT_GUI_USE_APPLET_IF_SUPPORTED,          TRUE),

	add_int_option  ("icon_scaling_factor",              OPT_GUI_ICON_SCALING_FACTOR,              100),

	add_bool_option ("resume_a_paused_download_on_complete", OPT_GUI_RESUME_ONE_PAUSED_DL_ON_COMPLETE, FALSE),

	add_bool_option ("remote_core_override_temp_with_locally_mounted_temp", OPT_GUI_OVERRIDE_TEMP, FALSE),

	add_str_option  ("remote_core_override_temp_locally_mounted_temp_path", OPT_GUI_OVERRIDE_TEMP_PATH, ""),
};


/* internal gui options (not saved) */

guint               option_gui_instance = 0;   /* set via the command line option '-n' */

static gchar       *prefpath = NULL;


/* functions */

static void        opt_read (void);

static void        opt_reset_to_default (OptNum i);

static void        opt_fill_defaults (void);

static void        opt_init_prefpath (void);

static void        opt_init_prefpath_if_not_yet_set (void);

static void        notify_change (GuiOption *option);


/******************************************************************************
 *
 *   opt_correct_string_val
 *
 *   Add '\' after each '\' on Windows
 *
 ***/

#ifdef G_OS_UNIX
# define opt_correct_string_val(s)  g_strdup(s)
#else

static gchar*
opt_correct_string_val (const gchar *pval)
{
	gint     i, j, len;
	gchar	  *val;

	if (!pval)
		return NULL;

	len = strlen(pval);

	/* every char needs escaping = worst case = len*2 */
	val = g_new (gchar, (len*2)+1);

	for ( i = 0, j = 0;   i <= len;  i++,j++)
	{
		val[j] = pval[i];

		if (pval[i] == '\\')
			val[++j] = '\\';
	}

	return val;
}

#endif

/******************************************************************************
 *
 *   opt_write_to_disk
 *
 ***/


void
opt_write_to_disk (void)
{
	const gchar *filename;
	FILE        *out;
	gint         i;

	filename = opt_get_opt_filename("options");

	out = fopen (filename, "w");
	g_return_if_fail ( out != NULL);

#ifdef G_OS_UNIX
	fchmod (fileno(out), (mode_t) S_IRUSR|S_IWUSR);
#endif

	fprintf (out, "\n# ed2k-gtk-gui options file\n\n");

	for (i = 0; i < OPT_NUM_OPTIONS; i++)
	{
		GuiOption	*p = &options[i];

		switch (p->type)
		{
			case G_TYPE_BOOLEAN:
			{
				fprintf (out, "%s = %s\n", p->name, ( GPOINTER_TO_INT(p->val) == 0 ) ? "false" : "true");
				break;
			}

			case G_TYPE_INT:
			{
				fprintf (out, "%s = %d\n", p->name, GPOINTER_TO_INT(p->val));
				break;
			}

			case G_TYPE_STRING:
			{
				gchar	*val;

				val = opt_correct_string_val((const gchar*)p->val);
				fprintf (out, "%s = \"%s\"\n", p->name, val ? val : "");

				G_FREE(val);
				break;
			}
		}

	}

	fprintf (out, "\n# fini\n\n");
	fclose(out);
}


/******************************************************************************
 *
 *   opt_reset_to_default
 *
 ***/

static void
opt_reset_to_default (OptNum i)
{
	GuiOption	*p = &options[i];

/*	g_print (" p->name = %s   p->num = %u   i = %u\n", p->name, p->num, i); */

	g_assert ( p->num == i );	/* otherwise options added in wrong order */

	switch (p->type)
	{
		case G_TYPE_BOOLEAN:
		case G_TYPE_INT:
		{
			p->val = GINT_TO_POINTER (p->def.def_int);

			break;
		}

		case G_TYPE_STRING:
		{
			G_FREE(p->val);
			p->val = g_strdup (p->def.def_str);

			break;
		}
	}
}

/******************************************************************************
 *
 *   opt_notify
 *
 *   Register a callback function that is called whenever
 *    the option specified is changed
 *
 ******************************************************************************/

void
opt_notify (OptNum opt, GuiOptNotifyFunc func)
{
	GuiOption	*p;

	g_return_if_fail ( opt < OPT_NUM_OPTIONS );
	g_return_if_fail ( func != NULL );

	p = &options[opt];

	p->notify_handlers = g_list_append(p->notify_handlers, func);
}


/******************************************************************************
 *
 *   notify_change
 *
 *   Calls all registered callback functions to
 *    signal that the option specified has changed
 *
 ******************************************************************************/

static void
notify_change (GuiOption *opt)
{
	GList *node;

	g_return_if_fail ( opt != NULL );

	for (node = opt->notify_handlers;  node != NULL;  node = node->next)
	{
		GuiOptNotifyFunc nfunc = (GuiOptNotifyFunc) node->data;

		nfunc(opt->num);
	}
}

/******************************************************************************
 *
 *   opt_fill_defaults
 *
 ***/

static void
opt_fill_defaults (void)
{
	gint	i;

	for (i = 0; i < OPT_NUM_OPTIONS; i++)
		opt_reset_to_default(i);
}


/******************************************************************************
 *
 *   opt_read_get_next_symbol
 *
 *   Returns a pointer to the GuiOption of the next symbol in the input file,
 *    or NULL on EOF
 *
 ******************************************************************************/

static GuiOption *
opt_read_get_next_symbol (GScanner *scanner)
{
	GTokenValue   curval;
	GTokenType    type;

	type = g_scanner_get_next_token (scanner);

	while (type != G_TOKEN_SYMBOL && type != G_TOKEN_EOF)
	{
		g_scanner_warn (scanner, "parse error (ignored, trying to find next valid symbol)\n");

		type = g_scanner_get_next_token (scanner);
	}

	if (type == G_TOKEN_EOF)
		return NULL;


	curval = g_scanner_cur_value (scanner);

	return (GuiOption*) curval.v_symbol;
}


/******************************************************************************
 *
 *   opt_read
 *
 ***/

static void
opt_read(void)
{

	const gchar    *filename;
	GTokenType      type;
	GuiOption      *opt;
	GScanner       *scanner;
	gboolean        negative_sign;    /* did we come across a negative sign right before an integer token? */
	guint           nerrors = 0;
	guint           nopts = 0;
	guint           scopeid;
	gint            fd, i;

	opt_fill_defaults();

	filename = opt_get_opt_filename("options");

	fd = open (filename, O_RDONLY);

	if ( fd <= 0 )
		return;

	scanner = g_scanner_new (NULL);

	scopeid = (guint) g_random_int();

	(void) g_scanner_set_scope(scanner, scopeid);

	/* Tell scanner about all known symbols (we'll just skip unknown ones) */
	for (i = 0; i < OPT_NUM_OPTIONS; ++i)
	{
		g_scanner_scope_add_symbol(scanner, scopeid, options[i].name, &options[i]);
	}

	g_scanner_input_file (scanner, fd);

	scanner->input_name = filename;

	while ((opt = opt_read_get_next_symbol(scanner)))
	{
		GTokenValue newval;

		type = g_scanner_get_next_token (scanner);
		if (type != G_TOKEN_EQUAL_SIGN)
		{
			g_scanner_error (scanner, "syntax error after '%s' (expected equal sign '=')\n", opt->name);
			nerrors++;
			continue;
		}

		type = g_scanner_get_next_token (scanner);

		/* if minus sign (ie. negative integer), proceed to integer */
		if (type == '-')
		{
			type = g_scanner_get_next_token (scanner);
			negative_sign = TRUE;
		}
		else
		{
			negative_sign = FALSE;
		}

		newval = g_scanner_cur_value (scanner);

		switch (type)
		{
			case G_TOKEN_INT:
			{
				if ((opt->type != G_TYPE_INT) && (opt->type != G_TYPE_BOOLEAN))
				{
					g_scanner_error (scanner, "integer assignment of non-integer symbol '%s'\n", opt->name);
					nerrors++;
					continue;
				}

				if (opt->type == G_TYPE_BOOLEAN)
				{
					if ((newval.v_int < 0) || (newval.v_int > 1))
					{
						g_scanner_error (scanner, "non-boolean integer assignment of boolean symbol '%s' (use 1/0 or true/false)\n", opt->name);
						nerrors++;
						continue;
					}

					opt->val = (newval.v_int == 0) ? GINT_TO_POINTER (FALSE) : GINT_TO_POINTER (TRUE);
				}
				else
				{
					if (negative_sign)
						opt->val = GINT_TO_POINTER (newval.v_int * (-1));
					else
						opt->val = GINT_TO_POINTER (newval.v_int);
				}

				notify_change(opt);

				nopts++;
			}
			break;

			case G_TOKEN_STRING:
			{
				if (opt->type != G_TYPE_STRING)
				{
					g_scanner_error (scanner, "string assignment of non-string symbol '%s'\n", opt->name);
					nerrors++;
					continue;
				}

				G_FREE(opt->val);
				opt->val = g_strdup (newval.v_string);

				notify_change(opt);

				nopts++;
			}
			break;


			case G_TOKEN_IDENTIFIER:
			{
				if (opt->type != G_TYPE_BOOLEAN)
				{
					g_scanner_error (scanner, "weird identifier value '%s' assigned to '%s'\n", (newval.v_identifier) ? newval.v_identifier : "(null)" , opt->name);
					nerrors++;
					break;
				}

				if (strcmp (newval.v_identifier, "false") == 0)
					opt->val = GINT_TO_POINTER (FALSE);

				else if (strcmp(newval.v_identifier, "true") == 0)
					opt->val = GINT_TO_POINTER (TRUE);

				else
				{
					g_scanner_error (scanner, "non-boolean assignment of boolean symbol '%s' (use 1/0 or true/false)\n", opt->name);
					nerrors++;
					continue;
				}

				notify_change(opt);

				nopts++;
			}
			break;

			case G_TOKEN_FLOAT:
			{
/*
				if (opt->type != G_TYPE_FLOAT)
				{
					g_scanner_error (scanner, "float assignment of non-float symbol '%s'\n", opt->name);
					nerrors++;
					continue;
				}

				opt->val = GINT_TO_POINTER (newval.v_int);
				nopts++;
*/
				if (1)
				{
					g_scanner_error (scanner, "float options not supported (or needed) at the moment. Symbol: '%s'\n", opt->name);
					nerrors++;
					continue;
				}
			}
			break;

			default :
			{
				g_scanner_error (scanner, "weird token '%d' assigned to '%s'\n", (gint) type, opt->name);
				nerrors++;
				break;
			}

		} /* switch (type) */

	} /* while */


	close (fd);

	g_scanner_destroy (scanner);

//	g_print ("%s: %d options set, %d errors\n", "config", nopts, nerrors);

	return;

}


/******************************************************************************
 *
 *   opt_set
 *
 ***/

void
opt_set (OptNum opt, gconstpointer val)
{
	GuiOption	*p;

	g_return_if_fail (opt >= 0 && opt < OPT_NUM_OPTIONS);

	p = &options[opt];

	g_return_if_fail ( p->name != NULL );

	switch (p->type)
	{
		case G_TYPE_BOOLEAN:
		case G_TYPE_UINT:
		case G_TYPE_INT:
		{
			p->val = (gpointer)val;
		}
		break;

		case G_TYPE_STRING:
		{
			if ( p->val != val )	/* fixes #722986 - Thanks, anonymous Hans :) */
				G_FREE(p->val)
			p->val = g_strdup ((gchar *)val);
		}
		break;

		default :
			g_return_if_reached();
	}

	notify_change(p);

	return;
}



/******************************************************************************
 *
 *   opt_get_int
 *
 ***/

gint
opt_get_int (OptNum num)
{
	g_return_val_if_fail ((num >= 0) && (num < OPT_NUM_OPTIONS), -1);

	return GPOINTER_TO_INT (options[num].val);
}


/******************************************************************************
 *
 *   opt_get_str
 *
 ***/

const gchar *
opt_get_str (OptNum num)
{
	g_return_val_if_fail ((num >= 0) && (num < OPT_NUM_OPTIONS), NULL);

	return (gchar*)options[num].val;
}



/******************************************************************************
 *
 *   opt_verify_opts_read
 *
 *   verify some restrictions are met
 *
 ***/

static void
opt_verify_opts_read (void)
{
	const gchar *slurl;

	if ( !opt_get_str(OPT_GUI_ICON_THEME)
	     || *opt_get_str(OPT_GUI_ICON_THEME) == 0x00
	     || !icons_check_theme (opt_get_str(OPT_GUI_ICON_THEME)) )
	{

		g_print ("WARNING: theme '%s' not found, using 'default'\n", opt_get_str(OPT_GUI_ICON_THEME));
		opt_set_str(OPT_GUI_ICON_THEME, "default");
	}

	if ( opt_get_int(OPT_GUI_SERVERS_ROTATE_INTERVAL) > 0
	        && opt_get_int(OPT_GUI_SERVERS_ROTATE_INTERVAL) < 30 )
	{
		opt_set_int(OPT_GUI_SERVERS_ROTATE_INTERVAL, 30);	/* make sure it's not too extreme */
	}

	slurl = opt_get_str(OPT_GUI_SERVERLIST_URL_1);
	if (slurl)
	{
		if (strstr(slurl,"users.aber.ac.uk/tpm01"))
			opt_reset_to_default (OPT_GUI_SERVERLIST_URL_1);
	}

	slurl = opt_get_str(OPT_GUI_SERVERLIST_URL_2);
	if (slurl)
	{
		if (strstr(slurl,"users.aber.ac.uk/tpm01"))
			opt_reset_to_default (OPT_GUI_SERVERLIST_URL_2);
	}

	if (!opt_get_bool(OPT_GUI_WINDOW_POS_ALLOW_NEGATIVE_VALUES))
	{
		gint posx = opt_get_int(OPT_GUI_WINDOW_POS_X);
		gint posy = opt_get_int(OPT_GUI_WINDOW_POS_Y);

		if (posx < 0)
			opt_set_int(OPT_GUI_WINDOW_POS_X, 0);
		
		if (posy < 0)
			opt_set_int(OPT_GUI_WINDOW_POS_Y, 0);
	}
}

/******************************************************************************
 *
 *   onTimeoutSaveOptions
 *
 *   Called every 5 mins, writes options to file
 *
 ******************************************************************************/

static gboolean
onTimeoutSaveOptions (gpointer foo)
{
	opt_write_to_disk();

	return TRUE; /* call again */
}


/******************************************************************************
 *
 *   opt_read_from_disk
 *
 ******************************************************************************/

void
opt_read_from_disk(void)
{
	static guint  timeout; /* 0 */
	gchar        *filename;

	filename = g_strdup(opt_get_opt_filename("options"));

	/* will fill in defaults and return
	 *  if file does not exist yet */
	opt_read();

	opt_verify_opts_read();

	if (timeout == 0)
		timeout = g_timeout_add(5*60*1000, (GSourceFunc) onTimeoutSaveOptions, NULL);

	G_FREE(filename);
}


/******************************************************************************
 *
 *   opt_init_prefpath
 *
 *   checks whether ~/.ed2k_gui/ exists, if not creates it, and sets prefpath
 *
 ***/

static void
opt_init_prefpath (void)
{
	const gchar *homedir;

	G_FREE(prefpath);

	homedir = g_get_home_dir();

#ifdef G_OS_WIN32
	/*  win98: g_get_home_dir() returns NULL
	 *  using wine + cross-compiled win32 binary, we
	 *  seem to be getting \home\<user> as the home
	 *  directory. That won't work. */
	if ( homedir==NULL || homedir[0] == '\\')
	{
		const gchar *windir;
		const gchar *winuser;
		
		mkdir(".\\config");
		
		winuser = g_get_user_name();
		
		if( winuser ) windir = g_strdup_printf(".\\config\\%s", winuser);
		else windir = g_strdup(".\\config");
		
		if( windir ) {
            mkdir(windir);
			homedir = windir; /* leaks */
		}
	}
#endif /* G_OS_WIN32 */

	g_assert(homedir != NULL );


	prefpath = g_strconcat (homedir, G_DIR_SEPARATOR_S, ".", PACKAGE, G_DIR_SEPARATOR_S, NULL);

	g_return_if_fail ( prefpath != NULL );

	/* does path   $HOME/.ed2k_gui/  not exist? */

	if ( !g_file_test (prefpath, G_FILE_TEST_EXISTS) )
	{
		/* ~/.ed2k_gui/ does NOT exists => create it */
#ifdef G_OS_UNIX
		if (mkdir (prefpath, S_IRWXU))
#else
		if (mkdir(prefpath))
#endif
		{
			g_printerr(_("--- couldn't create directory '%s' for GUI configuration files! (using home dir)\n"), prefpath);

			g_free(prefpath);
			prefpath = g_strdup(homedir);

			return;
		} /* else: mkdir() successful -   ~/.ed2k_gui/ exists now :) */
	}

	/*   ~/.ed2k_gui/ exists, but do we have write permission as well? */
	if ( access (prefpath, W_OK) )
	{
		/* no => try to change permissions */
		if ( chmod (prefpath, S_IRWXU) )
		{
			g_printerr (_("--- couldn't set write permissions for GUI config file directory '%s' (using home dir)\n"), prefpath);

			g_free(prefpath);
			prefpath = g_strdup(homedir);

			return;
		} /* else: changed permissions successfully */
	} /* else: yup, we have write permissions */
}



/******************************************************************************
 *
 *   opt_init_prefpath_if_not_yet_set
 *
 ***/

static void
opt_init_prefpath_if_not_yet_set (void)
{
	if (prefpath)
		return;

	opt_init_prefpath();
}


/******************************************************************************
 *
 *   opt_set_prefpath
 *
 *   can be an absolute or relative path
 *
 *   returns TRUE if everything went fine, otherwise FALSE
 *
 ***/

gboolean
opt_set_prefpath (const gchar *newpath)
{
	gboolean  ret;
	gchar     real[PATH_MAX+NAME_MAX+2];

	G_FREE(prefpath);

#ifdef G_OS_UNIX
	if ( realpath (newpath,real) != NULL )
#else
	g_snprintf(real, sizeof(real), "%s", newpath);
	if (1)
#endif
	{
		prefpath = g_strdup(real);

		if ( !g_file_test (prefpath, G_FILE_TEST_EXISTS) )
		{
			g_printerr (_("Path '%s' does not exist! Using default.\n"), prefpath);
			G_FREE(prefpath);
		}

		else if ( !g_file_test (prefpath, G_FILE_TEST_IS_DIR) )
		{
			g_printerr (_("Path '%s' is not a directory! Using default.\n"), prefpath);
			G_FREE(prefpath);
		}

		else if ( access(prefpath, W_OK) != 0 )
		{
			g_printerr (_("Path '%s' cannot be written to by current user. Using default.\n"), prefpath);
			G_FREE(prefpath);
		}
	}

	ret = (prefpath != NULL);

	if (!prefpath)
		opt_init_prefpath();

	return ret;
}


/******************************************************************************
 *
 *   opt_get_opt_filename_internal
 *
 *   Takes filename and puts path where preferences files are stored in front of it
 *
 *   returns static buffer, or NULL on error
 *
 ***/

static const gchar *
opt_get_opt_filename_internal (const gchar *fn, gboolean flag_use_instance)
{
	static gchar    *fullpath = NULL;
	gchar            instance[15];

	g_return_val_if_fail( fn != NULL, NULL );

	opt_init_prefpath_if_not_yet_set();

	g_return_val_if_fail ( g_file_test(prefpath, G_FILE_TEST_EXISTS), NULL );

	if ( (flag_use_instance)  &&  option_gui_instance > 0 )
	{
		g_snprintf (instance, sizeof(instance), ".%u", option_gui_instance);
	}
	else
		instance[0] = 0x00;  /* empty string (don't use index suffix) */

	G_FREE(fullpath);

	fullpath = g_strconcat ( prefpath, G_DIR_SEPARATOR_S, fn, instance, NULL);

	return fullpath;
}


const gchar *
opt_get_opt_filename (const gchar *fn)
{
	return opt_get_opt_filename_internal (fn, TRUE);
}


const gchar *
opt_get_opt_filename_without_instance (const gchar *fn)
{
	return opt_get_opt_filename_internal (fn, FALSE);
}

