/*
 *   This file is part of VBA Express.
 *
 *   Copyright (c) 2005-2006 Achraf cherti <achrafcherti@gmail.com>
 * 
 *   VBA Express 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.
 *
 *   VBA Express 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 VBA Express; if not, write to the Free Software
 *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

/**************************************
 * chargement et sauvegarde des
 * fichiers ini
 *
 * Licence: GPL
 *
 * Auteur: Achraf cherti
 * Email:  achrafcherti@gmail.com
 *************************************/

//notes: begin>end alors aucune section... ne rien faire

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

#define BINARY_HEADER "$$$LCFGBH$$$\n$$$A.C$$$\n$$$beGIn$$$\n"

static char *trim(char *str);
static LCFG_ELEMENT *add_element(LCFG *lcfg);
static char *lineinput(char *str, FILE* file);
static int traiter_line(LCFG_ELEMENT *e, char uncomment_var,char *line, char comment, char equal);
static char *premier(char *line,char c);
static int str_equal(const char *str1, const char *str2);
static int search_var(LCFG *lcfg, const char *var);

static int lcfg_save_binary_as(LCFG *lcfg, const char *filename);
static int lcfg_save_text_as(LCFG *lcfg, const char *filename);
static int lcfg_load_text(LCFG *lcfg, const char *filename);
static int lcfg_load_binary(LCFG *lcfg, const char *filename);

// *** ajout ***
static int lcfg_add(LCFG *lcfg, char type, const char *var, const char *value, const char *comment);

// *** Variables ***
static char default_comment='#'; //Le commentaire par défaut est...
static char default_equal='=';
static char default_autoindent=1;
static char default_guillemet=1;  //par défaut lors de l'ajout variable il y a guillemet

static size_t lcfg_fwrite(void *ptr, size_t size, size_t nmemb, FILE *file);
static size_t lcfg_fread(void *ptr, size_t size, size_t nmemb, FILE *file);
static void memcrypt(char *mem, size_t size);

// ** cryptage **
static const char *default_password=""; // mot de passe pour cryptage
static size_t pass_len=0;
static char _uncomment_var=0;
// des fichiers de configuration binaires...

/*****************************************
 * déclare globalement que tous les
 * ouvertures de fichiers config
 * prochaine vont décommenter les 
 * variables commentées d'avance.
 *****************************************/
void lcfg_set_uncomment_var(char enabled)
{
	_uncomment_var = enabled?1:0;
}

/*****************************************
 * changer le commentaire d'une variable
 * qui existe déjà.
 *
 * comment n'as pas le droit d'être null
 *
 * ret:
 * LCFG_OK
 * LCFG_E_NEXIST
 * LCFG_E_OUTMEM
 * LCFG_E_SECTION
 *****************************************/
int lcfg_var_comment(LCFG *lcfg, const char *var, const char *comment)
{
	int i;
	
	if(lcfg->end>=0 && lcfg->begin>lcfg->end) return LCFG_E_SECTION;
	i = search_var(lcfg,var);
	if(i==-1) return LCFG_E_NEXIST;
	
	{
		char *_comment=strdup(comment);
		if(!_comment) return LCFG_E_OUTMEM;

		//Mets le commentaire
		free(lcfg->el[i].comment);
		lcfg->el[i].comment=_comment;
	}
	
	return LCFG_OK;
}


/*****************************************
 * like ex. mais comment=0
 *****************************************/
int lcfg_var_value(LCFG *lcfg, const char *var, const char *value)
{
	return lcfg_var_value_ex(lcfg,var,value,0);
}

/*****************************************
 * Changer le contenu d'une 
 * variable 
 * si elle n'existe pas, elle est 
 * ajoutée dans "begin".
 *
 * comment peut être null
 * comment n'est mis que si la 
 * variable est ajoutée (quand elle
 * n'existe pas dans lcfg)
 *
 * ret:
 * ----
 * LCFG_OK
 * LCFG_E_OUTMEM
 * LCFG_E_SECTION
 *****************************************/
int lcfg_var_value_ex(LCFG *lcfg, const char *var, const char *value, const char *comment)
{
	int i;
	
	if(lcfg->end>=0 && lcfg->begin>lcfg->end) return LCFG_E_SECTION;
	i = search_var(lcfg,var);

	//si trouvé
	if(i!=-1) {
		char *_value = strdup(value);
		if(!_value) return LCFG_E_OUTMEM;
		free(lcfg->el[i].value);
		lcfg->el[i].value=_value; //mets la nouvelle valeur
		//petite modification si la variable est commentée... la décommenter :-)
		if(lcfg->el[i].type==LCFG_T_COMMENT_VAR) lcfg->el[i].type=LCFG_T_VAR;
		return LCFG_OK;
	}
	// S'il n'a pas trouvé...
	// alloquer pour ajouter nouveau
	// dans begin...
	else {
		LCFG_ELEMENT *save=lcfg->el;
		lcfg->el = (LCFG_ELEMENT *)realloc(lcfg->el, sizeof(LCFG_ELEMENT)*(lcfg->s_el+1));
		//outmem
		if(!lcfg->el) {
			lcfg->el = save;
			return LCFG_E_OUTMEM;
		}

		//Allocation des données
		{
			char *_value   = strdup(value);
			char *_var     = strdup(var);
			char *_comment = (comment)?strdup(comment):0;
			int begin,end;
			if(!_value || !_var || (comment && !_comment)) {
				//note: free ignore NULL
				free(_var);
				free(_value);
				free(_comment);
				return LCFG_E_OUTMEM;
			}

			//chercher la position ou mettre la nouvelle
			//var:
			//	* après toutes les LINE
			//	* Avant toutes les var
			//
			// ne mets jamais la var à la fin. Si c'est
			// le cas elle est dans l'avant dernière
			// ligne.
			//
			//mets begin et end
			end = (lcfg->end==-1)?lcfg->s_el-1:lcfg->end;
			begin=lcfg->begin; //cherche après BEGIN
			for(i=lcfg->begin;i<=end;i++) {
				//TODO: mettre qu'il mette var
				//après lignes de commentaires # avancée
				//pour cela créer nouveau type
				//de commentaires ligne:
				//LCFG_T_COMMENT
				if(lcfg->el[i].type==LCFG_T_VAR) break;

				//pour qu'il se mette après tous les commentaires
				//je l'ai mis après pour qu'il le mette
				//avant une variable ;)
				begin=i;
			}
			//toujours si begin=end alors begin=avant dernière
			if(begin==end) {
				begin=end-1;
				if(begin<lcfg->begin) 
					begin=lcfg->begin;
			}
			
			//free une place
			memmove(lcfg->el+begin+1,lcfg->el+begin,(lcfg->s_el - begin)*sizeof(LCFG_ELEMENT));

			//mets les valeurs dans cette place
			lcfg->el[begin].type      = LCFG_T_VAR;
			lcfg->el[begin].value     = _value;
			lcfg->el[begin].var       = _var;
			lcfg->el[begin].comment   = _comment;
			lcfg->el[begin].guillemet = default_guillemet;

			//Quelques mises à jour
			if(lcfg->end>=0) lcfg->end++;
			lcfg->s_el++;
		}
	}
	return LCFG_OK;
}

/*****************************************
 * pour la création manuelle d'un 
 * fichier de configuration...
 *****************************************/
void lcfg_new(LCFG *lcfg, char binary)
{
	memset(lcfg,0,sizeof(LCFG));
	lcfg->default_comment=default_comment; //Le commentaire par défaut
	lcfg->default_equal=default_equal; //Le commentaire par défaut
	lcfg->end=-1; // déterminer automatiquement...
	lcfg->autoindent=default_autoindent;
	lcfg->binary=binary;
	lcfg->uncomment_var=_uncomment_var;
}

/***************************
 * Ajoute une ligne
 * vide...
 *
 * ret: 
 * LCFG_E_OUTMEM
 * LCFG_OK
 **************************/
int lcfg_a_void(LCFG *lcfg)
{
	return lcfg_add(lcfg,LCFG_T_LINE,"\n",0,0);
}

/***************************
 * ADD COMMENT
 *
 * Ajoute un comment
 * à la fin.
 **************************/
int lcfg_a_comment(LCFG *lcfg, const char *comment)
{
	int n;
	
	char *var=(char *)malloc(strlen(comment)+3); // 3= # + \n + z
	if(!var) return LCFG_E_OUTMEM;
	
	var[0]=lcfg->default_comment;
	strcpy(var+1,comment);
	strcat(var+1,"\n");

	n=lcfg_add(lcfg,LCFG_T_LINE,var,0,0);
	
	free(var);
	return n;
}

/***************************
 * ADD SECTION 
 *
 * Ajoute une section
 * à la fin...
 **************************/
int lcfg_a_section(LCFG *lcfg, const char *section)
{ return lcfg_a_section_ex(lcfg,section,0); }

int lcfg_a_section_ex(LCFG *lcfg, const char *section, const char *comment)
{
return lcfg_add(lcfg,LCFG_T_SECTION,section,0,comment);
}

/***************************
 * Ajout de variable
 * à la fin...
 ***************************/
int lcfg_a_var(LCFG *lcfg, const char *var, const char *value)
{
	return lcfg_a_var_ex(lcfg,var,value,0);
}

int lcfg_a_var_ex(LCFG *lcfg, const char *var, const char *value, const char *comment)
{
	return lcfg_add(lcfg,LCFG_T_VAR,var,value,comment);
}

static int lcfg_add(LCFG *lcfg, char type, const char *var, const char *value, const char *comment)
{
	LCFG_ELEMENT *e=add_element(lcfg);
	int i;
	if(!e) return LCFG_E_OUTMEM; //pas assez de mémoire...
	
	// positionne i à la fin de l'élément!
	// à noter que le dernier élément est vidé avec add_element
	i = lcfg->s_el-1;

	//mets ce qu'il y a à mettre
	lcfg->el[i].type = type;

	//var
	lcfg->el[i].var = (!var)?(char *)malloc(1):(char *)malloc(strlen(var)+1);
	if(!lcfg->el[i].var) { lcfg->s_el--; return LCFG_E_OUTMEM; }
	if(!var) lcfg->el[i].var[0]=0; else strcpy(lcfg->el[i].var,var); 

	// value
	lcfg->el[i].value = (!value)?(char *)malloc(1):(char *)malloc(strlen(value)+1); 
	if(!lcfg->el[i].value) { free(lcfg->el[i].var); lcfg->s_el--; return LCFG_E_OUTMEM; }
	if(!value) lcfg->el[i].value[0]=0; else strcpy(lcfg->el[i].value,value); 

	//comment
	if(comment) {
		lcfg->el[i].comment = (!comment)?(char *)malloc(1):(char *)malloc(strlen(comment)+1); 
		if(!lcfg->el[i].comment) { free(lcfg->el[i].value); free(lcfg->el[i].var); lcfg->s_el--; return LCFG_E_OUTMEM; }
		if(!comment) lcfg->el[i].comment[0]=0; else strcpy(lcfg->el[i].comment,comment); 
	}

	//guillemet
	lcfg->el[i].guillemet = default_guillemet;

	return LCFG_OK; 
}


/*****************************************
 * CRYPTAGE
 *****************************************/
static void memcrypt(char *mem, size_t size)
{
	size_t i;
	size_t carac=0; //carac actuel ds le pws
	
	for(i=0;i<size;i++) {
		mem[i] = mem[i]^default_password[carac];
		carac++;
		if(carac>=pass_len) carac=0;
	}
}

// fread avec cryptage
static size_t lcfg_fread(void *ptr, size_t size, size_t nmemb, FILE *file)
{
	size_t n;
	n = fread(ptr,size,nmemb,file);
	memcrypt((char *)ptr,nmemb*size);
	return n;
}

// fwrite avec cryptage
static size_t lcfg_fwrite(void *ptr, size_t size, size_t nmemb, FILE *file)
{
	size_t n;
	memcrypt((char *)ptr,nmemb*size);
	n=fwrite(ptr,size,nmemb,file);
	memcrypt((char *)ptr,nmemb*size);
	return n;
}

/*****************************************
 * Change: equal, comment
 *****************************************/
void lcfg_set_guillemet(int enabled)
{ if(enabled) default_guillemet=1; else default_guillemet=0; }
void lcfg_set_password(const char *password) 
{ default_password=password; pass_len=strlen(password); }
void lcfg_set_autoindent(int enabled)
{ if(enabled) default_autoindent=1; else default_autoindent=0; }
void lcfg_set_equal(char equal)
{ default_equal=equal; }
void lcfg_set_comment(char comment)
{ default_comment=comment; }

/*****************************************
 * str_equal
 * si deux chaines sont égales...
 *
 * 0 si pas égales.
 ****************************************/
static int str_equal(const char *str1, const char *str2)
{
	if(!strcasecmp(str1,str2)) return 1;
	return 0;
}

/*****************************************
 * Change de section!
 *
 * si la section n'existe pas
 * elle est ajoutée à la fin
 *
 * si dernière ligne <> espace vide
 * alors il mets un retour à
 * la ligne avant section
 * pour une bonne mise en page
 * du fichier de configuration.
 *
 * ret:
 * LCFG_OK
 * LCFG_E_OUTMEM
 *****************************************/
int lcfg_set_section(LCFG *lcfg, const char *section)
{
	int i,j;
	int fin=lcfg->end;
	
	// Si null alors cela veut dire englober le tout!
	if(!section) {
		lcfg->begin=0;
		lcfg->end=-1;
		return 0;
	}
	
	// si fin<0 ou fin>=dernier element alors correction ;)
	if(fin<0 || fin>=lcfg->s_el) fin=lcfg->s_el-1;

	// parcours à la recherche de la section... 
	for(i=0;i<=fin;i++) {
		if(lcfg->el[i].type!=LCFG_T_SECTION) continue;

		if(str_equal(section,lcfg->el[i].var))
		{
			// cherche la fin de la section
			lcfg->begin=i+1;
			lcfg->end=-1; // met -1 on ne sait jamais (il pourrais ne pas la trouver la fin !)
			// recherche la fin de la section...
			for(j=i+1;j<=fin;j++) {
				if(lcfg->el[j].type==LCFG_T_SECTION) {
					lcfg->end=j-1; // et voilà !
					return 0; // et fin, sans erreurs !
				}
			}
			// ne change pas end parceque de toute façon 
			// elle indique la fin (-1)
			return 0; //pas d'erreur car trouvé!
		}
	}

	//la section n'existe pas...
	{
		int ret;
		//si la n'est pas ligne<>var ou section ajouter
		//un petit \n pour un bon indent du fichier
		//de configuration
		// TODO: Voir pq il n'ajoute jamais \n à la fin
		//       type!=LCFG_T_LINE ?
		//if(lcfg->s_el>0 && lcfg->el[lcfg->s_el-1].type!=LCFG_T_LINE) {
			ret = lcfg_a_void(lcfg);
			if(ret) {
				lcfg->begin=1; //rendre impossible l'ajout var
				lcfg->end=0;
				return ret;
			}
		//}
		
		//ajouter la section
		ret = lcfg_a_section(lcfg,section);
		if(ret) { 
			lcfg->begin=1; //rendre impossible l'ajout var
			lcfg->end=0;
			return ret;
		}

		//maintenant mets les positions
		lcfg->begin=lcfg->s_el; //on le connait car c la fin ;)
		lcfg->end=-1; //fin!
	}
	
	return LCFG_OK; //tout est ok
}


/*****************************************
 * chercher une variable dans une lint
 *
 * section=0 cela veut dire variable sans
 * prendre les sections en compte.
 *
 * var=0
 *
 * cela veut dire chercher une section.
 *
 * debut=0 c'est le début
 * fin=-1  la fin (calcul auto)
 *
 * search_var trim automatiquement var
 * si espace ou tab il y a.
 ****************************************/
static int search_var(LCFG *lcfg, const char *var)
{
	int i;
	int debut=lcfg->begin,fin=lcfg->end;

	// corrections sur début et fin...
	if(debut<0) debut=0;
	if(fin<0 || fin>=lcfg->s_el) fin=lcfg->s_el-1;
	if(debut>fin) return -1; //pas trouvé d'avance!
	
	// recherche...
	for(i=debut;i<=fin;i++) {
		if(lcfg->el[i].type!=LCFG_T_VAR && (lcfg->uncomment_var && lcfg->el[i].type!=LCFG_T_COMMENT_VAR)) continue;
		if(str_equal(var,lcfg->el[i].var))
			return i;
	}

	return -1; //err
}

/**************************************
 * Lecture numérique
 * 
 * retourne 0 si variable non trouvée.
 **************************************/
int lcfg_get_double(LCFG *lcfg, const char *var, double *out)
{
	char *s = lcfg_get_value(lcfg,var);
	if(!s) return 0;
	*out = atof(s);
	return 1;
}

int lcfg_get_long(LCFG *lcfg, const char *var, long *out)
{
	char *s = lcfg_get_value(lcfg,var);
	if(!s) return 0;
	*out = atol(s);
	return 1;
}

int lcfg_get_int(LCFG *lcfg, const char *var, int *out)
{
	char *s = lcfg_get_value(lcfg,var);
	if(!s) return 0;
	*out = atoi(s);
	return 1;
}

/**************************************
 * Retourne un pointeur vers value
 * correspondant à var.
 *
 * NULL si pas trouvé.
 **************************************/
char *lcfg_get_value(LCFG *lcfg, const char *var)
{
	int i = search_var(lcfg,var);
	if(lcfg->el[i].type==LCFG_T_COMMENT_VAR) return 0;
	if(i>=0) return lcfg->el[i].value;
	return 0;
}

/**************************************
 * unload
 * sauvegarde et désallocation.
 *************************************/
void lcfg_free(LCFG *lcfg)
{
	int i;
	if(!lcfg->el) {
		memset(lcfg,0,sizeof(LCFG));
		return;
	}
	for(i=0;i<lcfg->s_el;i++) {
		LCFG_ELEMENT *e=&lcfg->el[i];
		
		// si var alloquée
		if(e->var) free(e->var);

		// si value allquée
		if(e->value) free(e->value);

		//comment...
		if(e->comment) free(e->comment);

		//le surplus... (utilisé dans T_COMMENT_VAR) pour remettre le contenu comme il était au tout début
		if(e->extra) free(e->extra);
	}
	
	free(lcfg->el);

	memset(lcfg,0,sizeof(LCFG));
	return;
}

/**************************************
 * Forcer un chargement!
 **************************************/
int lcfg_load_ex(LCFG *lcfg, const char *filename, char binary)
{
	if(binary) 
		return lcfg_load_binary(lcfg,filename);
	else
		return lcfg_load_text(lcfg,filename);
}

/**************************************
 * charger un ini!
 **************************************/
int lcfg_load(LCFG *lcfg, const char *filename)
{
	char *header;
	size_t len=strlen(BINARY_HEADER);
	char binary=0;
	FILE *file;
	
	// Détection binaire/ascii
	// charge header
	file = fopen(filename,"rb");
	if(!file) return LCFG_E_FOPEN; 
	header=(char *)malloc(len); //sans z
	if(!header) { fclose(file); return LCFG_E_OUTMEM; }
	if(lcfg_fread(header,len,1,file)>=1) { 
		fclose(file); //pas la peine de gérer cette erreur...
		if(strncmp(header,BINARY_HEADER,len)==0) 
			binary=1;
	}
	free(header);

	// ouverture
	return (binary)?
		lcfg_load_binary(lcfg,filename)
		:
		lcfg_load_text(lcfg,filename);
}

//========================
// ouverture en binaire!
//========================
#define mread(ptr,sz) n=lcfg_fread(ptr,sz,1,file); if(sz!=0) ntest(1);
#define ntest(num) if(n<num) { lcfg_free(lcfg); fclose(file); return LCFG_E_FREAD; }
#define quit(ret) { lcfg_free(lcfg); fclose(file); return ret;  }
static int lcfg_load_binary(LCFG *lcfg, const char *filename)
{
	FILE *file;
	char *header;
	size_t len=strlen(BINARY_HEADER);
	size_t n;
	int i;
	char c; //comment enabled

	// ouverture
	file = fopen(filename,"rb");
	if(!file) return LCFG_E_FOPEN;
	
	// header
	header = (char *)malloc(len);
	if(lcfg_fread(header,len,1,file)<1) 
		{free(header); fclose(file); return LCFG_E_FFORMAT;}
	if(strncmp(header,BINARY_HEADER,len)!=0) {
		free(header);
		fclose(file);
		return LCFG_E_FFORMAT;
	}
	free(header);

	// charge quelques trucs
	lcfg_new(lcfg,1); 
	n=lcfg_fread(&lcfg->default_comment,sizeof(lcfg->default_comment),1,file);
	n+=lcfg_fread(&lcfg->default_equal,sizeof(lcfg->default_equal),1,file);
	n+=lcfg_fread(&lcfg->autoindent,sizeof(lcfg->autoindent),1,file);
	n+=lcfg_fread(&lcfg->s_el,sizeof(lcfg->s_el),1,file);
	if(n<4) {fclose(file); return LCFG_E_FREAD;}

	// malloque el[]
	lcfg->el = (LCFG_ELEMENT *)malloc(sizeof(LCFG_ELEMENT)*lcfg->s_el);
	if(!lcfg->el) { fclose(file); return LCFG_E_OUTMEM; }
	memset(lcfg->el,0,sizeof(LCFG_ELEMENT)*lcfg->s_el);

	// charge tt ls éléments
	for(i=0;i<lcfg->s_el;i++) {
		mread(&lcfg->el[i].type,1);

		switch(lcfg->el[i].type) {
			case LCFG_T_VAR:
				// Len
				mread(&len,sizeof(len)); 
				//Var
				if(!(lcfg->el[i].var = (char *)malloc(len+1))) {quit(LCFG_E_OUTMEM);}
				mread(lcfg->el[i].var,len); 
				lcfg->el[i].var[len]=0;
				
				// Len
				mread(&len,sizeof(len)); 
				//Value
				if(!(lcfg->el[i].value = (char *)malloc(len+1))) {quit(LCFG_E_OUTMEM);}
				mread(lcfg->el[i].value,len); 
				lcfg->el[i].value[len]=0;

				// guillemet
				mread(&lcfg->el[i].guillemet,1); 
				
				// comment enabled
				mread(&c,1); 

				if(c) {
					// COMMENT
					// Len
					mread(&len,sizeof(len)); 
					//Value
					if(!(lcfg->el[i].comment = (char *)malloc(len+1))) 
					{quit(LCFG_E_OUTMEM);}
					mread(lcfg->el[i].comment,len); 
					lcfg->el[i].comment[len]=0;
				}

				break;

			case LCFG_T_SECTION:
				// Len
				mread(&len,sizeof(len)); 
				//Var
				if(!(lcfg->el[i].var = (char *)malloc(len+1))) {quit(LCFG_E_OUTMEM);}
				mread(lcfg->el[i].var,len); 
				lcfg->el[i].var[len]=0;

				// comment enabled ??
				mread(&c,1); 
				if(c) {
					// alors ok charger un comment !!
					// Len
					mread(&len,sizeof(size_t)); 
					//Value
					if(!(lcfg->el[i].comment = (char *)malloc(len+1))) {quit(LCFG_E_OUTMEM);}
					mread(lcfg->el[i].comment,len); 
					lcfg->el[i].comment[len]=0;
				}

				break;

			case LCFG_T_LINE:
				// Len
				mread(&len,sizeof(len)); 
				//Value
				if(!(lcfg->el[i].var = (char *)malloc(len+1))) {quit(LCFG_E_OUTMEM);}
				mread(lcfg->el[i].var,len); 
				lcfg->el[i].var[len]=0;
				
				break;

			default:
				quit(LCFG_E_FFORMAT);
		}
	}

	if(fclose(file)) return LCFG_E_FCLOSE; 
	return LCFG_OK;
}

// chargement d'un fichier de configuration en format
// de texte...
static int lcfg_load_text(LCFG *lcfg, const char *filename)
{
	char *ptr=0;
	LCFG_ELEMENT *el;
	
	// ouverture
	FILE *file = fopen(filename,"r");
	if(!file) return LCFG_E_FOPEN;

	// init 
	lcfg_new(lcfg,0);

	// chargement de l'ini
	while(!feof(file)) 
	{
		// lit une ligne
		ptr = lineinput(ptr,file);
		if(!ptr) { //pas assez de mémire
			fclose(file); lcfg_free(lcfg);
			return LCFG_E_OUTMEM;
		}

		// Ajouter un élément
		el = add_element(lcfg);
		if(!el) {
			fclose(file); free(ptr); lcfg_free(lcfg);
			return LCFG_E_OUTMEM;
		}

		// mets line dans el selon si c'est une variable ou une
		// ligne normale
		if(traiter_line(el,lcfg->uncomment_var,ptr,lcfg->default_comment,lcfg->default_equal)) {
			fclose(file); free(ptr); lcfg_free(lcfg);
			return LCFG_E_OUTMEM;
		}
	}

	// mets le pointeur à 0
	// if(ptr) car: il se pourrait que le fichier soit
	// vide et que la boucle reçoit un break avant 
	// d'appeler lineinput.
	if(ptr) free(ptr);

	// ici traite l'erreor d'fclose aussi!
	if(fclose(file)) {
		lcfg_free(lcfg);
	   	return LCFG_E_FCLOSE;
	}
	
	return LCFG_OK;

}

/**************************************
 * cherche si c est le premier carac-
 * tere (sans compter les espace et
 * tab).
 *
 * '        # salut'
 *
 * par exemple là # est le premier.
 **************************************/
static char *premier(char *str, char c)
{
	while(!(*str==c || *str==0)) {
		if(*str!=' ' && *str!='\t') return 0; // rien à part espaces!
		str++;
	}
	if(*str!=c) str=0;
	return str;
}

/**************************************
 * fonction qui teste si c'est une
 * variable ou une ligne normale
 * et enfin mets le résultat dans e
 * qui est soit une ligne soit une
 * variable avec sa valeur.
 *
 * nonz pour erreur de mémoire.
 *
 * comment contient le caractère 
 * ascii du commentaire.
 * Par exemple: '#'.
 **************************************/
static int traiter_line(LCFG_ELEMENT *e, char uncomment_var, char *line, char comment, char equal)
{
	char *diese, *egal;
	char *s;
	char *crochet,*crochet2;
	char *g1,*g2;
	
	memset(e,0,sizeof(LCFG_ELEMENT));

	diese = premier(line,comment);
	egal  = index(line,equal);
	crochet = premier(line,'[');
	crochet2 = rindex(line,']');

	// Si c'est une variable = valeur
	// ou encore #variable=valeur (varible commentée...)
	if((!diese && egal) || (diese && uncomment_var && egal>diese)) {
		// init type
		if(diese) {
			e->type = LCFG_T_COMMENT_VAR; //variable commentée :-)
			//sauvegarder le contenu pour qu'il remette ça 
			//comme ce qui était avant
			e->extra=strdup(line);
			if(!e->extra) return LCFG_E_OUTMEM;
			//fait comme si la diese n'existait pas :-)
			line=diese+1;
			egal = index(line,equal);
		}
		else
			e->type = LCFG_T_VAR;

		// Enlever égal
		*egal=0;
		
		//init var
		s = trim(line); //prends var
		e->var=strdup(s);
		if(!e->var) return LCFG_E_OUTMEM;

		//enleve \n ds value
		s=rindex(egal+1,'\n'); if(s) *s=0;

		// Si il y a des guillemets
		s=egal+1; // value dans s
		
		// Dans le cas de var = "salut" # pipi roro
		g1 = premier(s,'\"');
		if(g1) {
			g2 = index(g1+1,'\"');
			if(!g2) {
				goto normal;
			}
			*g2=0; // enlève g2

			s=g2+1; // pour goto normal
			// le contenu de sera restoré à la fin
			// dans if(g1) ...

			e->guillemet = 1; //guillemets !

			// cherche le commentaire...
			goto normal;
		}
		// dans le cas normal: var = joumla zouina ! # popo
		else {
			normal:
			// enleve le commentaire normalement
			// la dernière #...
			diese = index(s,comment);
			if(diese) {
				*diese=0;
				e->comment=strdup(diese+1);
				if(!e->comment) return LCFG_E_OUTMEM;
			}
			s=trim(s);
		}

		// cette dernière if() sert pour la compatibilité
		// avec le goto.
		if(g1) 
			s=g1+1;
		else
			s=trim(egal+1);

		//init value
		e->value=strdup(s);
		if(!e->value) return LCFG_E_OUTMEM;
	}
	// Section!
	else if(crochet && crochet2) {
		crochet2 = rindex(line,']');
		if(crochet2) {
			*crochet2=0;
			crochet++;
			s = trim(crochet);

			// init type
			e->type = LCFG_T_SECTION;
			
			// var
			e->var = strdup(s);
			if(!e->var) return LCFG_E_OUTMEM;
		}
	}
	// Les autres choses
	else {
		e->type=LCFG_T_LINE;
		e->var =strdup(line);
		if(!e->var) return LCFG_E_OUTMEM;
	}

	return LCFG_OK;
}

/**************************************
 * charger une ligne entière
 * d'un fichier.
 *
 * n'enlève pas \n
 **************************************
 * str doit être soit null
 * soit pointeur malloc.
 * la fonction retourne une version
 * realloquée de str contenant
 * la ligne entière.
 **************************************
 * NULL si pas assez de mémoire...
 * str est désalloqué automatiquement.
 **************************************/
#define BUFFER_SIZE 1024
static char *lineinput(char *str, FILE* file)
{
	char buffer[BUFFER_SIZE];
	char *save;
	size_t len;

	// Vide str...
	str=(char *)realloc(save=str,1);
	if(!str) {
		free(save);
		return 0;
	}
	str[0]=0;
	
	//init
	len=0;

	// Commence le chargement de la ligne
	while(!(feof(file))) {
		buffer[0]=0;
		(char *)fgets(buffer,BUFFER_SIZE,file);
		len=+strlen(buffer);
		
		// Ajoute la chaine
		str = (char *)realloc(save=str,len+1);
		if(!str) { free(save); return 0; }
		strcat(str,buffer);
		
		// quitter si derniere
		if(len>0 && buffer[len-1]=='\n') break;
	}
	
	return str;
}
#undef BUFFER_SIZE


/********************************************
 * Ajoute un élément dans lcfg
 * et retourne le pointeur vers cet élément.
 * 
 * s'il n'est pas possible de l'ajouter
 * il retourne 0.
 ********************************************/
static LCFG_ELEMENT *add_element(LCFG *lcfg)
{
	LCFG_ELEMENT *ptr,*save;
	lcfg->el = (LCFG_ELEMENT *)realloc(save=lcfg->el, (++lcfg->s_el)*sizeof(LCFG_ELEMENT));
	// Pas assez de mémoire...
	if(!lcfg->el) {
		lcfg->el=save;
		// je ne fais pas ici memset car
		// unload a besoin du pointeur pour le désalloquer!
		return 0; 
	}

	// mets le pointeur alloqué à 0
	ptr = lcfg->el+lcfg->s_el-1;
	memset(ptr,0,sizeof(LCFG_ELEMENT));

	// et enfin retourne le pointeur
	return ptr;
}

/********************************************
 * Trim str
 ********************************************/
static char *trim(char *str)
{
	char *s=str+strlen(str)-1;
	if(s<str) return str;

	//rtrim
	while(*s==' ' || *s=='\t') {
		*(s--)=0;
		if(s==str-1) break;
	}

	//ltrim
	while(*str!=0 && (*str==' ' || *str=='\t')) {
		str++;
	}

	return str;
}

/************************************* 
 * SAVE TEXT
 *************************************/
static int lcfg_save_text_as(LCFG *lcfg, const char *filename)
{
	FILE *file = fopen(filename, "w");
	int i; // int pour se déplacer dans lcfg->s_el
	size_t j; //pour se déplacer dans les len
	size_t len;

	size_t max_len_var=0;   //mets le plus gros len de var et de value
	size_t max_len_value=0;  
	
	if(!file) 
		return LCFG_E_FOPEN;
	
	// calcul max_len_*
	for(i=0;i<lcfg->s_el;i++) {
		if(lcfg->el[i].type==LCFG_T_VAR || (lcfg->uncomment_var && lcfg->el[i].type==LCFG_T_COMMENT_VAR)) {
			size_t len=strlen(lcfg->el[i].var);
			if(max_len_var<len) max_len_var=len;

			len=strlen(lcfg->el[i].value);
			if(max_len_value<len) max_len_value=len;
		}
	}
	
	// sauvegarde
	for(i=0;i<lcfg->s_el;i++) {
		//variable commentée?
		//il réinitialise le tout comme ce qui était au tout début
		if(lcfg->el[i].type==LCFG_T_COMMENT_VAR) {
			len+=fprintf(file,"%s",lcfg->el[i].extra);
		}
		//variable normale
		else if(lcfg->el[i].type==LCFG_T_VAR) {
			if(lcfg->el[i].type==LCFG_T_COMMENT_VAR) len+=fprintf(file,"%c",lcfg->default_comment);

			// Afficher: var =
			len=fprintf(file,"%s",lcfg->el[i].var);

			// Affiche les espaces pour un bon affichage!
			if(lcfg->autoindent)
				for(j=len;j<=max_len_var+1;j++)  // +1 espaces
					fputc(' ',file);
			//else TODO faire espace personnalisé
			//	fprintf(file," ");
			
			// Afficher '= '
			len+=fprintf(file,"%c",lcfg->default_equal);

			// Afficher la valeur de la variable
			len=0; //=0 pour le commentaire
			if(lcfg->el[i].guillemet)
				len+=fprintf(file,"\"%s\"",lcfg->el[i].value)-1; //2="" 1=espace
			else
				len+=fprintf(file,"%s",lcfg->el[i].value)-1; //1=espace

			// tout cela pour toi oh commentaire ;)
			if(lcfg->el[i].comment)  {
				if(lcfg->autoindent)
					for(j=len;j<=max_len_value+4;j++) // +4 espaces
						fputc(' ',file);
				else
					fprintf(file,"\t\t\t");

				// Affiche le commentaire, si commentaire il y a
				fprintf(file,"%c%s",lcfg->default_comment,lcfg->el[i].comment);
			}
			fprintf(file,"\n");
		}
		// SECTION!
		if(lcfg->el[i].type==LCFG_T_SECTION) {
			fprintf(file,"[ %s ]",lcfg->el[i].var);
			
			if(lcfg->el[i].comment) 
				fprintf(file,"\t\t%c%s",lcfg->default_comment,lcfg->el[i].comment);
			fprintf(file,"\n");
		}

		// si ligne normale, écrite telle qu'elle est!
		else if(lcfg->el[i].type==LCFG_T_LINE) 
			fprintf(file,"%s",lcfg->el[i].var);

	}
	
	if(fclose(file)) return LCFG_E_FCLOSE;
	
	return LCFG_OK;
}

/************************************* 
 * SAVE BINARY
 *
 * Format de fichier:
 * ==================
 * HEADER
 * char default_comment
 * char default_equal
 * char autoindent
 * s_el (int)
 *
 * tout les éléments
 *
 * élément:
 * ========
 *    char type
 *    
 *    si VAR
 *    ======
 *    size_t len_var
 *    char *var (avec zero)
 *
 *    size_t len_value
 *    char *value (avec zero)
 *
 *    char guillemet
 *
 *    char comment_enabled
 *    si oui:
 *    	size_t len_comment
 *    	char *comment
 *
 *    si SECTON:
 *    ==========
 *    char *var
 *
 *    char comment_enabled
 *    si oui:
 *    	size_t len_comment;
 *    	char *comment
 * 
 *    Autre:
 *    ======
 *    size_t len
 *    char *var
 *
 *************************************/
static int lcfg_save_binary_as(LCFG *lcfg, const char *filename)
{
	FILE *file = fopen(filename,"wb");
	char header[]=BINARY_HEADER;
	int i;
	size_t n=0;
	char c;
	size_t len;
	
	//TODO: Ajouter au lieu de remove un fcopy /tmp/fichier dans fichier
	//efface c mieux...
	
	// ouverture!
	if(!file) return LCFG_E_FOPEN;

	// Header
	n+=lcfg_fwrite(header,strlen(header),1,file);
	
	// Trucs de début
	n+=lcfg_fwrite(&lcfg->default_comment,sizeof(lcfg->default_comment),1,file);
	n+=lcfg_fwrite(&lcfg->default_equal,sizeof(lcfg->default_equal),1,file);
	n+=lcfg_fwrite(&lcfg->autoindent,sizeof(lcfg->autoindent),1,file);
	n+=lcfg_fwrite(&lcfg->s_el,sizeof(lcfg->s_el),1,file);
	if(n<5) { fclose(file); remove(filename); return LCFG_E_FWRITE; }

	// sauvegarde le tout!
	for(i=0;i<lcfg->s_el;i++) {
		n=lcfg_fwrite(&lcfg->el[i].type,sizeof(lcfg->el[i].type),1,file);

		switch(lcfg->el[i].type) 
		{
			// VAR
			case LCFG_T_VAR:
				// save VAR
				len=strlen(lcfg->el[i].var);
				n+=lcfg_fwrite(&len,sizeof(len),1,file);
				n+=lcfg_fwrite(lcfg->el[i].var,len,1,file);
				if(len==0) n++; //bien sûre... sinon il croira qu'il
				                //ne l'as pas lu...

				// save VALUE
				len=strlen(lcfg->el[i].value);
				n+=lcfg_fwrite(&len,sizeof(len),1,file);
				n+=lcfg_fwrite(lcfg->el[i].value,len,1,file);
				if(len==0) n++; //bien sûre... sinon il croira qu'il
				                //ne l'as pas lu...

				// guillemet
				n+=lcfg_fwrite(&lcfg->el[i].guillemet,sizeof(char),1,file);
				
				// save 
				if(lcfg->el[i].comment) {
					//comment enabled
					c=1;
					n+=lcfg_fwrite(&c,sizeof(c),1,file);
					
					// comment
					len=strlen(lcfg->el[i].comment);
					n+=lcfg_fwrite(&len,sizeof(len),1,file);
					n+=lcfg_fwrite(lcfg->el[i].comment,len,1,file);
					if(len==0) n++; //bien sûre... sinon il croira qu'il
				                //ne l'as pas lu...

					if(n<9) { fclose(file); remove(filename); return LCFG_E_FWRITE; }
					//9 car il prends en compte le premier n= avant switch
				}
				else {
					c=0; //comment=false
					n+=lcfg_fwrite(&c,sizeof(c),1,file);
					if(n<7) { fclose(file); remove(filename); return LCFG_E_FWRITE; }
				}
					
				break;
				
			case LCFG_T_SECTION:
				// save VAR
				len=strlen(lcfg->el[i].var);
				n+=lcfg_fwrite(&len,sizeof(len),1,file);
				n+=lcfg_fwrite(lcfg->el[i].var,len,1,file);
				if(len==0) n++;

				//comment...
				if(lcfg->el[i].comment) {
					//comment_enabled
					c=1;
					n+=lcfg_fwrite(&c,len,1,file);

					// comment...
					len=strlen(lcfg->el[i].comment);
					n+=lcfg_fwrite(&len,sizeof(len),1,file);
					n+=lcfg_fwrite(lcfg->el[i].comment,len,1,file);
					if(len==0) n++; //bien sûre... sinon il croira qu'il
					                //ne l'as pas lu...
					if(n<6) { fclose(file); remove(filename); return LCFG_E_FWRITE; }
				}
				else {
					c=0;
					n+=lcfg_fwrite(&c,len,1,file);
					if(n<4) { fclose(file); remove(filename); return LCFG_E_FWRITE; }
				}
				break;

			case LCFG_T_LINE:
				// line !
				len=strlen(lcfg->el[i].var);
				n+=lcfg_fwrite(&len,sizeof(len),1,file);
				n+=lcfg_fwrite(lcfg->el[i].var,len,1,file);
				if(len==0) n++; //bien sûre... sinon il croira qu'il
				                //ne l'as pas lu...
				
				if(n<3) { fclose(file); remove(filename); return LCFG_E_FWRITE; }
				break;
		}
	}

	// début!
	if(fclose(file)) return LCFG_E_FCLOSE; //pas de remove ici

	return LCFG_OK;
}

/************************************* 
 * SAVE AS (générale)
 *************************************/
int lcfg_save(LCFG *lcfg, const char *filename)
{ return lcfg_save_ex(lcfg,filename,lcfg->binary); }

int lcfg_save_ex(LCFG *lcfg, const char *filename, char binary)
{
	return (binary)?
		lcfg_save_binary_as(lcfg,filename)
		:
		lcfg_save_text_as(lcfg,filename);
}

