#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#define DMID 14
#include "debug.h"

#include "defines.h"
#include "lprintf.h"
#include "lfile.h"
#include "lib.h"
#include "tini.h"

#ifndef FALSE
#define FALSE 0
#endif

#ifndef TRUE
#define TRUE 1
#endif

/**************/

typedef struct tini_value {
	char **list;
	int n;
} TiniValue;

void t_free_value(TiniValue **v)
{
	if (!v || !*v) return ;
	free((*v)->list);
	free(*v);
	*v = NULL;
}

typedef struct tini_data {
	TiniValue **list;
	int n;
} TiniData;

void t_free_data(TiniData **d)
{
	int i;
	for(i = 0; i < (*d)->n; i++)
		t_free_value(&(*d)->list[i]);
	free((*d)->list);
	free(*d);
	*d = NULL;
}

struct tini {
	TiniSetting *table;
	TiniData **data;
	TiniData **dflt;
	int max_id;
};

extern int emsg(const char *arg_list, ...);

/**************/

void tini_abort(char *msg) {
	emsg("TINI: %s",msg);
	lib_abort();
}

TiniData *t_getdata(Tini *ini, int id)
{
	TiniData *p;	
	if (id >= ini->max_id) tini_abort("id out of range");
	p = ini->data[id];
	return p;
}

TiniData *t_getdflt(Tini *ini, int id)
{
	TiniData *p;
	if (id >= ini->max_id) tini_abort("id out of range");
	p = ini->dflt[id];
	return p;
}

char *t_getvalue(TiniData *d, int n, int p)
{
	if (!d) return NULL;
	if (n >= d->n) return NULL;
	if (p >= d->list[n]->n) return NULL;
	return d->list[n]->list[p];
}

//advances pline past leading spaces
//finds setting name if present and advances pline past it
//returns id of setting name if present
//if we are trying to skip only a certain 'expect'ed setting name specify it's id otherwise specify 0
int t_getid(Tini *ini, char **pline, int expect)
{	
	char *line = *pline;
	char name[BFSZ];	
	int i;

	//skip leading spaces
	for(line = *pline; sisspace(*line); line++) {}
	*pline = line;

	//get setting name
	lib_copy_upto_any(&line," \t\r\n",name,BFSZ);
	lib_skip_any(&line," \t\r\n");

	//find setting id
	if (expect < 0) expect = 0;
	for(i = expect; i < ini->max_id && (expect == 0 || i == expect); i++) {
		if (strcmpnc(ini->table[i].setting,name) != 0) continue;
		*pline = line;
		return i;
	}

	return -1;
}

//line MUST have setting name removed and no leading spaces
char **t_split_parse(TiniSetting *s, char *line, int *np)
{
	char **res = NULL;	
	int length,i,n;
	char *dup;

	//lets skip leading spaces to be super-safe
	for(i = 0; sisspace(line[i]); i++) {}
	dup = strdup(&line[i]);

	//strip trailing spaces
	for(i = 0; dup[i]; i++) {}
	for(i--; i >= 0 && sisspace(dup[i]); i--) dup[i] = 0;

	//count # of parts
	for(n = 1, i = 0; dup[i]; i++) {
		if (sisspace(dup[i]) && dup[i-1] != '\\')
			n++;
	}
	
	//allocate results
	res = calloc(n,sizeof(char *));
	*np = n;

	//parse
	res[0] = dup;
	length = lib_strlen(dup)+1;
	for(n = 1, i = 0; dup[i]; i++) {
		if (dup[i-1] == '\\') {
			//remove escaped characters
			memmove(&dup[i-1],&dup[i],length-i);
			length--;
		}
		else if (sisspace(dup[i])) {
			//split on spaces
			dup[i++] = 0;
			while(sisspace(dup[i])) i++;
			res[n++] = &dup[i];
		}
	}

	return res;
}

//line MUST have setting name removed and no leading spaces
void t_set(TiniSetting *s, TiniData **data, char *line)
{
	TiniData *d = data[s->id];
	TiniValue *v;

	if (!d) {
		d = calloc(1,sizeof(TiniData));
		data[s->id] = d;
	}

	if (s->flags & TINI_MULTI) {
		d->list = realloc(d->list,sizeof(TiniValue*) * (d->n+1));
	}
	else {
		if (!d->list) d->list = calloc(1,sizeof(TiniValue));
		else {
			t_free_value(&d->list[0]);
			d->n = 0;
		}
	}

	v = calloc(1,sizeof(TiniValue));
	v->list = t_split_parse(s,line,&v->n);
	d->list[d->n++] = v;
}

void t_rmv_all(TiniSetting *s, TiniData **data)
{
	if (!data[s->id]) return;
	t_free_data(&data[s->id]);
}

void t_rmv_one(TiniSetting *s, TiniData **data, int n)
{
	TiniData *d = data[s->id];
	if (!d) return;
	t_free_value(&d->list[n]);
	if (n != d->n-1) memmove(&d->list[n],&d->list[n+1],sizeof(TiniValue*)*(d->n-1-n));
	d->n--;
}

void t_load_dflts(Tini *ini)
{
	char *line;
	int i;

	for(i = 0; i < ini->max_id; i++) {
		if (!ini->table[i].dflt) continue;
		line = ini->table[i].dflt;
		t_getid(ini,&line,0);
		t_set(&ini->table[i],ini->dflt,line);
	}
}

/**************/

Tini *tini_init(TiniSetting *table)
{
	Tini *ini = NULL;
	int i;

	if (!table) return NULL;
	for(i = 0; table[i].setting; i++) {}

	ini = calloc(1,sizeof(Tini));	
	ini->data = calloc(i,sizeof(TiniData*));
	ini->dflt = calloc(i,sizeof(TiniData*));
	ini->table = table;
	ini->max_id = i;

	t_load_dflts(ini);

	return ini;
}

void tini_free(Tini **ini)
{
	int id;

	if (!ini || !*ini) return ;
	for(id = 0; id < (*ini)->max_id; id++) {
		if ((*ini)->data[id]) t_free_data(&(*ini)->data[id]);
		if ((*ini)->dflt[id]) t_free_data(&(*ini)->data[id]);
	}
	free((*ini)->data);
	free((*ini)->dflt);
	free(*ini);
	*ini = NULL;
}

/**************/

int tini_load(Tini *ini, char *file, char *err, int bfsz)
{
	char bf[BFSZ];
	LFILE *f;

	if (!ini) {
		lprintf(err,bfsz,"ini handle is NULL");
		return FALSE;
	}

	if (!file) {
		lprintf(err,bfsz,"file name is NULL");
		return FALSE;
	}

	f = lfile_fopen(file,"r",1,err,__LINE__);
	if (!f) {	
		int save_err = errno;
		//lprintf(err,bfsz,"read failed (%s)",file,strerror(errno));
		emsg("read failed (%s): %s %s",file,err,strerror(errno));
		errno = save_err;
		return FALSE;
	}

	while(lfile_fgets(bf,BFSZ,f)) {
		if (bf[0] == '#') continue;
		tini_set(ini,bf);
	}

	lfile_fclose(&f,__LINE__);
	return TRUE;
}

void t_write_value(LFILE *f, TiniSetting *s, TiniValue *v)
{
	char tmp[BFSZ];
	int i;

	if (!s) return ;
	if (!v) return ;
	for(i = 0; i < v->n; i++) {
		tini_quote_value(ncpy(tmp,v->list[i],BFSZ-1),BFSZ);
		lfile_fprintf(f," %s",tmp);
	}
}

void t_write_data(LFILE *f, TiniSetting *s, TiniData *d)
{
	int i;

	if (!s) return ;
	if (!d) return ;
	for(i = 0; i < d->n; i++) {
		lfile_fprintf(f,"%s",s->setting);
		t_write_value(f,s,d->list[i]);
		lfile_fprintf(f,"\n");
	}
}

int tini_save(Tini *ini, char *file, char *err, int bfsz)
{
	char tmp[BFSZ];
	LFILE *f;
	int id;

	if (!ini) {
		lprintf(err,bfsz,"ini handle is NULL");
		return FALSE;
	}

	if (!file) {
		lprintf(err,bfsz,"file name is NULL");
		return FALSE;
	}

	lprintf(tmp,BFSZ,"%s.tmp",file);
	f = lfile_fopen(tmp,"w",1,err,__LINE__);
	if (!f) {
		//lprintf(err,bfsz,"write failed (%s)",strerror(errno));
		emsg("write failed (%s):%s %s",tmp,err,strerror(errno));
		return FALSE;
	}

	for(id = 0; id < ini->max_id; id++) {
		if (!ini->data[id]) continue;
		t_write_data(f,&ini->table[id],ini->data[id]);
	}

	lfile_fclose(&f,__LINE__);

	if (lib_remove_with_retry(file) != 0) {
		lprintf(err,bfsz,"remove failed (%s)",strerror(errno));
		emsg("remove failed (%s):%s",file,strerror(errno));
		return FALSE;
	}

	if (lib_rename_with_retry(tmp,file) != 0) {
		lprintf(err,bfsz,"rename failed (%s)",strerror(errno));
		emsg("rename failed (%s)->(%s):%s",tmp,file,strerror(errno));
		return FALSE;
	}

	return TRUE;
}

/**************/

int tini_set(Tini *ini, char *line)
{
	int id;

	if (!ini) return FALSE;
	if (!line) return FALSE;
	id = t_getid(ini,&line,0);
	if (id == -1) return FALSE;
	t_set(&ini->table[id],ini->data,line);
	return TRUE;
}

int tini_set_id(Tini *ini, int id, char *line)
{
	if (!ini) return FALSE;
	if (!line) return FALSE;
	if (id >= ini->max_id) tini_abort("id out of range");
	t_getid(ini,&line,id);
	t_set(&ini->table[id],ini->data,line);
	return TRUE;
}

/**************/

void tini_clear(Tini *ini)
{
	int id;
	if (!ini) return ;
	for(id = 0; id < ini->max_id; id++)
		t_rmv_all(&ini->table[id],ini->data);
}

void tini_rmv(Tini *ini, int id)
{
	if (!ini) return ;
	if (id >= ini->max_id) tini_abort("id out of range");
	t_rmv_all(&ini->table[id],ini->data);
}

/**************/

char *tini_get(Tini *ini, int id)
{
	char *res = NULL;
	TiniData *d;	

	if (ini) {
		d = t_getdata(ini,id);
		res = t_getvalue(d,0,0);
		if (!res) {
			d = t_getdflt(ini,id);
			res = t_getvalue(d,0,0);
		}
	}
	return res;
}

int tini_true(Tini *ini, int id)
{
	char *p = tini_get(ini,id);
	if (p) return istrue(p);
	return FALSE;
}

int tini_get_i(Tini *ini, int id)
{
	char *p = tini_get(ini,id);
	if (p) return atoi(p);
	return 0;
}

char *tini_get_bf(Tini *ini, int id, char *bf, int bfsz)
{
	char *p = tini_get(ini,id);
	if (p) ncpy(bf,p,bfsz-1);
	else *bf = 0;
	return bf;
}

/**************/

struct titr {
	Tini *ini;
	int id;
	int n;
};

/**************/

Titr *tini_first(Tini *ini, int id)
{
	Titr *itr = NULL;
	TiniData *d;

	d = t_getdata(ini,id);
	if (d && d->n) {
		itr = calloc(1,sizeof(Titr));
		itr->ini = ini;
		itr->id = id;
	}
	return itr;
}

Titr *tini_valid(Titr *itr)
{
	if (itr) {
		TiniData *d = t_getdata(itr->ini,itr->id);

		if (itr->n < 0) itr->n = 0;
		if (itr->n >= d->n)
			tini_end(&itr);
	}
	return itr;
}

Titr *tini_next(Titr *itr)
{
	if (itr) {
		TiniData *d = t_getdata(itr->ini,itr->id);

		itr->n++;
		if (itr->n >= d->n)
			tini_end(&itr);
	}
	return itr;
}

void tini_end(Titr **itr)
{
	if (!itr || !*itr) return ;
	free(*itr);
	*itr = NULL;
}

/**************/

char *tini_cur(Titr *itr)
{
	TiniData *d;
	if (!itr) return NULL;
	d = t_getdata(itr->ini,itr->id);
	if (!d) return NULL;
	return t_getvalue(d,itr->n,0);
}

int tini_cur_true(Titr *itr)
{
	char *p = tini_cur(itr);
	if (p) return istrue(p);
	return FALSE;
}

int tini_cur_i(Titr *itr)
{
	char *p = tini_cur(itr);
	if (p) return atoi(p);
	return 0;
}

char *tini_cur_bf(Titr *itr, char *bf, int bfsz)
{
	char *p = tini_cur(itr);
	if (p) ncpy(bf,p,bfsz-1);
	else *bf = 0;
	return bf;
}

/**************/

char *tini_cur_p(Titr *itr, int part)
{
	TiniData *d;	
	if (!itr) return NULL;
	d = t_getdata(itr->ini,itr->id);
	if (!d) return NULL;
	return t_getvalue(d,itr->n,part);
}

int tini_cur_p_true(Titr *itr, int part)
{
	char *p = tini_cur_p(itr,part);
	if (p) return istrue(p);
	return FALSE;
}

int tini_cur_p_i(Titr *itr, int part)
{
	char *p = tini_cur_p(itr,part);
	if (p) return atoi(p);
	return 0;
}

char *tini_cur_p_bf(Titr *itr, int part, char *bf, int bfsz)
{
	char *p = tini_cur_p(itr,part);
	if (p) ncpy(bf,p,bfsz-1);
	else *bf = 0;
	return bf;
}

/**************/

void tini_cur_rmv(Titr *itr)
{
	if (!itr) return ;
	t_rmv_one(&itr->ini->table[itr->id],itr->ini->data,itr->n);
	itr->n--;
}

/**************/

int tini_quote_value(char *value, int bfsz)
{
	char *s;
	int len;
	
	len = lib_strlen(value);
	for(s = value; *s && len < bfsz-1; s++) {
		if (*s != ' ' && *s != '\\') continue;
		memmove(s+1,s,len-(s-value)+1);		
		*s++ = '\\';
		len++;
	}
	return len;
}
