//
//config.h
//
//functions related to turn configuration files and command lines into configuration.
//
//-UserX 2001/11/02
//#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "misc/global.h"

#include "base/mem.h"
#include "base/logger.h"
#include "base/cfg.h"
#include "base/parse.h"
#include "base/array.h"
#include "base/strarray.h"

#include "file/file.h"
/**
@name config
@author UserX
*/
//@{

StringIntPair boollist[] = {
		{"yes", 1},
		{"y", 1},
		{"true", 1},
		{"t", 1},
		{"on", 1},
		{"1", 1},

		{"no", 0},
		{"n", 0},
		{"false", 0},
		{"f", 0},
		{"off", 0},
		{"0", 0},

		{NULL, -1}
	};

char defaultconfigfilename[] = DEFAULT_CONFIGNAME;
char *configfilename = NULL;

ConfigEntry blankconfigentry = BLANKCONFIGENTRY;

ConfigEntryArrayHandle *cearray = NULL;

StringArrayHandle *cmdline = NULL;

/**
Initializes the internal variables of the config system.
*/
void configInit(void) {
	if(cearray == NULL) {
		cearray = (ConfigEntryArrayHandle *) arrayMake(sizeof(ConfigEntry), 0, &blankconfigentry, NULL, NULL);
	}
}

/**
Adds an array of \Ref{ConfigEntry}. 
The array must be terminated with a BLANKCONFIGENTRY (or CETYPE_NONE).
@param cea The array to use.
*/
void configAddList(ConfigEntry *cea) {
	int i;
	int j;
	configInit();
	if(cea == NULL) {
		return;
	}
	for(i = 0; cea[i].type != CETYPE_NONE; i++) {
		j = cearray->size;
		arrayInsert((ArrayHandle *)cearray, j, 1);
		cearray->data[j] = cea[i];
	}
}

/**
Clear the list of available config parameters.
This will clear every thing previously added by configAddList().
*/
void configResetList(void) {
	configInit();
	arrayDelete((ArrayHandle *)cearray, 0, cearray->size);
}

/**
Match a string to the long name of a config entry.
@param s The string to match.
@return The index of the match or -1 if none found.
*/
int configMatchEntry(char *s) {
	int j;
	for(j = 0; j < cearray->size; j++) {
		if(stringCaseCompare(cearray->data[j].name.name, s) == 0) {
			return j;
		}
	}
	return -1;
}

/**
Match a string to the short name of a config entry.
@param s The string to match.
@return The index of the match or -1 if none found.
*/
int configMatchEntryCL(char *s) {
	int j;
	for(j = 0; j < cearray->size; j++) {
		if(stringCompare(cearray->data[j].name.clname, s) == 0) {
			return j;
		}
	}
	return -1;
}

/**
Validates a parameter
@param func This the function to call to do validating. 
@param extra This the 'extra' field of the ConfigEntry. 
@param value Pointer to the value being checked.
@param s The original line.
@return Zero if it is good, non-zero if it is bad.
*/
int configValidate(ConfigEntryValidateFunc func, void *extra, void *value, char **s) {
	int result = 0;
	if(func != NULL) {
		func(extra, &result, value, s);
	}
	return result;
}

/**
Parses a the value of a ConfigEntry.
@param index Index of the ConfigEntry.
@param key The key string.
@param value The value in string form.
@param ss The original line (//todo: fix the calling functions).
@return Zero on success, non-zero on failure.
*/
int configReadEntryData(int index, char *key, char *value, char *ss) {
	int v;
	char *s;
	//char *ss;
	switch(abs(cearray->data[index].type)) {
	case CETYPE_INT: //todo: format checking
		v = atoi(value);
		if(configValidate(cearray->data[index].validatefunc, cearray->data[index].funcextra, (void *) &v, &ss) == 0){
			*cearray->data[index].i.data = v;
			return 0;
		} else {
			logmsg(LOG_ERROR, stringCopyMany(
					"Bad value: Key \"", 
					key,
					"\" , value: \"",
					value,
					"\": ", ss,
					NULL));
			//stringFree(ss);
			//ss = NULL;
			return 1;
		}
		break;
	case CETYPE_FLAG:
		if(isStringBlank(value)) {
			if(cearray->data[index].i.datadefault == 0) {
				*cearray->data[index].i.data = 1;
			} else {
				*cearray->data[index].i.data = 0;
			}
			return 0;
		}
		//fallthrough
	case CETYPE_BOOL:
		switch(stringIntPairSearch(boollist, value)) {
		case 1:
			*cearray->data[index].i.data = 1;
			return 0;
			break;
		case 0:
			*cearray->data[index].i.data = 0;
			return 0;
			break;
		default: //case -1:
			logmsg(LOG_ERROR, stringCopyMany(
					"Bad value: key \"",
					key,
					"\", value \"",
					value,
					"\": Bad boolean value use 'true' or 'false'",
					NULL));
			return 1;
		}
		break;
	case CETYPE_STRING:
		//stringFree(*cearray->data[index].s.data);
		s = stringCopy(value);
		if(configValidate(cearray->data[index].validatefunc, cearray->data[index].funcextra, (void *)s, &ss) == 0) {
			stringFree(*cearray->data[index].s.data);
			*cearray->data[index].s.data = s;
			return 0;
		} else {
			logmsg(LOG_ERROR, stringCopyMany(
					"Bad value: Key \"", 
					key,
					"\" , value: \"",
					value,
					"\": ", ss,
					NULL));
			//stringFree(ss);
			//ss = NULL;
			return 1;
		}
		break;
	case CETYPE_FUNC:
		s = stringCopy(value);
		if(configValidate(cearray->data[index].validatefunc, cearray->data[index].funcextra, (void *)s, &ss) == 0) {
			(cearray->data[index].getset.setfunc)(cearray->data[index].funcextra, s);
			return 0;
		} else {
			logmsg(LOG_ERROR, stringCopyMany(
					"Bad value: Key \"", 
					key,
					"\" , value: \"",
					value,
					"\": ", ss,
					NULL));
			//stringFree(ss);
			//ss = NULL;
			return 1;
		}
		break;
	default:
		logmsg(LOG_ERROR, stringCopyMany(
				"Internal error: Key \"", 
				key,
				"\" , value: \"",
				value,
				"\": unrecognized config entry type.",
				NULL));
	}
	return 1;
}

/**
Reads a specific config file.
@param config The filename of the file to be read.
*/
void configReadFile(char *config) {
	int i;
	int j;
	StringPairArrayHandle *spa;

	if(isStringBlank(config)) {
		return;
	}
	spa = spaReadFile(config);
	if(spa == NULL) {
		return;
	}
	for(i = 0; i < spa->size; i++) {
		j = configMatchEntry(spa->data[i].key);
		if(j != -1) {
			configReadEntryData(j, spa->data[i].key, spa->data[i].val, NULL);
		} else {
			logmsg(LOG_ERROR, stringCopyMany(
					"Configuration file: unknown key \"",
					spa->data[i].key,
					"\", with value \"",
					spa->data[i].val,
					"\"",
					NULL));
			break;
		}
	}
	spaFree(spa);
}

/**
Read the set/specified config file.
*/
void configRead(void) {
	if(configfilename == NULL) {
		configReadFile(defaultconfigfilename);
	} else {
		configReadFile(configfilename);
	}
}

/**
Writes a specific config file.
@param config The filename of the file to be written.
*/
void configWriteFile(char *config) {
	FileHandle *fh;
	int i;
	int j;
	StringArrayHandle *sa;
	configInit();
	//todo:
	LOGDEBUG("configWriteFile: begin");
	LOGMEMDUMP();
	fh = fileOpen(config, "w");
	if(fh == NULL) {
		LOGMEMDUMP();
		//todo: error message
		return;
	}
	for(i = 0; i < cearray->size; i++) {
		if(cearray->data[i].name.name != NULL) {
			switch(cearray->data[i].type) {
			case CETYPE_FLAG:
			case CETYPE_BOOL:
				fileWriteString(fh, stringCopyMany(cearray->data[i].name.name, " = ", (*cearray->data[i].i.data)?"true":"false", "\n", NULL));
				break;
			case CETYPE_INT:
				fileWriteString(fh, stringJoinMany(stringCopy(cearray->data[i].name.name), " = ", intToString(*cearray->data[i].i.data), "\n", NULL));
				break;
			case CETYPE_STRING:
				if((*cearray->data[i].s.data) != NULL) {
					fileWriteString(fh, stringCopyMany(cearray->data[i].name.name, " = ", (*cearray->data[i].s.data), "\n", NULL));
				} else {
					fileWriteString(fh, stringCopyMany(cearray->data[i].name.name, " = \n", NULL));
				}
				break;
			case CETYPE_FUNC:
				(cearray->data[i].getset.getfunc)(cearray->data[i].funcextra, &sa);
				for(j = 0; j < sa->size; j++) {
					fileWriteString(fh, stringCopyMany(stringCopy(cearray->data[i].name.name), " = ", sa->data[j], "\n", NULL));
				}
				saFree(sa);
				break;
			}
		}
	}
	fileClose(fh);
	LOGDEBUG("configWriteFile: finsihed");
	LOGMEMDUMP();
}

/**
Writes to the set/specified config file.
*/
void configWrite(void) {
	if(configfilename == NULL) {
		configWriteFile(defaultconfigfilename);
	} else {
		configWriteFile(configfilename);
	}
}

/**
Reset all config parameters to defaults.
*/
void configReset(void) {
	int i;
	configInit();

	for(i = 0; i < cearray->size; i++) {
		switch(abs(cearray->data[i].type)) {
		case CETYPE_BOOL:
		case CETYPE_INT:
			*cearray->data[i].i.data = cearray->data[i].i.datadefault;
			break;
		case CETYPE_STRING:
			stringFree(*cearray->data[i].s.data);
			*cearray->data[i].s.data = stringCopy(cearray->data[i].s.datadefault);
			break;
		case CETYPE_FUNC:
			(cearray->data[i].getset.resetfunc)(cearray->data[i].funcextra);
			break;
		}
	}
}

/**
Sets the name of the configuration file or resets it to the default if filename is blank or NULL.
@param filename The filename to use.
*/
void setConfigFile(char *filename) {
	
	stringFree(configfilename);
	if(filename == NULL || strlen(filename) == 0) {
		configfilename = NULL;
	} else {
		configfilename = stringCopy(filename);
	}
}


/**
Show help for all config options with short help.
*/
void configShowHelp(void) {
	int i;
	int j;
	int n;
	char *s;
	char *ss;
	StringArrayHandle *sa;

	printf("Command line options:\n");
	sa = saMake();
	for(i = 0; i < cearray->size;) {
		if(cearray->data[i].help.shrt == NULL) {
			i++;
			continue;
		}
		s = cearray->data[i].help.shrt;
		for(j = i + 1, n = 0; j < cearray->size; j++, n++) {
			if(cearray->data[j].help.shrt == NULL || cearray->data[j].help.shrt == s) {
				continue;
			}
			break;
		}
		//todo: better formating
		j = i + n + 1;
		for(; i < j; i++) {
			if(!isStringBlank(cearray->data[i].name.name)) {
				saAppend(sa, stringAppend("--", cearray->data[i].name.name));
			}
			if(!isStringBlank(cearray->data[i].name.clname)) {
				saAppend(sa, stringAppend("-", cearray->data[i].name.clname));
			}
		}
		if(sa->size != 0) {
			ss = saImplode(sa, " , ");
			printf("%s", ss);
			printf("\t\t%s\n", _(s));
			stringFree(ss);
		}
		saDeleteMany(sa, 0, sa->size);
	}
	saFree(sa);
}

/**
Read configuration from the command line.
*/
void configReadCL(void) {
	int i;
	int j;
	char *s;
	char *c;
	char *l;
	for(i = 1; i < cmdline->size; i++) {
		s = cmdline->data[i];
		if(s[0] == '-' && stringLength(s) > 1) {
			s = stringCopy(s);
			/*find an '=' in this string*/
			c = strchr(s, '=');
			/*if it was found 0 it to terminate s before the '='*/
			if(c != NULL) {
				c[0] = 0;
				c++;
			}
			/*if wasn't check if the next string starts with an '='*/
			if(c == NULL) {
				if(i + 1 < cmdline->size) {
					if(cmdline->data[i + 1][0] == '=') {
						/*there is so go forward one string*/
						i++;
						c = cmdline->data[i] + 1;
					}
				}
			}
			/*if there were no '=' at all*/
			if(c == NULL) {
				/*check that the current string is a short option*/
				if(s[1] != '-') {
					/*if it is short mark the next */
					c = cmdline->data[i] + 2;
				}
			}
			/*if we have a possible parameter for the option.*/
			if(c != NULL) {
				/*if it is blank*/
				if(c[0] == 0) {
					/*and the next string is not an option.*/
					if(i + 1 < cmdline->size) {
						if(cmdline->data[i + 1][0] != '-') {
							/*make the next string the option to this parameter*/
							i++;
							c = cmdline->data[i];
						}
					}
				}
			}
			if(s[1] == '-') {
				/*long option*/
				j = configMatchEntry(s + 2);
			} else {
				/*short option*/
				if(s[1] != 0) {
					//c = s + 2;
					l = stringMake(1);
					l[0] = s[1];
					j = configMatchEntryCL(l);
					stringFree(l);
				} else {
					j = -1;
				}
			}
			if(j != -1) {
				if(c == NULL) {
					configReadEntryData(j, s, "", NULL);
				} else {
					configReadEntryData(j, s, c, NULL);
				}
			} else {
				LOGERROR(stringCopyMany("Config bad option(", s, "):", _("Unknown/unsupported option"), NULL));
			}
			stringFree(s);
		} else {
			LOGERROR(stringCopyMany("Config bad option(", s, "):", _("Unknown/unsupported option"), NULL));
		}
		
	}

}

/**
Resets reads configuration options from the command line and the preset config file.
*/
void configReadAll(void) {
	configReset();
	configReadCL();
	configRead();
	configReadCL();
}

/**
Sets the command line parameters.
@param argc main() style argc.
@param argv main() style argv.
*/
void configSetCL(int argc, char **argv) {
	int i;
	saFree(cmdline);
	cmdline = saMake();
	for(i = 0; i < argc; i++) {
		saAppendCopy(cmdline, argv[i]);
	}
}

#if defined(__WIN32__) || defined(WIN32) || defined(_WIN32)


/**
Reads windows command line options.
Converts a string into  int argc, char **argv  and then calls configSetCL() with them.
Treats all whitespaces as sperators between parameters.
@param lpcmdline The command line string.
*/
void configSetWinCL(LPSTR lpcmdline) {
	int i;
	int l;
	int ontext = FALSE;
	char *argbuffer = NULL;
	char **argv = NULL;
	int argc = 0;

	if (lpcmdline == NULL) return;

	//todo: change "iip " to what the actually program name is.
	argbuffer = stringAppend2("iip ", lpcmdline); 
	l = strlen(argbuffer);
	for (i = 0; i < l + 1; i++) { 
		switch(argbuffer[i]) {
		case 0: //null terminator
		case ' ': //space
		case '\t': //tab
			argbuffer[i] = 0;
			if(ontext) {
				ontext = FALSE;
			}
			break;
		default:
			if(!ontext) {
				argc++;
				argv = memResize(argv, argc * sizeof(char *));
				ontext = TRUE;
				argv[argc - 1] = argbuffer + i;
			}
		}
	}
	
	configSetCL(argc, argv);

	memFree(argv);
	stringFree(argbuffer);

}
  
#endif //WIN32

//@}
