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

#include "../include/string.h"
#include "../include/fio.h"
#include "../include/disk.h"

#include "cfg.h"
#include "cfg_fio.h"
#include "config.h"


gint CFGFileOpen(
	const gchar *filename, cfg_item_struct *cfg_list
);
gint CFGFileSave(
	const gchar *filename, const cfg_item_struct *cfg_list
);


#ifndef CFG_COMMENT_CHAR
# define CFG_COMMENT_CHAR		'#'
#endif

#ifndef CFG_DELIMINATOR_CHAR
# define CFG_DELIMINATOR_CHAR	'='
#endif

#define ATOI(s)         (((s) != NULL) ? atoi(s) : 0)
#define ATOL(s)         (((s) != NULL) ? atol(s) : 0)
#define ATOF(s)         (((s) != NULL) ? atof(s) : 0.0f)
#define STRDUP(s)       (((s) != NULL) ? g_strdup(s) : NULL)

#define MAX(a,b)        (((a) > (b)) ? (a) : (b))
#define MIN(a,b)        (((a) < (b)) ? (a) : (b))
#define CLIP(a,l,h)     (MIN(MAX((a),(l)),(h)))
#define STRLEN(s)       (((s) != NULL) ? strlen(s) : 0)
#define STRISEMPTY(s)   (((s) != NULL) ? (*(s) == '\0') : TRUE)


/*
 *	Loads the cfg list from the specified file and stores the
 *	loaded values in to the given cfg list.
 *
 *	Returns:
 *
 *	0	Success.
 *	-1	General error (including no such file).
 *	-2	Bad value.
 *	-3	System error.
 */
gint CFGFileOpen(
	const gchar *filename, cfg_item_struct *cfg_list
)
{
	gboolean ignore_all_errors = FALSE;
	gint i;
	FILE *fp;
	struct stat stat_buf;
	gchar *parm = NULL;

	if(STRISEMPTY(filename) || (cfg_list == NULL))
	    return(-2);

	if(stat(filename, &stat_buf))
	{
#if 0
	    g_printerr(
		"%s: Warning: No such file.\n",
		filename
	    );
#endif
	    return(-1);
	}
#ifdef S_ISREG
	if(!S_ISREG(stat_buf.st_mode))
	{
	    g_printerr(
		"%s: Error: Not a file.\n",
		filename
	    );
	    return(-2);
	}
#endif

	/* Open the configuration file for reading */
	fp = FOpen((const char *)filename, "rb");
	if(fp == NULL)
	{
	    g_printerr(
		"%s: Error: Unable to open file for reading.\n",
		filename
	    );
	    return(-1);
	}

	/* Begin reading configuration file */
	while(TRUE)
	{
	    parm = (gchar *)FSeekNextParm(
		fp, (char *)parm,
		CFG_COMMENT_CHAR, CFG_DELIMINATOR_CHAR
	    );
	    if(parm == NULL)
		break;

	    /* Match parameter from cfg list */
	    i = CFGItemListMatchParameter(cfg_list, parm);
	    if(i < 0)
	    {
		/* No such parameter */
		if(!ignore_all_errors)
		    g_printerr(
"%s: Warning: Unsupported parameter \"%s\".\n",
			filename,
			parm
		    );
		FSeekNextLine(fp);
	    }
	    else
	    {
		cfg_item_struct *ci = &cfg_list[i];
		gchar *s;
		gint vi[10];
		glong vl[10];
		gdouble vd[10];

		gint8		i8;
		guint8		ui8;
		gint16		i16;
		guint16		ui16;
		gint32 		i32;
		guint32		ui32;
		gint64		i64;
		guint64		ui64;
		gfloat		f;
		gdouble		d;
		cfg_color_struct color;

		switch(ci->type)
		{
		  case CFG_ITEM_TYPE_INT8:
		    FGetValuesI(fp, (int *)vi, 1);
		    i8 = (gint8)vi[0];
		    CFGItemSetValue(ci, &i8);
		    break;
		  case CFG_ITEM_TYPE_UINT8:
		    FGetValuesI(fp, (int *)vi, 1);
		    ui8 = (guint8)vi[0];
		    CFGItemSetValue(ci, &ui8);
		    break;

		  case CFG_ITEM_TYPE_INT16:
		    FGetValuesI(fp, (int *)vi, 1);
		    i16 = (gint16)vi[0];
		    CFGItemSetValue(ci, &i16);
		    break;
		  case CFG_ITEM_TYPE_UINT16:
		    FGetValuesI(fp, (int *)vi, 1);  
		    ui16 = (guint16)vi[0];
		    CFGItemSetValue(ci, &ui16);
		    break;

		  case CFG_ITEM_TYPE_INT32:
		    FGetValuesI(fp, (int *)vi, 1);
		    i32 = (gint32)vi[0];
		    CFGItemSetValue(ci, &i32);
		    break;
		  case CFG_ITEM_TYPE_UINT32:
		    FGetValuesI(fp, (int *)vi, 1);
		    ui32 = (guint32)vi[0];
		    CFGItemSetValue(ci, &ui32);
		    break;

		  case CFG_ITEM_TYPE_INT64:
		    FGetValuesL(fp, (long *)vl, 1);
		    i64 = (gint64)vl[0];
		    CFGItemSetValue(ci, &i64);
		    break;
		  case CFG_ITEM_TYPE_UINT64:
		    FGetValuesL(fp, (long *)vl, 1);
		    ui64 = (guint64)vl[0];
		    CFGItemSetValue(ci, &ui64);
		    break;

		  case CFG_ITEM_TYPE_FLOAT:
		    FGetValuesF(fp, (double *)vd, 1);
		    f = (gfloat)vd[0];
		    CFGItemSetValue(ci, &f);
		    break;
		  case CFG_ITEM_TYPE_DOUBLE:
		    FGetValuesF(fp, (double *)vd, 1);
		    d = (gdouble)vd[0];
		    CFGItemSetValue(ci, &d);
		    break;

		  case CFG_ITEM_TYPE_STRING:
		    g_free(ci->value);
		    ci->value = FGetString(fp);
		    break;

		  case CFG_ITEM_TYPE_INTLIST:
		    s = (gchar *)FGetString(fp);
		    if(s != NULL)
		    {
			gchar *s2 = s;
			cfg_intlist_struct *intlist = CFGIntListNew(NULL);

			/* Delete existing Cfg Item value (if any) */
			CFGItemResetValue(ci);

			/* Format:
			 *
			 * <i1> [i2] [i3] [i4] [...]
			 */
			while(*s2 != '\0')
			{
			    while(ISSPACE(*s2))
				s2++;

			    intlist->list = g_list_append(
				intlist->list,
				(gpointer)ATOI(s2)
			    );

			    /* Seek s2 to next value or end of string */
			    while(!ISSPACE(*s2) && (*s2 != '\0'))
				s2++;
			}

			/* Set value to cfg item */
			ci->value = intlist;

			/* Delete string */
			g_free(s);
		    }
		    break;

		  case CFG_ITEM_TYPE_COLOR:   
		    /* Format:
		     *
		     * <r> <g> <b> <a>
		     */
		    FGetValuesF(fp, (double *)vd, 4);
		    color.r = (gfloat)vd[0];
		    color.g = (gfloat)vd[1];
		    color.b = (gfloat)vd[2];
		    color.a = (gfloat)vd[3];
		    CFGItemSetValue(ci, &color);
		    break;

		  case CFG_ITEM_TYPE_ACCELKEY_LIST:
		    s = (gchar *)FGetString(fp);
		    if(s != NULL)
		    {
			gchar *s2 = s;
			GList *glist, *akey_glist;
			gint opid;
			guint key, modifiers;
			cfg_accelkey_struct *akey;
			cfg_accelkey_list_struct
			    *akey_list_src = CFG_ACCELKEY_LIST(ci->value),
			    *akey_list = CFGAccelkeyListNew(
				(akey_list_src != NULL) ?
				    akey_list_src->list : NULL
			    );

			/* Delete existing Cfg Item value (if any) */
			CFGItemResetValue(ci);
			akey_list_src = NULL;

			/* Unable to create new Accelkey List? */
			if(akey_list == NULL)
			{
			    g_free(s);
			    break;
			}

			akey_glist = akey_list->list;

			/* Format:
			 *
			 * <opid1> <key1> <modifiers1>
			 * [opid2] [key2] [modifiers2]
			 * [opid3] [key3] [modifiers3]
			 * [...]
			 */
			while(*s2 != '\0')
			{
#define SEEK_NEXT	{		\
 while(!ISSPACE(*s2) && (*s2 != '\0'))	\
  s2++;					\
 while(ISSPACE(*s2))			\
  s2++;					\
}

			    while(ISSPACE(*s2))
				s2++;

			    opid = ATOI(s2);
			    SEEK_NEXT
			    key = (guint)ATOI(s2);
			    SEEK_NEXT
			    modifiers = (guint)ATOI(s2);
			    SEEK_NEXT

			    /* Check if this Accelkey's opid with one
			     * in the Accelkey List
			     */
			    for(glist = akey_glist;
				glist != NULL;
				glist = g_list_next(glist)
			    )
			    {
				akey = CFG_ACCELKEY(glist->data);
				if(akey == NULL)
				    continue;
				if(akey->opid <= 0)
				    continue;

				if(akey->opid == opid)
				{
				    akey->key = key;
				    akey->modifiers = modifiers;
				    break;
				}
			    }
#undef SEEK_NEXT
			}

			/* Set value to cfg item */
			ci->value = akey_list;

			/* Delete string */
			g_free(s);
		    }
		    break;

		  case CFG_ITEM_TYPE_STYLE:
		    s = (gchar *)FGetString(fp);
		    if(s != NULL)
		    {
			gint n;
			gchar *s2 = s, *s3;
			cfg_color_struct *c;
			cfg_style_struct *style = CFGStyleNew();

#define SEEK_NEXT	{		\
 while(!ISSPACE(*s2) && (*s2 != '\0'))	\
  s2++;					\
 while(ISSPACE(*s2))			\
  s2++;					\
}
#define GET_COLOR	{		\
 c->r = ATOF(s2);			\
 SEEK_NEXT				\
 c->g = ATOF(s2);			\
 SEEK_NEXT				\
 c->b = ATOF(s2);			\
 SEEK_NEXT				\
 c->a = ATOF(s2);			\
 SEEK_NEXT				\
}

			/* Delete existing Cfg Item value (if any) */
			CFGItemResetValue(ci);

			/* Format:
			 *
			 * <font_name>
			 * (following repeats 5 times for the 5 states)
			 * <fg_rgba> <bg_rgba> <text_rgba> <base_rgba>
			 * <bg_pixmap_name>
			 */
			if(!strcasepfx(s2, "*none*"))
			{
			    style->font_name = STRDUP(s2);
			    s3 = strpbrk(style->font_name, " \t");
			    if(s3 != NULL)
				*s3 = '\0';
			}
			SEEK_NEXT

			for(n = 0; n < 5; n++)
			{
			    style->color_flags[n] |= CFG_STYLE_FG |
				CFG_STYLE_BG | CFG_STYLE_TEXT |
				CFG_STYLE_BASE;
			    c = &style->fg[n];
			    GET_COLOR
			    c = &style->bg[n];
			    GET_COLOR
			    c = &style->text[n];
			    GET_COLOR
			    c = &style->base[n];
			    GET_COLOR

			    /* bg_pixmap_name is defined if the first
			     * character is not a '-'
			     */
			    if(!strcasepfx(s2, "*none*"))
			    {
				style->bg_pixmap_name[n] = STRDUP(s2);
				s3 = strpbrk(style->bg_pixmap_name[n], " \t");
				if(s3 != NULL)
				    *s3 = '\0';
			    }
			    SEEK_NEXT
			}

			/* Set value to cfg item */
			ci->value = style;

			/* Delete string */
			g_free(s);
#undef GET_COLOR
#undef SEEK_NEXT
		    }
		    break;

		  default:
		    /* Unsupported value type */
		    if(!ignore_all_errors)
			g_printerr(
"%s: Warning: Unsupported value type \"%i\" for parameter \"%s\".\n",
			    filename,
			    ci->type,
			    parm
			);
		    FSeekNextLine(fp);
		    break;
		}
	    }
	}

	g_free(parm);

	/* Close configuration file */
	FClose(fp);

	return(0);
}


/*
 *	Saves the given cfg list to the specified file.
 *
 *	Returns:
 *
 *	0	Success
 *	-1	General error (including unable to open file for writing)
 *	-2	Bad value
 *	-3	System error
 */
gint CFGFileSave(
	const gchar *filename, const cfg_item_struct *cfg_list
)
{
	gint i;
	gchar *parent;
	FILE *fp;
	struct stat stat_buf;
	const cfg_item_struct *ci;

	if(STRISEMPTY(filename) || (cfg_list == NULL))
	    return(-2);

	/* Check if the file already exists */
	if(!stat((const char *)filename, &stat_buf))
	{
#ifdef S_ISDIR
	    /* Check if it is a directory */
	    if(S_ISDIR(stat_buf.st_mode))
	    {
		g_printerr(
"%s: Is a directory.\n",
		    filename
		);
		return(-2);
	    }
#endif
	}

	/* Get the parent directory and create it as needed */
	parent = g_dirname(filename);
	if(parent != NULL)
	{
	    const mode_t m = umask(0);
	    gint status;

	    umask(m);

	    status = rmkdir(
		(char *)parent,
		(~m) &
		    (S_IRUSR | S_IWUSR | S_IXUSR |
		     S_IRGRP | S_IWGRP | S_IXGRP |
		     S_IROTH | S_IWOTH | S_IXOTH)
	    );

	    g_free(parent);

	    if(status)
	    {
		g_printerr(
"%s: Unable to create the parent directory.\n",
		    filename
		);
		return(-1);
	    }
	}

	/* Open configuration file for writing */
	fp = fopen((const char *)filename, "wb");
	if(fp == NULL)
	{
	    const gint error_code = (gint)errno;
	    gchar *error_msg = STRDUP(g_strerror(error_code));
	    if(error_msg != NULL)
	    {
		*error_msg = toupper((int)*error_msg);
		g_printerr(
"%s: %s.\n",
		    filename, error_msg
		);
		g_free(error_msg);
	    }
	    return(-1);
	}

	/* Begin writing configuration file */
	i = 0;
	ci = &cfg_list[i];
	while((ci->type != 0) &&
	      (ci->parameter != NULL)
	)
	{
	    if(!STRISEMPTY(ci->parameter))
	    {
		const gchar *parm = ci->parameter;
		const gpointer value = ci->value;
		const cfg_intlist_struct *intlist;
		const cfg_color_struct *color;
		const cfg_accelkey_list_struct *accelkey_list;
		const cfg_style_struct *style;

		switch(ci->type)
		{
		  case CFG_ITEM_TYPE_NONE:
		    break;

		  case CFG_ITEM_TYPE_INT8:
		    fprintf(
			fp,
			"%s %c %i\n",
			parm, CFG_DELIMINATOR_CHAR,
			(value != NULL) ?
			    (int)(*(gint8 *)value) : 0
		    );
		    break;
		  case CFG_ITEM_TYPE_UINT8:
		    fprintf(
			fp,
			"%s %c %i\n",
			parm, CFG_DELIMINATOR_CHAR,
			(value != NULL) ?
			    (unsigned int)(*(guint8 *)value) : 0
		    ); 
		    break;

		  case CFG_ITEM_TYPE_INT16:
		    fprintf(
			fp,
			"%s %c %i\n",
			parm, CFG_DELIMINATOR_CHAR,
			(value != NULL) ?
			    (int)(*(gint16 *)value) : 0
		    );
		    break;
		  case CFG_ITEM_TYPE_UINT16:
		    fprintf(
			fp,
			"%s %c %i\n",
			parm, CFG_DELIMINATOR_CHAR,
			(value != NULL) ?
			    (unsigned int)(*(guint16 *)value) : 0
 		    );
		    break;

		  case CFG_ITEM_TYPE_INT32:
		    fprintf(
			fp,
			"%s %c %i\n",
			parm, CFG_DELIMINATOR_CHAR,
			(value != NULL) ?
			    (int)(*(gint32 *)value) : 0
		    );
		    break;
		  case CFG_ITEM_TYPE_UINT32:
		    fprintf(
			fp,
			"%s %c %i\n",
			parm, CFG_DELIMINATOR_CHAR,
			(value != NULL) ?
			    (unsigned int)(*(guint32 *)value) : 0
		    );
		    break;

		  case CFG_ITEM_TYPE_INT64:
		    fprintf(
			fp,
			"%s %c %ld\n",
			parm, CFG_DELIMINATOR_CHAR,
			(value != NULL) ?
			    (long)(*(gint64 *)value) : 0
		    );
		    break;
		  case CFG_ITEM_TYPE_UINT64:
		    fprintf(
			fp,
			"%s %c %ld\n",
			parm, CFG_DELIMINATOR_CHAR,
			(value != NULL) ?
			    (unsigned long)(*(guint64 *)value) : 0
  		    );
		    break;

		  case CFG_ITEM_TYPE_FLOAT:
		    fprintf(
			fp,
			"%s %c %f\n",
			parm, CFG_DELIMINATOR_CHAR,
			(value != NULL) ?
			    (float)(*(gfloat *)value) : 0.0f
 		    );
		    break;
		  case CFG_ITEM_TYPE_DOUBLE:
		    fprintf(
			fp,
			"%s %c %f\n",
			parm, CFG_DELIMINATOR_CHAR,
			(value != NULL) ?
			    (double)(*(gdouble *)value) : 0.0
		    );
		    break;

		  case CFG_ITEM_TYPE_STRING:
		    fprintf(
			fp,
			"%s %c %s\n",
			parm, CFG_DELIMINATOR_CHAR,
			(value != NULL) ?
			    (const char *)((gchar *)value) : ""
		    );
		    break;

		  case CFG_ITEM_TYPE_INTLIST:
		    /* Format:
		     *
		     * <i>
		     * (repeats...)
		     */
		    intlist = CFG_INTLIST(value);
		    fprintf(
			fp,
			"%s %c ",
			parm, CFG_DELIMINATOR_CHAR
		    );
		    if(intlist != NULL)
		    {
			GList *glist = intlist->list;
			while(glist != NULL)
			{
			    fprintf(fp, "%i", (int)glist->data);
			    glist = g_list_next(glist);
			    if(glist != NULL)
				fprintf(fp, " ");
			}
		    }
		    fprintf(fp, "\n");
		    break;

		  case CFG_ITEM_TYPE_COLOR:
		    /* Format:
		     *
		     * <r> <g> <b> <a>
		     */
		    color = CFG_COLOR(value);
		    fprintf(
			fp,
			"%s %c %f %f %f %f\n",
			parm, CFG_DELIMINATOR_CHAR,
			(value != NULL) ?
			    (float)color->r : 0.0f,
			(value != NULL) ?
			    (float)color->g : 0.0f,
			(value != NULL) ?   
			    (float)color->b : 0.0f,
			(value != NULL) ?
			    (float)color->a : 0.0f
		    );
		    break;

		  case CFG_ITEM_TYPE_ACCELKEY_LIST:
		    /* Format:
		     *
		     * <opid1> <key1> <modifiers1>
		     * [opid2] [key2] [modifiers2]
		     * [opid3] [key3] [modifiers3]
		     * [...]
		     */
		    accelkey_list = CFG_ACCELKEY_LIST(value);
		    fprintf(
			fp,
			"%s %c ",
			parm, CFG_DELIMINATOR_CHAR
		    );
		    if(accelkey_list != NULL)
		    {
			cfg_accelkey_struct *akey;
			GList *glist = accelkey_list->list;
			while(glist != NULL)
			{
			    akey = CFG_ACCELKEY(glist->data);
			    if(akey == NULL)
			    {
				glist = g_list_next(glist);
				continue;
			    }
			    if(akey->opid <= 0)
			    {
				glist = g_list_next(glist);
				continue;
			    }

			    fprintf(
				fp, "%i %i %i",
				(int)akey->opid,
				(int)akey->key,
				(int)akey->modifiers
			    );
			    glist = g_list_next(glist);
			    if(glist != NULL)
				fprintf(fp, "\\\n ");
			}
		    }
		    fprintf(fp, "\n");  
		    break;

		  case CFG_ITEM_TYPE_STYLE:
		    /* Format:
		     *
		     * <font_name>
		     * (repeat 5 times for 5 states)
		     * <fg_rgba> <bg_rgba> <text_rgba> <base_rgba>
		     * <bg_pixmap_name>
		     */
		    style = CFG_STYLE(value);
		    fprintf(
			fp,
			"%s %c ",
			parm, CFG_DELIMINATOR_CHAR
		    );
		    if(style != NULL)
		    {
			gint n;
			const cfg_color_struct *c;

			fprintf(
			    fp,
			    "%s\\\n ",
			    STRISEMPTY(style->font_name) ?
				"*none*" : (const char *)style->font_name
			);

			for(n = 0; n < CFG_STYLE_STATES; n++)
			{
			    c = &style->fg[n];
			    fprintf(
				fp,
				"%f %f %f %f\\\n ",
				(float)c->r, (float)c->g, (float)c->b, (float)c->a
			    );
			    c = &style->bg[n];
			    fprintf(
				fp,
				"%f %f %f %f\\\n ",
				(float)c->r, (float)c->g, (float)c->b, (float)c->a
			    );
			    c = &style->text[n];
			    fprintf(
				fp,
				"%f %f %f %f\\\n ",
				(float)c->r, (float)c->g, (float)c->b, (float)c->a
			    );
			    c = &style->base[n];
			    fprintf(
				fp,
				"%f %f %f %f\\\n ",
				(float)c->r, (float)c->g, (float)c->b, (float)c->a
			    );
			    fprintf(
				fp,
				"%s",
				STRISEMPTY(style->bg_pixmap_name[n]) ?
				    "*none*" : (const char *)style->bg_pixmap_name[n]
			    );
			    /* Not last state? */
			    if(n < (CFG_STYLE_STATES - 1))
				fprintf(fp, "\\\n ");
			}
		    }
		    fprintf(fp, "\n");
		    break;


		}
	    }

	    i++;
	    ci = &cfg_list[i];
	}

	/* Close configuration file */
	FClose(fp);

	return(0);
}
