/* mr_conf.c
 *
 * $Id$
 *
 * Based on the work done by Anton (c)2002-2004 Anton Kulchitsky  mailto:anton@kulchitsky.org
 * Code (c)2006 Bruno Cornec <bruno@mondorescue.org>
 *   
 *     Main file of mr_conf : a very small and simple
 *     library for configuration file reading
 * 
 * Provided under the GPLv2
 */


#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "mr_types.h"
#include "mr_msg.h"
#include "mr_mem.h"
#include "mr_gettext.h"

/* error flags */
#define MRCONF_NO_ERROR             0x0
#define MRCONF_BAD_FILE             0x1
#define MRCONF_READING_FAILED       0x3
#define MRCONF_NO_GROUP_START       0x4
#define MRCONF_NO_GROUP_END         0x5
#define MRCONF_FIELD_NOT_FOUND      0x6
#define MRCONF_FIELD_NO_VALUE       0x7
#define MRCONF_TOO_LONG_STRING      0x8
#define MRCONF_CLOSE_BUT_NOT_OPEN   0x9
#define MRCONF_OPEN_OPENED          0xA
#define MRCONF_CALL_BUT_NOT_OPEN    0xB
#define MRCONF_STRING_QUOTE		    0xC
#define MRCONF_STRING_ENDQUOTE	    0xD

/*setting flags*/
#define MRCONF_FLAG_VERBOSE         0x1

/*All strings of the library are here*/
#define MRCONF_STR_ERROR          _("MRCONF: Error:")
#define MRCONF_STR_WARNING        _("MRCONF: Warning:")

#define MRCONF_STR_BAD_FILE       _("cannot open the file: ")
#define MRCONF_STR_ALLOC_FAILED   _("cannot allocate the memory")
#define MRCONF_STR_READING_FAILED _("cannot read file into buffer")
#define MRCONF_STR_DEFAULT_ERROR  _("default")
#define MRCONF_STR_FIELD_NOT_FOUND   _("the field is not found, field:")
#define MRCONF_STR_FIELD_NO_VALUE   _("the field :")
#define MRCONF_STR_OPEN_OPENED _("attempt to open mr_conf, but it is opened: aborted")
#define MRCONF_STR_HALT _("MRCONF: Error occured: immidiate halt")

/*warnings*/
#define MRCONF_STR_IGNORE    _("has no value, ignoring it")
#define MRCONF_STR_CLOSE_BUT_NOT_OPEN _("attempt to close mr_conf but it has not been opened yet")
#define MRCONF_STR_CALL_BUT_NOT_OPEN _("attempt to use mr_conf when it has not been opened yet")
#define MRCONF_STR_STRING_QUOTE		_("string should be surrounded by quotes")
#define MRCONF_STR_STRING_ENDQUOTE		_("a string is not finished by a quote")

/*Flags of internal state*/
#define MRCONF_INTFLAG_OPEN 0x1	/*set if memory allocated */

/* Character for comments */
#define MRCONF_COMM_CHAR '#'

/*"private" members declarations*/
static size_t mr_conf_filesize(const char *name);
#define mr_conf_error_msg(x, y)	{mr_conf_error_msg_int(x, y, __LINE__,__FILE__);}
static void mr_conf_error_msg_int(int error_code, const char *add_line, int line, const char *file);
static void mr_conf_remove_comments(void);
static int mr_conf_check_int_flag(const int flag);
static void mr_conf_set_int_flag(const int flag);
static void mr_conf_drop_int_flag(const int flag);

/*global "private" variables*/
static char *buffer = NULL;		/*buffer for configuration file */
static int internal_flags = 0;	/*state of the module */
static FILE *CONF = NULL;		/* Configuration file FD */
static char *mr_conf_filename = NULL;	/* Configuration filename */

/*if output all error and warning messages*/
static int mr_conf_flags = MRCONF_FLAG_VERBOSE;

/* 
 * Format of the configuration file is as follows:
 *
 * attribute1 = int_value
 * attribute2 = float_value
 * attribute3 = string_value
 */


/*open and read file: each call must be coupled with mr_conf_close
  function: return 0 if success*/
int mr_conf_open(const char *filename) {
	size_t length;				/*length of the buffer/file */

	/* check if mr_conf is already opened? */
	if (mr_conf_check_int_flag(MRCONF_INTFLAG_OPEN)) {
		mr_conf_error_msg(MRCONF_OPEN_OPENED, NULL);
		return MRCONF_OPEN_OPENED;
	}

	length = mr_conf_filesize(filename);
	CONF = fopen(filename, "r");

	mr_asprintf(mr_conf_filename, "%s", filename);

	/*if file is empty or not exist => error */
	if (length == 0) {
		mr_conf_error_msg(MRCONF_BAD_FILE, filename);
		return (MRCONF_BAD_FILE);
	}

	/*creating and reading buffer for file content */

	/*allocate memory for the buffers */
	buffer = (char *) mr_malloc(sizeof(char) * (length + 1));

	/*set flag that module is in "open" state */
	mr_conf_set_int_flag(MRCONF_INTFLAG_OPEN);

	/*reading file in buffer (skip all 0 characters) */

	if (fread(buffer, sizeof(char), length, CONF)) {
		// FIXME
	}
	buffer[length] = (char) 0;	/*finalize the string */

	if (ferror(CONF)) {
		mr_conf_error_msg(MRCONF_READING_FAILED, "");
		return (MRCONF_READING_FAILED);
	}

	/* finally we have to remove all comment lines */
	mr_conf_remove_comments();

	return MRCONF_NO_ERROR;
}

/*release all memory and prepare to the next possiable config file*/
void mr_conf_close(void) {
	/* if not opened => error */
	if (!mr_conf_check_int_flag(MRCONF_INTFLAG_OPEN)) {
		mr_conf_error_msg(MRCONF_CLOSE_BUT_NOT_OPEN, NULL);
	}
	mr_free(buffer);
	fclose(CONF);

	mr_free(mr_conf_filename);

	/*set flag that module is in "close" state */
	mr_conf_drop_int_flag(MRCONF_INTFLAG_OPEN);
}

/*read field value after string str in the current file*/
static char *mr_conf_read(const char *field_name) {
	char *p = NULL;				/*pointer to the field */

	/* check if mr_conf is not yet opened? */
	if (!mr_conf_check_int_flag(MRCONF_INTFLAG_OPEN)) {
		mr_conf_error_msg(MRCONF_CALL_BUT_NOT_OPEN, NULL);
		return NULL;
	}

	/*read the number */
	p = strstr(buffer, field_name);
	if (p == NULL) {
		mr_conf_error_msg(MRCONF_FIELD_NOT_FOUND, field_name);
		return NULL;
	} else {
		p += strlen(field_name);
		while ((*p != '\n') && (*p != '\0') && (*p != '=')) {
				p++;
		}
		if (*p != '=') {
			mr_conf_error_msg(MRCONF_FIELD_NO_VALUE, field_name);
			return NULL;
		} else {
			/* points after the = sign */
			p++;
		}
	}
	/* skip initial spaces and tabs after = */
	while ((*p == ' ') || (*p == '\t')) {
			p++;
	}

	return p;
}

/*read integer number after string str in the current file*/
char *mr_conf_iread(const char *field_name) {
	char *p = NULL;				/*pointer to the field */
	char *p1 = NULL;			/*return field */

	p = mr_conf_read(field_name);
	if (p != NULL) {
		mr_asprintf(p1, "%d", p);
		}
	return p1;
}

/*read float/double number after string str in the current file*/
char *mr_conf_fread(const char *field_name) {
	char *p = NULL;				/*pointer to the field */
	char *p1 = NULL;			/*return field */

	p = mr_conf_read(field_name);
	if (p != NULL) {
		mr_asprintf(p1, "%f", p);
	}
	return p1;
}


/*
*/
char *mr_conf_sread(const char *field_name) {
	char *p = NULL;				/*pointer to the field */
	char *q = NULL;				/*pointer to the field */
	char *r = NULL;				/*pointer to the field */
	char *ret = NULL;			/*return value */
	int size = 0;				/*size of returned string */
	int i = 0;

	ret = NULL;

	p = mr_conf_read(field_name);
	if (p == NULL) {
		return(p);
	}
	mr_asprintf(q, "%s", p);

	if (*p != '"') {
		mr_conf_error_msg(MRCONF_STRING_QUOTE, field_name);
		return (NULL);
	}
	p++;

	/* trunk at first \n */
	r = index(q,'\n');
	r--;
	if (*r != '"') {
		mr_conf_error_msg(MRCONF_STRING_QUOTE, field_name);
		return (NULL);
	}
	r--;

	size = r-q+1;
	/*copy filtered data to the buffer */
	ret = (char *) mr_malloc(sizeof(char) * (size));
	while (i < size - 1) {
		ret[i] = *p;
		i++;
		p++;
	}

	ret[i] = (char) 0;		/* and set its length */
	mr_free(q);

	return ret;
}

/*read boolean after string str in the current file*/
char *mr_conf_bread(const char *field_name) {
	char *p = NULL;				/*pointer to the field */
	char *p1 = NULL;				/*pointer to the field */
	
	p = mr_conf_read(field_name);
	if (p != NULL) {
		/* match if yes/true/1 */
		if ((strncasecmp(p, "y" , (size_t)1) == 0) ||
			(strncasecmp(p, "t" , (size_t)1) == 0) ||
			(strncasecmp(p, "1" , (size_t)1) == 0)) {
			mr_asprintf(p1, "%d", TRUE);
		} else {
			mr_asprintf(p1, "%d", FALSE);
		}
	}
	return p1;
}

/* Convert a string with decimal value of TRUE/FALSE in boolean */
bool mr_atob(const char *str) {
	bool ret = FALSE;
	char *p = NULL;

	mr_asprintf(p, "%d", FALSE);
	if (strcmp(str,p) != 0) {
		ret = TRUE;
	}
	mr_free(p);
	return(ret);
}

/*removes all comments from the buffer*/
static void mr_conf_remove_comments(void) {
	char *tmp_buf;				/*temporary buffer without comments */
	size_t length				/*initial length */ ;
	size_t i;					/*iterator */
	size_t k;					/*conditioned iterator for tmp_buffer */
	bool found_quote = FALSE;
	bool found_comment = FALSE;

	length = strlen(buffer);

	/*sizing the new chain */
	k = 0;
	i = 0;
	while (i < length) {
		/* Handle quotes to detect strings */
		if ((buffer[i] == '"') && (! found_comment)) {
			if (found_quote) {
				found_quote = FALSE;
			} else {
				found_quote = TRUE;
			}
		}
		/* Handle start of comment - only when not in a string */
		if (buffer[i] == MRCONF_COMM_CHAR) {
			if (found_quote) {
				found_comment = FALSE;
			} else {
				found_comment = TRUE;
			}
		}
		/* Comments end with EOL */
		if (buffer[i] == '\n') {
			found_comment = FALSE;
		}
		if (! found_comment) {
			k++;
			i++;
		} else {
			/* Skip comment */
			while ((buffer[i] != '\n') && (buffer[i] != (char) 0)) {
				i++;
			}
			if (buffer[i] == (char) 0) {
				mr_conf_error_msg(MRCONF_STRING_ENDQUOTE, buffer);
			}
		}
	}
	/* k is new buffer length now */
	tmp_buf = (char *) mr_malloc(sizeof(char) * (k + 1));

	k = 0;
	i = 0;
	while (i < length) {
		if (buffer[i] == '"') {
			if (found_quote) {
				found_quote = FALSE;
			} else {
				found_quote = TRUE;
			}
		}
		if ((buffer[i] != MRCONF_COMM_CHAR) || (found_quote)) {
			tmp_buf[k++] = buffer[i++];
		} else {
			/* Skip comment as it's not inside a string */
			while ((buffer[i] != '\n') && (buffer[i] != (char) 0)) {
				i++;
			}
		}
	}
	tmp_buf[k] = (char) 0;		/*and set its length */

	mr_free(buffer);
	/*copy filtered data to the buffer */
	buffer = tmp_buf;
}

static int mr_conf_check_int_flag(const int flag) {
	return (flag & internal_flags);
}

static void mr_conf_set_int_flag(const int flag) {
	internal_flags = flag | internal_flags;
}

static void mr_conf_drop_int_flag(const int flag) {
	internal_flags = (~flag) & internal_flags;
}


/*local function to define size of a file. Return 0 for mistake*/
static size_t mr_conf_filesize(const char *name) {
	FILE *F = fopen(name, "r");	/*file to open */
	size_t length;				/*number to return */

	if (F == NULL) {
		return 0;
	}

	fseek(F, 0, SEEK_END);		/*set position to the end of file */
	length = ftell(F);			/*define number of position=> this is its
								   length */
	fclose(F);

	return length;
}

/*output error message*/
static void mr_conf_error_msg_int(int error_code, const char *add_line, int line, const char *file) {
	if ((mr_conf_flags & MRCONF_FLAG_VERBOSE)) {	/*if verbose mode */
		switch (error_code) {
		case MRCONF_BAD_FILE:
			mr_msg_int(1,line,file,"%s (%s) %s %s", MRCONF_STR_ERROR, mr_conf_filename, MRCONF_STR_BAD_FILE,
				   add_line);
			break;

		case MRCONF_READING_FAILED:
			mr_msg_int(1,line,file,"%s (%s) %s", MRCONF_STR_ERROR, mr_conf_filename, MRCONF_STR_READING_FAILED);
			break;

		case MRCONF_FIELD_NOT_FOUND:
			mr_msg_int(1,line,file,"%s (%s) %s \"%s\"", MRCONF_STR_WARNING, mr_conf_filename, MRCONF_STR_FIELD_NOT_FOUND, add_line);
			break;

		case MRCONF_FIELD_NO_VALUE:
			mr_msg_int(1,line,file,"%s (%s) %s \"%s\"", MRCONF_STR_ERROR, mr_conf_filename, MRCONF_STR_FIELD_NO_VALUE, add_line);
			mr_msg_int(1,line,file,"%s (%s) %s", MRCONF_STR_WARNING, mr_conf_filename, MRCONF_STR_IGNORE);
			break;

		case MRCONF_CLOSE_BUT_NOT_OPEN:
			mr_msg_int(1,line,file,"%s (%s) %s", MRCONF_STR_WARNING, mr_conf_filename, MRCONF_STR_CLOSE_BUT_NOT_OPEN);
			break;

		case MRCONF_CALL_BUT_NOT_OPEN:
			mr_msg_int(1,line,file,"%s (%s) %s", MRCONF_STR_WARNING, mr_conf_filename, MRCONF_STR_CALL_BUT_NOT_OPEN);
			break;

		case MRCONF_OPEN_OPENED:
			mr_msg_int(1,line,file,"%s (%s) %s", MRCONF_STR_ERROR, mr_conf_filename, MRCONF_STR_OPEN_OPENED);
			break;

		case MRCONF_STRING_QUOTE:
			mr_msg_int(1,line,file,"%s: %s (%s) %s", add_line, MRCONF_STR_ERROR, mr_conf_filename, MRCONF_STR_STRING_QUOTE);
			break;

		case MRCONF_STRING_ENDQUOTE:
			mr_msg_int(1,line,file,"%s: %s (%s) %s", add_line, MRCONF_STR_ERROR, mr_conf_filename, MRCONF_STR_STRING_ENDQUOTE);
			break;

		default:
			mr_msg_int(1,line,file,"%s (%s) %s", MRCONF_STR_ERROR, mr_conf_filename, MRCONF_STR_DEFAULT_ERROR);
			break;
		}
	}
}
