/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
/*
 * viking -- GPS Data and Topo Analyzer, Explorer, and Manager
 *
 * Copyright (C) 2013, Rob Norris <rw_norris@hotmail.com>
 *
 * 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.
 *
 * 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */
 /*
  * Sort of like the globals file, but values are automatically saved via program use.
  * Some settings are *not* intended to have any GUI controls.
  * Other settings be can used to set other GUI elements.
  *
  * ATM This is implemented using the simple (for me!) GKeyFile API - AKA an .ini file
  *  One might wish to consider the more modern alternative such as:
  *            http://developer.gnome.org/gio/2.26/GSettings.html
  * Since these settings are 'internal' I have no problem with them *not* being supported
  *  between various Viking versions, should one switch to different API/storage methods.
  * Indeed even the internal settings themselves can be liable to change.
  */
#include "settings.h"
#include "dir.h"
#include "globals.h"

static GKeyFile *keyfile;

#define VIKING_INI_FILE "viking.ini"

static gboolean settings_load_from_file()
{
	GKeyFileFlags flags = G_KEY_FILE_KEEP_COMMENTS;

	GError *error = NULL;

	gchar *fn = g_build_filename ( a_get_viking_dir(), VIKING_INI_FILE, NULL );

	if ( !g_key_file_load_from_file ( keyfile, fn, flags, &error ) ) {
		// Probably first run or alternate config dir so file may not exist yet
		g_debug ( "%s: %s", error->message, fn );
		g_free ( fn );
		g_error_free ( error );
		return FALSE;
	}

	g_free ( fn );

	return TRUE;
}

void a_settings_init()
{
	keyfile = g_key_file_new();
	settings_load_from_file();
}

/**
 * a_settings_uninit:
 *
 *  ATM: The only time settings are saved is on program exit
 *   Could change this to occur on window exit or dialog exit or have memory hash of values...?
 */
void a_settings_uninit()
{
	GError *error = NULL;
	gchar *fn = g_build_filename ( a_get_viking_dir(), VIKING_INI_FILE, NULL );
	gsize size;

	gchar *keyfilestr = g_key_file_to_data ( keyfile, &size, &error );

	if ( error ) {
		g_warning ( "%s", error->message );
		g_error_free ( error );
		goto tidy;
	}

	g_file_set_contents ( fn, keyfilestr, size, &error );
	if ( error ) {
		g_warning ( "%s: %s", error->message, fn );
		g_error_free ( error );
	}

	g_key_file_free ( keyfile );
 tidy:
	g_free ( keyfilestr );
	g_free ( fn );
}

// ATM, can't see a point in having any more than one group for various settings
#define VIKING_SETTINGS_GROUP "viking"

static gboolean settings_get_boolean ( const gchar *group, const gchar *name, gboolean *val )
{
	GError *error = NULL;
	gboolean success = TRUE;
	gboolean bb = g_key_file_get_boolean ( keyfile, group, name, &error );
	if ( error ) {
		// Only print on debug - as often may have requests for keys not in the file
		if ( vik_debug && vik_verbose )
			g_message ( "%s", error->message );
		g_error_free ( error );
		success = FALSE;
	}
	*val = bb;
	return success;
}

gboolean a_settings_get_boolean ( const gchar *name, gboolean *val )
{
	return settings_get_boolean ( VIKING_SETTINGS_GROUP, name, val );
}

void a_settings_set_boolean ( const gchar *name, gboolean val )
{
	g_key_file_set_boolean ( keyfile, VIKING_SETTINGS_GROUP, name, val );
}

static gboolean settings_get_string ( const gchar *group, const gchar *name, gchar **val )
{
	GError *error = NULL;
	gboolean success = TRUE;
	gchar *str = g_key_file_get_string ( keyfile, group, name, &error );
	if ( error ) {
		// Only print on debug - as often may have requests for keys not in the file
		if ( vik_debug && vik_verbose )
			g_message ( "%s", error->message );
		g_error_free ( error );
		success = FALSE;
	}
	*val = str;
	return success;
}

/**
 * Free the returned string in @val after use
 */
gboolean a_settings_get_string ( const gchar *name, gchar **val )
{
	return settings_get_string ( VIKING_SETTINGS_GROUP, name, val );
}

void a_settings_set_string ( const gchar *name, const gchar *val )
{
	g_key_file_set_string ( keyfile, VIKING_SETTINGS_GROUP, name, val );
}

static gboolean settings_get_integer ( const gchar *group, const gchar *name, gint *val )
{
	GError *error = NULL;
	gboolean success = TRUE;
	gint ii = g_key_file_get_integer ( keyfile, group, name, &error );
	if ( error ) {
		// Only print on debug - as often may have requests for keys not in the file
		if ( vik_debug && vik_verbose )
			g_message ( "%s", error->message );
		g_error_free ( error );
		success = FALSE;
	}
	*val = ii;
	return success;
}

gboolean a_settings_get_integer ( const gchar *name, gint *val )
{
	return settings_get_integer ( VIKING_SETTINGS_GROUP, name, val );
}

void a_settings_set_integer ( const gchar *name, gint val )
{
	g_key_file_set_integer ( keyfile, VIKING_SETTINGS_GROUP, name, val );
}

static gboolean settings_get_double ( const gchar *group, const gchar *name, gdouble *val )
{
	GError *error = NULL;
	gboolean success = TRUE;
	gdouble dd = g_key_file_get_double ( keyfile, group, name, &error );
	if ( error ) {
		// Only print on debug - as often may have requests for keys not in the file
		if ( vik_debug && vik_verbose )
			g_message ( "%s", error->message );
		g_error_free ( error );
		success = FALSE;
	}
	*val = dd;
	return success;
}

gboolean a_settings_get_double ( const gchar *name, gdouble *val )
{
	return settings_get_double ( VIKING_SETTINGS_GROUP, name, val );
}

void a_settings_set_double ( const gchar *name, gdouble val )
{
	g_key_file_set_double ( keyfile, VIKING_SETTINGS_GROUP, name, val );
}

static gboolean settings_get_integer_list ( const gchar *group, const gchar *name, gint **vals, gsize *length )
{
	GError *error = NULL;
	gboolean success = TRUE;
	gint *ints = g_key_file_get_integer_list ( keyfile, group, name, length, &error );
	if ( error ) {
		// Only print on debug - as often may have requests for keys not in the file
		if ( vik_debug && vik_verbose )
			g_message ( "%s", error->message );
		g_error_free ( error );
		success = FALSE;
	}
	*vals = ints;
	return success;
}

/*
 * The returned list of integers should be freed when no longer needed
 */
gboolean a_settings_get_integer_list ( const gchar *name, gint **vals, gsize* length )
{
	return settings_get_integer_list ( VIKING_SETTINGS_GROUP, name, vals, length );
}

void a_settings_set_integer_list ( const gchar *name, gint vals[], gsize length )
{
	g_key_file_set_integer_list ( keyfile, VIKING_SETTINGS_GROUP, name, vals, length );
}

gboolean a_settings_get_integer_list_contains ( const gchar *name, gint val )
{
	gint* vals = NULL;
	gsize length;
	// Get current list and see if the value supplied is in the list
	gboolean contains = FALSE;
	// Get current list
	if ( a_settings_get_integer_list ( name, &vals, &length ) ) {
		// See if it's not already there
		gint ii = 0;
		if ( vals && length ) {
			while ( ii < length ) {
				if ( vals[ii] == val ) {
					contains = TRUE;
					break;
				}
				ii++;
			}
			// Free old array
			g_free (vals);
		}
	}
	return contains;
}

void a_settings_set_integer_list_containing ( const gchar *name, gint val )
{
	gint* vals = NULL;
	gsize length = 0;
	gboolean need_to_add = TRUE;
	gint ii = 0;
	// Get current list
	if ( a_settings_get_integer_list ( name, &vals, &length ) ) {
		// See if it's not already there
		if ( vals ) {
			while ( ii < length ) {
				if ( vals[ii] == val ) {
					need_to_add = FALSE;
					break;
				}
				ii++;
			}
		}
	}
	// Add value into array if necessary
	if ( need_to_add ) {
		// NB not bothering to sort this 'list' ATM as there is not much to be gained
		guint new_length = length + 1;
		gint new_vals[new_length];
		// Copy any previous values
		if ( vals ) {
			for ( ii = 0; ii < length; ii++ ) {
				new_vals[ii] = vals[ii];
			}
		}
		new_vals[length] = val; // Set the new value
		// Apply
		a_settings_set_integer_list ( name, new_vals, new_length );
	}

	// Free old array
	if ( vals )
		g_free (vals);
}

/*
 * The returned list of strings should be freed when no longer needed
 *  use g_strfreev().
 */
static gchar** settings_get_string_list ( const gchar *group, const gchar *name, gsize *length )
{
	GError *error = NULL;
	gchar **msgs = g_key_file_get_string_list ( keyfile, group, name, length, &error );
	if ( error ) {
		// Only print on debug - as often may have requests for keys not in the file
		if ( vik_debug && vik_verbose )
			g_message ( "%s", error->message );
		g_error_free ( error );
	}
	return msgs;
}

/**
 * a_settings_get_string_list:
 *
 * Returns: A #GList of strings
 *
 * Free the returned list after use (including the contents)
 */
GList* a_settings_get_string_list ( const gchar *name )
{
	//gchar **vals = NULL;
	GList *gl = NULL;
	gsize length;
	gchar **vals = settings_get_string_list ( VIKING_SETTINGS_GROUP, name, &length );
	if ( vals ) {
		for ( guint ii = 0; ii < length; ii++ )
			gl = g_list_prepend ( gl, g_strdup(vals[ii]) );
		gl = g_list_reverse ( gl );
		g_strfreev ( vals );
	}
	return gl;
}

void a_settings_clear_string_list ( const gchar *name )
{
	g_key_file_set_string_list ( keyfile, VIKING_SETTINGS_GROUP, name, NULL, 0 );
}

gboolean a_settings_get_string_list_contains ( const gchar *name, const gchar *val )
{
	//	gchar **vals = NULL;
	gsize length;
	// Get current list and see if the value supplied is in the list
	gboolean contains = FALSE;
	// Get current list
	gchar **vals = settings_get_string_list ( VIKING_SETTINGS_GROUP, name, &length );
	if ( vals ) {
		//if ( settings_get_string_list ( VIKING_SETTINGS_GROUP, name, vals, &length ) ) {
		// See if it's not already there
		//		if ( vals ) {
			contains = g_strv_contains ( (const gchar * const*)vals, val );
			g_strfreev ( vals );
			//}
	}
	return contains;
}

void a_settings_set_string_list_containing ( const gchar *name, const gchar *val )
{
	//	gchar** vals = NULL;
	gsize length = 0;
	gboolean need_to_add = TRUE;
	//gint ii = 0;
	// Get current list
	gchar **vals = settings_get_string_list ( VIKING_SETTINGS_GROUP, name, &length );
	if ( vals ) {
		//	if ( settings_get_string_list ( VIKING_SETTINGS_GROUP, name, vals, &length ) ) {
		// See if it's not already there
		//if ( vals )
			need_to_add = ! g_strv_contains ( (const gchar * const*)vals, val );
	}
	// Add value into array if necessary
	if ( need_to_add ) {
		//gchar **new_vals = g_strdupv ( vals ); // Not sure how to append to this
		// NB not bothering to sort this 'list' ATM as there is not much to be gained
		const gchar *new_vals[length+2]; // +2 for new value + null
		// Copy any previous values
		for ( guint ii=0; ii<length; ii++ )
			new_vals[ii] = g_strdup ( vals[ii] );

		// Add the new value and the final terminator
		new_vals[length] = g_strdup ( val ); // Set the new value
		new_vals[length+1] = NULL;

		/*
		GPtrArray *string_list = g_ptr_array_new ();
		// Copy any previous values
		while ( ii < length ) {
			g_ptr_array_add ( string_list, g_strdup ( vals[ii] ) );
			ii++;
		}
		// Add the new value
		g_ptr_array_add ( string_list, g_strdup ( val ) );
		gchar **new_vals = (gchar**)g_ptr_array_free ( string_list, FALSE );
		*/
		// Apply
		g_key_file_set_string_list ( keyfile, VIKING_SETTINGS_GROUP, name, new_vals, length+1 );
	}

	// Free old array
	if ( vals )
		g_strfreev ( vals );
}
