/* -*- 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-2020, 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
 *
 */
#include <gtk/gtk.h>
#include <glib/gi18n.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <glib/gstdio.h>
#include "viklayer_defaults.h"
#include "dir.h"
#include "file.h"

#define VIKING_LAYER_DEFAULTS_INI_FILE "viking_layer_defaults.ini"

// A list of the parameter types in use
static GPtrArray *paramsVD;

static GKeyFile *keyfile;

static gboolean loaded;

// Reset callback functions stored here
static GHashTable *pointers;

static VikLayerParamData get_default_data_answer ( const gchar *group, const gchar *name, VikLayerParamType ptype, gpointer *success )
{
	VikLayerParamData data = VIK_LPD_BOOLEAN ( FALSE );

	GError *error = NULL;

	switch ( ptype ) {
	case VIK_LAYER_PARAM_DOUBLE: {
		gdouble dd = g_key_file_get_double ( keyfile, group, name, &error );
		if ( !error ) data.d = dd;
		break;
	}
	case VIK_LAYER_PARAM_UINT: {
		guint32 uu = g_key_file_get_integer ( keyfile, group, name, &error );
		if ( !error ) data.u = uu;
		break;
	}
	case VIK_LAYER_PARAM_INT: {
		gint32 ii = g_key_file_get_integer ( keyfile, group, name, &error );
		if ( !error ) data.i = ii;
		break;
	}
	case VIK_LAYER_PARAM_BOOLEAN: {
		gboolean bb = g_key_file_get_boolean ( keyfile, group, name, &error );
		if ( !error ) data.b = bb;
		break;
	}
	case VIK_LAYER_PARAM_STRING: {
		gchar *str = g_key_file_get_string ( keyfile, group, name, &error );
		if ( !error ) data.s = str;
		break;
	}
	//case VIK_LAYER_PARAM_STRING_LIST: {
	//	gchar **str = g_key_file_get_string_list ( keyfile, group, name, &error );
	//	data.sl = str_to_glist (str); //TODO convert
	//	break;
	//}
	case VIK_LAYER_PARAM_COLOR: {
		gchar *str = g_key_file_get_string ( keyfile, group, name, &error );
		if ( !error ) {
			memset(&(data.c), 0, sizeof(data.c));
			gdk_color_parse ( str, &(data.c) );
		}
		g_free ( str );
		break;
	}
	case VIK_LAYER_PARAM_PTR:
	case VIK_LAYER_PARAM_PTR_DEFAULT: {
		gchar *key = g_strdup_printf ( "%s.%s", group, name );
		data.ptr = g_hash_table_lookup ( pointers, key );
		g_free ( key );
		break;
	}
	default: break;
	}
	*success = GINT_TO_POINTER (TRUE);
	if ( error ) {
		g_debug ( "%s", error->message );
		g_error_free ( error );
		*success = GINT_TO_POINTER (FALSE);
	}

	return data;
}

static VikLayerParamData get_default_data ( const gchar *group, const gchar *name, VikLayerParamType ptype )
{
	gpointer success = GINT_TO_POINTER (TRUE);
	// NB This should always succeed - don't worry about 'success'
	return get_default_data_answer ( group, name, ptype, &success );
}

static void set_default_data ( VikLayerParamData data, const gchar *group, const gchar *name, VikLayerParamType ptype )
{
	switch ( ptype ) {
	case VIK_LAYER_PARAM_DOUBLE:
		g_key_file_set_double ( keyfile, group, name, data.d );
		break;
	case VIK_LAYER_PARAM_UINT:
		g_key_file_set_integer ( keyfile, group, name, data.u );
		break;
	case VIK_LAYER_PARAM_INT:
		g_key_file_set_integer ( keyfile, group, name, data.i );
		break;
	case VIK_LAYER_PARAM_BOOLEAN:
		g_key_file_set_boolean ( keyfile, group, name, data.b );
		break;
	case VIK_LAYER_PARAM_STRING:
		if ( data.s )
			g_key_file_set_string ( keyfile, group, name, data.s );
		break;
	case VIK_LAYER_PARAM_COLOR: {
		gchar *str = g_strdup_printf ( "#%.2x%.2x%.2x", (int)(data.c.red/256),(int)(data.c.green/256),(int)(data.c.blue/256));
		g_key_file_set_string ( keyfile, group, name, str );
		g_free ( str );
		break;
	}
	case VIK_LAYER_PARAM_PTR:
	case VIK_LAYER_PARAM_PTR_DEFAULT: {
		gchar *key = g_strdup_printf ( "%s.%s", group, name );
		g_hash_table_insert ( pointers, key, data.ptr );
		break;
	}
	default: break;
	}
}

static void defaults_run_changeparam ( GtkWidget* ww, ui_change_values values )
{
	VikLayerParam *vlp = values[UI_CHG_PARAM];
	if ( vik_layer_get_interface(vlp->layer)->change_param )
		vik_layer_get_interface(vlp->layer)->change_param(ww, values);
}

static void defaults_run_setparam ( gpointer index_ptr, guint16 i, VikLayerParamData data, VikLayerParam *params )
{
	// Index is only an index into values from this layer
	gint index = GPOINTER_TO_INT ( index_ptr );
	VikLayerParam *vlp = (VikLayerParam *)g_ptr_array_index(paramsVD,index+i);

	set_default_data ( data, vik_layer_get_interface(vlp->layer)->fixed_layer_name, vlp->name, vlp->type );
}

static VikLayerParamData defaults_run_getparam ( gpointer index_ptr, guint16 i, gboolean notused2 )
{
	// Index is only an index into values from this layer
	gint index = GPOINTER_TO_INT ( index_ptr );
	VikLayerParam *vlp = (VikLayerParam *)g_ptr_array_index(paramsVD,index+i);

	return get_default_data ( vik_layer_get_interface(vlp->layer)->fixed_layer_name, vlp->name, vlp->type );
}

// Return default value of a parameter (rather than from the layer defaults themselves)
static VikLayerParamData getparam_default_value ( gpointer index_ptr, guint16 i, gboolean notused2 )
{
	// Index is only an index into values from this layer
	gint index = GPOINTER_TO_INT ( index_ptr );
	VikLayerParam *vlp = (VikLayerParam *)g_ptr_array_index(paramsVD,index+i);
	VikLayerParamData data;
	data.s = NULL;
	if ( vlp->default_value )
		data = vlp->default_value();
	return data;
}

static void use_internal_defaults_if_missing_default ( VikLayerTypeEnum type )
{
	VikLayerParam *params = vik_layer_get_interface(type)->params;
	if ( ! params )
		return;

	guint16 params_count = vik_layer_get_interface(type)->params_count;
	guint16 i;
	// Process each parameter
	for ( i = 0; i < params_count; i++ ) {
		if ( params[i].group != VIK_LAYER_NOT_IN_PROPERTIES ) {
			// Don't attempt to refresh pointers
			if ( params[i].type <= VIK_LAYER_PARAM_STRING_LIST ) {
				gpointer success = GINT_TO_POINTER (FALSE);
				// Check current default is available
				VikLayerParamData data = get_default_data_answer ( vik_layer_get_interface(type)->fixed_layer_name, params[i].name, params[i].type, &success );
				// If no longer have a viable default
				if ( ! GPOINTER_TO_INT (success) ) {
					// Reset value
					if ( params[i].default_value ) {
						VikLayerParamData paramd = params[i].default_value();
						set_default_data ( paramd, vik_layer_get_interface(type)->fixed_layer_name, params[i].name, params[i].type );
					}
				} else {
					// Need to free data (which is otherwise unused) if its a dynamic type (i.e. strings)
					if ( params[i].type == VIK_LAYER_PARAM_STRING )
						g_free ( (gchar*)data.s );
				}
			}
		}
	}
}

static gboolean defaults_load_from_file()
{
	GKeyFileFlags flags = G_KEY_FILE_KEEP_COMMENTS;

	GError *error = NULL;

	gchar *fn = g_build_filename ( a_get_viking_dir(), VIKING_LAYER_DEFAULTS_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 );

	// Ensure if have a key file, then any missing values are set from the internal defaults
	VikLayerTypeEnum layer;
	for ( layer = 0; layer < VIK_LAYER_NUM_TYPES; layer++ ) {
		use_internal_defaults_if_missing_default ( layer );
	}

	return TRUE;
}

/* TRUE on success */
static gboolean layer_defaults_save_to_file()
{
	gboolean answer = TRUE;
	GError *error = NULL;
	gchar *fn = g_build_filename ( a_get_viking_dir(), VIKING_LAYER_DEFAULTS_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 );
		answer = FALSE;
		goto tidy;
	}

	// optionally could do:
	// g_file_set_contents ( fn, keyfilestr, size, &error );
    // if ( error ) {
	//	g_warning ( "%s: %s", error->message, fn );
	//	 g_error_free ( error );
	//  answer = FALSE;
	//	goto tidy;
	// }

	FILE *ff;
	if ( !(ff = g_fopen ( fn, "w")) ) {
		g_warning ( _("Could not open file: %s"), fn );
		answer = FALSE;
		goto tidy;
	}
	// Layer defaults not that secret, but just incase...
	if ( g_chmod ( fn, 0600 ) != 0 )
		g_warning ( "%s: Failed to set permissions on %s", __FUNCTION__, fn );

	fputs ( keyfilestr, ff );
	fclose ( ff );

tidy:
	g_free ( keyfilestr );
	g_free ( fn );

	return answer;
}

/**
 * Free returned array after use if anything was created
 */
static VikLayerParam* allocate_params_for_layer ( VikLayerTypeEnum layer, guint *layer_params_count_return, gint *indexer )
{
	// Need to know where the params start and they finish for this layer

	// 1. inspect every registered param - see if it has the layer value required to determine overall size
	//    [they are contiguous from the start index]
	// 2. copy the these params from the main list into a tmp array
	//
	// Return this tmp array for use in passing on to uibuilder for display
	guint layer_params_count = 0;
	gboolean found_first = FALSE;
	gint index = 0;
	int i;
	for ( i = 0; i < paramsVD->len; i++ ) {
		VikLayerParam *param = (VikLayerParam*)(g_ptr_array_index(paramsVD,i));
		if ( param->layer == layer ) {
			layer_params_count++;
			if ( !found_first ) {
				index = i;
				found_first = TRUE;
			}
		}
	}
	VikLayerParam *params = g_new0(VikLayerParam,layer_params_count);
	for ( i = 0; i < layer_params_count; i++ ) {
		params[i] = *((VikLayerParam*)(g_ptr_array_index(paramsVD,i+index)));
	}
	*layer_params_count_return = layer_params_count;
	*indexer = index;
	return params;
}

/**
 * a_layer_defaults_show_window:
 * @parent:     The Window
 * @layername:  The layer
 *
 * This displays a Window showing the default parameter values for the selected layer
 * It allows the parameters to be changed.
 *
 * Returns: %TRUE if the window is displayed (because there are parameters to view)
 */
gboolean a_layer_defaults_show_window ( GtkWindow *parent, const gchar *layername )
{
	if ( ! loaded ) {
		// since we can't load the file in a_defaults_init (no params registered yet),
		// do it once before we display the params.
		defaults_load_from_file();
		loaded = TRUE;
	}

	VikLayerTypeEnum layer = vik_layer_type_from_string ( layername );
	guint layer_params_count = 0;
	gint index = 0;
	VikLayerParam *params = allocate_params_for_layer ( layer, &layer_params_count, &index );

	// Have we any parameters to show!
	if ( !layer_params_count )
		return FALSE;

    gchar *title = g_strconcat ( layername, ": ", _("Layer Defaults"), NULL );

	if ( a_uibuilder_properties_factory ( title,
	                                      parent,
	                                      params,
	                                      layer_params_count,
	                                      vik_layer_get_interface(layer)->params_offset,
	                                      vik_layer_get_interface(layer)->params_groups,
	                                      vik_layer_get_interface(layer)->params_groups_count,
	                                      0,
	                                      NULL,
	                                      (gboolean (*) (gpointer,guint16,VikLayerParamData,gpointer)) defaults_run_setparam,
	                                      GINT_TO_POINTER ( index ),
	                                      params,
	                                      defaults_run_getparam,
	                                      GINT_TO_POINTER ( index ),
	                                      defaults_run_changeparam,
	                                      FALSE,
	                                      NULL,
	                                      NULL,
	                                      TRUE) ) {
		// Save
		layer_defaults_save_to_file();
    }

    g_free ( title );
    g_free ( params );

    return TRUE;
}

/**
 * a_layer_defaults_register:
 * @vlp:        The parameter
 * @defaultval: The default value
 * @layername:  The layer in which the parameter resides
 *
 * Call this function on to set the default value for the particular parameter
 */
void a_layer_defaults_register (VikLayerParam *vlp, VikLayerParamData defaultval, const gchar *layername )
{
	/* copy value */
	VikLayerParam *newvlp = g_new(VikLayerParam,1);
	*newvlp = *vlp;

	g_ptr_array_add ( paramsVD, newvlp );

	set_default_data ( defaultval, layername, vlp->name, vlp->type );
}

/**
 * a_layer_defaults_init:
 *
 * Call this function at startup
 */
void a_layer_defaults_init()
{
	keyfile = g_key_file_new();

	/* not copied */
	paramsVD = g_ptr_array_new ();

	loaded = FALSE;

	pointers = g_hash_table_new_full ( g_str_hash, g_str_equal, g_free, NULL );
}

/**
 * a_layer_defaults_uninit:
 *
 * Call this function on program exit
 */
void a_layer_defaults_uninit()
{
	g_key_file_free ( keyfile );
	g_ptr_array_foreach ( paramsVD, (GFunc)g_free, NULL );
	g_ptr_array_free ( paramsVD, TRUE );
	g_hash_table_destroy ( pointers );
}

/**
 * a_layer_defaults_get:
 * @layername:  String name of the layer
 * @param_name: String name of the parameter
 * @param_type: The parameter type
 *
 * Call this function to get the default value for the parameter requested
 */
VikLayerParamData a_layer_defaults_get ( const gchar *layername, const gchar *param_name, VikLayerParamType param_type )
{
	if ( ! loaded ) {
		// since we can't load the file in a_defaults_init (no params registered yet),
		// do it once before we get the first key.
		defaults_load_from_file();
		loaded = TRUE;
	}

	return get_default_data ( layername, param_name, param_type );
}

/**
 * a_layer_defaults_save:
 *
 * Call this function to save the current layer defaults
 * Normally should only be performed if any layer defaults have been changed via direct manipulation of the layer
 *  rather than the user changing the preferences via the dialog window above
 *
 * This must only be performed once all layer parameters have been initialized
 *
 * Returns: %TRUE if saving was successful
 */
gboolean a_layer_defaults_save ()
{
	// Generate defaults
	VikLayerTypeEnum layer;
	for ( layer = 0; layer < VIK_LAYER_NUM_TYPES; layer++ ) {
		use_internal_defaults_if_missing_default ( layer );
	}

	return layer_defaults_save_to_file();
}

/**
 * a_layer_defaults_reset_show:
 *
 * Update displayed properties with the default values for the specified layer
 * @index_ptr: Either a pointer to an integer value of the layers parameter index
 *              - and thus reset widget parameters to Viking's system default values
 *             Or a pointer to a #VikLayer
 *              - in which case the widgets are reset to the User's Layer default values
 */
void a_layer_defaults_reset_show ( const gchar *layername, gpointer index_ptr, gint16 group )
{
	VikLayerTypeEnum layer = vik_layer_type_from_string ( layername );
	guint layer_params_count = 0;
	gint index = 0;
	VikLayerParam *params = allocate_params_for_layer ( layer, &layer_params_count, &index );
	// No parameters!
	if ( !layer_params_count )
		return;

	// Refresh view - with defaults values for the params on display
	// Since we always calculate the index value above,
	//  we can use the incoming parameter value to control the conditional behaviour
	if ( GPOINTER_TO_INT(index_ptr) != index )
		// Reset to user layer defaults
		a_uibuilder_factory_refresh ( params, layer_params_count, group, defaults_run_getparam, GINT_TO_POINTER(index) );
	else
		// Reset to system default
		a_uibuilder_factory_refresh ( params, layer_params_count, group, getparam_default_value, GINT_TO_POINTER(index) );

	g_free ( params );
}
