/** \file wpref.c
 * Handle loading and saving preferences.
 */

/*  XTrkCad - Model Railroad CAD
 *  Copyright (C) 2005 Dave Bullis
 *
 *  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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <dirent.h>
#include <sys/stat.h>
#include <errno.h>

#define GTK_DISABLE_SINGLE_INCLUDES
#define GDK_DISABLE_DEPRECATED
#define GTK_DISABLE_DEPRECATED
#define GSEAL_ENABLE


#include <gtk/gtk.h>
#include <gdk/gdk.h>

#include "wlib.h"
#include "gtkint.h"
#include "dynarr.h"
#include "i18n.h"

#include "xtrkcad-config.h"

extern char wConfigName[];
static char appLibDir[BUFSIZ];
static char appWorkDir[BUFSIZ];
static char userHomeDir[BUFSIZ];

static char *profileFile;


/*
 *******************************************************************************
 *
 * Get Dir Names
 *
 *******************************************************************************
 */


/** Find the directory where configuration files, help, demos etc are installed.
 *  The search order is:
 *  1. Directory specified by the XTRKCADLIB environment variable
 *  2. Directory specified by XTRKCAD_INSTALL_PREFIX/share/xtrkcad
 *  3. /usr/share/xtrkcad
 *  4. /usr/local/share/xtrkcad
 *
 *  \return pointer to directory name
 */

const char * wGetAppLibDir( void )
{
	char * cp, *ep;
	char msg[BUFSIZ*2];
	char envvar[80];
	struct stat buf;

	if (appLibDir[0] != '\0') {
		return appLibDir;
	}

	for (cp=wlibGetAppName(),ep=envvar; *cp; cp++,ep++) {
		*ep = toupper(*cp);
	}
#ifndef __APPLE__
	if ( strstr( XTRKCAD_VERSION, "Beta" ) != NULL ) {
		strcat( ep, "BETA" );
	}
#endif
	strcat( ep, "LIB" );
	ep = getenv( envvar );
	if (ep != NULL) {
		if ((stat( ep, &buf) == 0 ) && S_ISDIR( buf.st_mode)) {
			strncpy( appLibDir, ep, sizeof(appLibDir) -1 );
			//printf( "wAppLbDir=%s\n", appLibDir );
			return appLibDir;
		}
	}

	strcpy(appLibDir, "../share/");
	strcat(appLibDir, wlibGetAppName());
	if ((stat( appLibDir, &buf) == 0 ) && S_ISDIR( buf.st_mode)) {
		//printf( "wAppLbDir=%s\n", appLibDir );
		return appLibDir;
	}

	char * dir1 = "/usr/share/";
	char * dir2 = "/usr/local/share/";
	char * beta = "";
	if ( strstr( XTRKCAD_VERSION, "Beta" ) != NULL ) {
		dir1 = "/usr/local/share/";
		dir2 = "/usr/share/";
#ifndef __APPLE__
		beta = "-beta";
#endif
	}

	strcpy( appLibDir, dir1 );
	strcat( appLibDir, wlibGetAppName() );
	strcat( appLibDir, beta );
	if ((stat( appLibDir, &buf) == 0 ) && S_ISDIR( buf.st_mode)) {
		//printf( "wAppLbDir=%s\n", appLibDir );
		return appLibDir;
	}

	strcpy( appLibDir, dir2 );
	strcat( appLibDir, wlibGetAppName() );
	if ((stat( appLibDir, &buf) == 0 ) && S_ISDIR( buf.st_mode)) {
		//printf( "wAppLbDir=%s\n", appLibDir );
		return appLibDir;
	}

	sprintf( msg,
	         _("The required configuration files could not be located in the expected location.\n\n"
	           "Usually this is an installation problem. Make sure that these files are installed in either \n"
	           "  ../share/xtrkcad or\n"
	           "  /usr/share/%s or\n"
	           "  /usr/local/share/%s\n"
	           "If this is not possible, the environment variable %s must contain "
	           "the name of the correct directory."),
	         wlibGetAppName(), wlibGetAppName(), envvar );
	wNoticeEx( NT_ERROR, msg, _("Ok"), NULL );
	appLibDir[0] = '\0';
	wExit(0);
	return NULL;
}

/**
 * Get the working directory for the application. This directory is used for storing
 * internal files including rc files. If it doesn't exist, the directory is created
 * silently.
 *
 * \return    pointer to the working directory
 */


const char * wGetAppWorkDir(
        void )
{
	char tmp[BUFSIZ+20];
	char * homeDir;
	DIR *dirp;

	if (appWorkDir[0] != '\0') {
		return appWorkDir;
	}

	if ((homeDir = getenv( "HOME" )) == NULL) {
		wNoticeEx( NT_ERROR, _("HOME is not set"), _("Exit"), NULL);
		wExit(0);
	}
	sprintf( appWorkDir, "%s/.%s", homeDir, wlibGetAppName() );
#ifndef __APPLE__
	if ( strstr( XTRKCAD_VERSION, "Beta" ) != NULL ) {
		strcat( appWorkDir, "-beta" );
	}
#endif
	if ( (dirp = opendir(appWorkDir)) != NULL ) {
		closedir(dirp);
	} else {
		if ( mkdir( appWorkDir, 0777 ) == -1 ) {
			sprintf( tmp, _("Cannot create %s"), appWorkDir );
			wNoticeEx( NT_ERROR, tmp, _("Exit"), NULL );
			wExit(0);
		} else {
			/*
			 * check for default configuration file and copy to
			 * the workdir if it exists
			 */
			struct stat stFileInfo;
			char appEtcConfig[BUFSIZ];
			sprintf( appEtcConfig, "/etc/%s.rc", wlibGetAppName());

			if ( stat( appEtcConfig, &stFileInfo ) == 0 ) {
				char copyConfigCmd[(BUFSIZ * 2) + 3];
				sprintf( copyConfigCmd, "cp %s %s", appEtcConfig, appWorkDir );
				system( copyConfigCmd );
			}
		}
	}
	return appWorkDir;
}

/**
 * Get the user's home directory. The environment variable HOME is
 * assumed to contain the proper directory.
 *
 * \return    pointer to the user's home directory
 */

const char *wGetUserHomeDir( void )
{
	char *homeDir;

	if( userHomeDir[ 0 ] != '\0' ) {
		return userHomeDir;
	}

	if ((homeDir = getenv( "HOME" )) == NULL) {
		wNoticeEx( NT_ERROR, _("HOME is not set"), _("Exit"), NULL);
		wExit(0);
	} else {
		strcpy( userHomeDir, homeDir );
	}

	return userHomeDir;
}


/*
 *******************************************************************************
 *
 * Preferences
 *
 *******************************************************************************
 */

typedef struct {
	char * section;
	char * name;
	wBool_t present;
	wBool_t dirty;
	char * val;
} prefs_t;
dynArr_t prefs_da;
#define prefs(N) DYNARR_N(prefs_t,prefs_da,N)
wBool_t prefInitted = FALSE;

/**
 * Define the name of the configuration file. Needed size is calculated, allocated and 
 * initialized with the filename
 * 
 * \param name overwrite default configuration 
 */

void static
wlibSetProfileFilename(char *name)
{
	const char *workDir;

	workDir = wGetAppWorkDir();
	if (name && name[0]) {
		size_t length;
		length = snprintf(profileFile, 0, "%s", name);
		profileFile = malloc(length + sizeof(NULL));
		snprintf( profileFile, length, "%s", name );
	} else {
		size_t length; 
		length = snprintf(profileFile, 0, "%s/%s.rc", workDir, wConfigName );
		profileFile = malloc(length + sizeof(NULL));
		length = snprintf(profileFile, length+sizeof(NULL), "%s/%s.rc", workDir, wConfigName );
	}
}


/**
 * Get the name of the configuration file.
 *
 * \return pointer to the filename.
 *
 */

char *
wGetProfileFilename()
{
	return(profileFile);
}

/**
 * Read the configuration file into memory
 */

static void readPrefs( char * name, wBool_t update )
{
	char tmp[BUFSIZ+32], *np, *vp, *cp;
	FILE * prefFile;
	prefs_t * p;

	prefInitted = TRUE;
	wlibSetProfileFilename(name);

	prefFile = fopen( profileFile, "r" );
	if (prefFile == NULL) {
		// First run, no .rc file yet
		return;
	}
	while ( ( fgets(tmp, sizeof tmp, prefFile) ) != NULL ) {
		char *sp;

		sp = tmp;
		while ( *sp==' ' || *sp=='\t' ) { sp++; }
		if ( *sp == '\n' || *sp == '#' ) {
			continue;
		}
		np = strchr( sp, '.' );
		if (np == NULL) {
			wNoticeEx( NT_INFORMATION, tmp, _("Continue"), NULL );
			continue;
		}
		*np++ = '\0';
		while ( *np==' ' || *np=='\t' ) { np++; }
		vp = strchr( np, ':' );
		if (vp == NULL) {
			wNoticeEx( NT_INFORMATION, tmp, _("Continue"), NULL );
			continue;
		}
		*vp++ = '\0';
		while ( *vp==' ' || *vp=='\t' ) { vp++; }
		cp = vp + strlen(vp) -1;
		while ( cp >= vp && (*cp=='\n' || *cp==' ' || *cp=='\t') ) { cp--; }
		cp[1] = '\0';
		if (update) {
			for (int i=0; i<prefs_da.cnt; i++) {
				p = &DYNARR_N(prefs_t,prefs_da,i);
				if (strcmp(p->name,np)==0 && strcmp(p->section,sp)==0) {
					p->val = strdup(vp);
					p->dirty = TRUE;
					break;
				}
			}
		} else {
			DYNARR_APPEND( prefs_t, prefs_da, 10 );
			p = &prefs(prefs_da.cnt-1);
			p->name = strdup(np);
			p->section = strdup(sp);
			p->dirty = FALSE;
			p->val = strdup(vp);
		}
	}
	fclose( prefFile );
}

/**
 * Store a string in the user preferences.
 *
 * \param section IN section in preferences file
 * \param name IN name of parameter
 * \param sval IN value to save
 */

void wPrefSetString(
        const char * section,		/* Section */
        const char * name,		/* Name */
        const char * sval )		/* Value */
{
	prefs_t * p;

	if (!prefInitted) {
		readPrefs("", FALSE);
	}

	for (p=&prefs(0); p<&prefs(prefs_da.cnt); p++) {
		if ( strcmp( p->section, section ) == 0 && strcmp( p->name, name ) == 0 ) {
			if (p->val) {
				free(p->val);
			}
			p->dirty = TRUE;
			p->val = (sval?strdup( sval ):NULL);
			return;
		}
	}
	DYNARR_APPEND( prefs_t, prefs_da, 10 );
	p = &prefs(prefs_da.cnt-1);
	p->name = strdup(name);
	p->section = strdup(section);
	p->dirty = TRUE;
	p->val = (sval?strdup(sval):NULL);
}

/**
 * Get a string from the user preferences.
 *
 * \param section IN section in preferences file
 * \param name IN name of parameter
 */

char * wPrefGetStringBasic(
        const char * section,			/* Section */
        const char * name )			/* Name */
{
	prefs_t * p;

	if (!prefInitted) {
		readPrefs("", FALSE);
	}

	for (p=&prefs(0); p<&prefs(prefs_da.cnt); p++) {
		if ( strcmp( p->section, section ) == 0 && strcmp( p->name, name ) == 0 ) {
			return p->val;
		}
	}
	return NULL;
}

/**
 * Store an integer value in the user preferences.
 *
 * \param section IN section in preferences file
 * \param name IN name of parameter
 * \param lval IN value to save
 */

void wPrefSetInteger(
        const char * section,		/* Section */
        const char * name,		/* Name */
        long lval )		/* Value */
{
	char tmp[20];

	snprintf(tmp, sizeof(tmp), "%ld", lval );
	wPrefSetString( section, name, tmp );
}

/**
 * Read an integer value from the user preferences.
 *
 * \param section IN section in preferences file
 * \param name IN name of parameter
 * \param res OUT resulting value
 * \param default IN default value
 * \return TRUE if value differs from default, FALSE if the same
 */

wBool_t wPrefGetIntegerBasic(
        const char * section,		/* Section */
        const char * name,		/* Name */
        long * res,		/* Address of result */
        long def )		/* Default value */
{
	const char * cp;
	char *cp1;

	cp = wPrefGetStringBasic( section, name );
	if (cp == NULL) {
		*res = def;
		return FALSE;
	}
	*res = strtol(cp,&cp1,0);
	if (cp==cp1) {
		*res = def;
		return FALSE;
	}
	return TRUE;
}

/**
 * Save a float value in the preferences file.
 *
 * \param section IN the file section into which the value should be saved
 * \param name IN the name of the preference
 * \param lval IN the value
 */

void wPrefSetFloat(
        const char * section,		/* Section */
        const char * name,		/* Name */
        double lval )		/* Value */
{
	char tmp[20];

	snprintf(tmp, sizeof(tmp), "%0.6f", lval );
	wPrefSetString( section, name, tmp );
}

/**
 * Read a float from the preferencesd file.
 *
 * \param section IN the file section from which the value should be read
 * \param name IN the name of the preference
 * \param res OUT pointer for the value
 * \param def IN	default value
 * \return TRUE if value was read, FALSE if default value is used
 */


wBool_t wPrefGetFloatBasic(
        const char * section,		/* Section */
        const char * name,		/* Name */
        double * res,		/* Address of result */
        double def )		/* Default value */
{
	const char * cp;
	char *cp1;

	cp = wPrefGetStringBasic( section, name );
	if (cp == NULL) {
		*res = def;
		return FALSE;
	}
	*res = strtod(cp, &cp1);
	if (cp == cp1) {
		*res = def;
		return FALSE;
	}
	return TRUE;
}

void wPrefsLoad(char * name)
{
	readPrefs(name,TRUE);
}

/**
 * Save the configuration to a file. The config parameters are held and updated in an array.
 * To make the settings persistant, this function has to be called.
 *
 */

void wPrefFlush(
        char * name )
{
	prefs_t * p;
	char tmp[BUFSIZ+32];
	FILE * prefFile;

	if (!prefInitted) {
		return;
	}

	wlibSetProfileFilename(name);

	prefFile = fopen( profileFile, "w" );
	if (prefFile == NULL) {
		// Can not write pref file
		size_t n = BUFSIZ+32-1-strlen(tmp);
		strncat( tmp, ": ", n );
		strncat( tmp, strerror(errno), n-2 );
		wNoticeEx( NT_ERROR, tmp, "Ok", NULL );
		return;
	}

	for (p=&prefs(0); p<&prefs(prefs_da.cnt); p++) {
		if(p->val) {
			fprintf( prefFile,  "%s.%s: %s\n", p->section, p->name, p->val );
		}
	}
	fclose( prefFile );
}

/**
 * Clear the preferences from memory
 * \return
 */

void wPrefReset(
        void )
{
	prefs_t * p;

	prefInitted = FALSE;
	for (p=&prefs(0); p<&prefs(prefs_da.cnt); p++) {
		if (p->section) {
			free( p->section );
		}
		if (p->name) {
			free( p->name );
		}
		if (p->val) {
			free( p->val );
		}
	}
	prefs_da.cnt = 0;
}

/**
 * Split a line from the config file ie. rc ini-file into separate tokens. The
 * line is split into sections, name of value and value following. Pointers
 * to the respective token are returned. These are zero-terminated. 
 * If a token is not present, NULL is returned instead. 
 * The input line is modified.
 * 
 * \param line		input line, modified during excution of function
 * \param section	section if present
 * \param name		name of config value if present
 * \param value		name of value if present
 */
void 
wPrefTokenize(char* line, char** section, char** name, char** value)
{
	*section = NULL;
	*name = NULL;
	*value = NULL;

	*section = strtok(line, ".");
	*name = strtok(NULL, ":");
	*value = strtok(NULL, "\n");
	if(*value)
		g_strstrip(*value);

}

/**
 * A valid line for a config file is created from the individual elements. 
 * Values not need for specific statement are ignored. Eg. when section is 
 * present, name and value are not used. 
 * The caller has to make sure, that the return buffer is large enough.
 * 
 * \param section	section, first token, dellimited with '.'
 * \param name		name, left side of ':'
 * \param value		value, right side of ':'
 * \param result	pointer to buffer for formated line. 
 */

void 
wPrefFormatLine(const char* section, const char* name, const char* value, char* result)
{
	if (!value || *value == '\0') {
		value = "";
	}
	sprintf(result, "%s.%s: %s", section, name, value);
}