#include "settings.h"
#include "../common/strutils.h"

#include <stdio.h>
#include <windows.h>

static Settings *g_settings;
//---------------------------------------------------------------------------------
#ifdef X2BC_USE_INI_FORMATS 
bool settings_parse_pair_const(const char *pszSectionName, Settings::conststringpair &p)
{
	return g_settings->parseConstant(pszSectionName, p);
}
//---------------------------------------------------------------------------------
bool settings_parse_pair_format(const char *pszSectionName, Settings::conststringpair &p)
{
	return g_settings->parseFormat(pszSectionName, p);
}
//---------------------------------------------------------------------------------
int CompareStatFormats(const void *a, const void *b)
{
	int ida, idb;
	ida=((Settings::StatFormat*)a)->id;
	idb=((Settings::StatFormat*)b)->id;
	
	return ((Settings::StatFormat*)a)->id - ((Settings::StatFormat*)b)->id;
}
#endif // defined(X2BC_USE_INI_FORMATS )
//---------------------------------------------------------------------------------
bool Settings::loadINI(const char *pszFileName)
{
	bool bRes;
	char szSectionName[50];
	
	g_settings=this;
	clearErrors();
	
	GetPrivateProfileString("library", "WriteWarnings", "off", szSectionName, sizeof(szSectionName), pszFileName);
	m_bOutputWarnings=(stricmp(szSectionName, "on")==0);
	
	GetPrivateProfileString("library", "Convert", "on", szSectionName, sizeof(szSectionName), pszFileName);
	m_bConvert=(stricmp(szSectionName, "on")==0);
	
	GetPrivateProfileString("library", "FrameWarnings", "on", szSectionName, sizeof(szSectionName), pszFileName);
	m_bStatFormatWarnings=(stricmp(szSectionName, "on")==0);
	
	GetPrivateProfileString("library", "XtraPointInfo", "on", szSectionName, sizeof(szSectionName), pszFileName);
	m_bXtraPntInfo=(stricmp(szSectionName, "on")==0);
	
	GetPrivateProfileString("library", "XtraX3BobInfo", "off", szSectionName, sizeof(szSectionName), pszFileName);
	m_bXtraX3BobInfo=(stricmp(szSectionName, "on")==0);
	
#ifdef X2BC_USE_INI_FORMATS
	size_t size;
	
	GetPrivateProfileString("library", "Constants", "Const", szSectionName, sizeof(szSectionName), pszFileName);
	
	char *pszConstants=readSection(szSectionName, pszFileName, &size);
	if(size==0){
		error(Error, E_SectionEmpty, "Section with constants '%s' not defined or empty.", szSectionName);
		bRes=false;
	}
	else
		bRes=parseLines(szSectionName, pszConstants, size, settings_parse_pair_const);
		
	delete[] pszConstants;
	
	if(bRes){
		GetPrivateProfileString("library", "Default", "Format", szSectionName, sizeof(szSectionName), pszFileName);
		char *pszFormat=readSection(szSectionName, pszFileName, &size);
		if(size==0){
			error(Error, E_SectionEmpty, "Section with frame formats '%s' not defined or empty.", szSectionName);
			bRes=false;
		}
		else
			bRes=parseLines(szSectionName, pszFormat, size, settings_parse_pair_format);

		delete[] pszFormat;
	}
#else
	bRes=true;
#endif // !defined(X2BC_USE_INI_FORMATS)
	return bRes;
}
//---------------------------------------------------------------------------------
void Settings::error(ErrorType type, ErrorCodes code, const char *format, ...)
{
	va_list ap;
	va_start(ap, format);
	
	ErrorMsg *e=new ErrorMsg();
	e->type=type;
	e->code=code;
	
	_vsnprintf(e->message, sizeof(e->message), format, ap);
	va_end(ap);
	
	errors.push_back(e);
}
//---------------------------------------------------------------------------------

#ifdef X2BC_USE_INI_FORMATS
char * Settings::readSection(const char *pszSection, const char *pszFileName, size_t *size)
{
	char *data;
	int _size=0, res;
	
	do{
		_size+=1500;
		data=new char[_size];
		if(data==NULL) break;
		res=GetPrivateProfileSection(pszSection, data, _size, pszFileName);
		if(res==_size - 2){
			delete[] data;
		}
	}
	while(res==_size - 2);
	if(size) *size=res;
	return data;
}
//---------------------------------------------------------------------------------
bool Settings::parseLines(const char *pszSectionName, char *pszData, size_t size, ParsePairCallback *callback)
{
	size_t count;
	char **lines=lineexplode(pszData, size, &count);
	bool bRes=true;
	
	for(size_t i=0; i < count; i++){
		char *line=lines[i];
	
		int j=0;
		bool bComment=false;
		char *equal, *left, *right;
		
		left=right=equal=0;
		
		while(line[j]!=0){
			switch(line[j]){
				case '=':
					line[j]=0;
					equal=line + j;
					left=line;
					break;
				case ';':
					line[j]=0;
					j--; // so the main while(...) will encouter this added 0 and terminate
					break;
			}
			j++;
		}
		if(equal){
			right=equal + 1;
		
			conststringpair p=parsePair(left, right);
			if(callback(pszSectionName, p)==false) {
				bRes=false;
				break; // break if parsing fails
			}
		}
		else
			error(Warning, W_LineIgnored, "Section \"%s\": line '%s' ignored.", pszSectionName, line);
	}
	
	delete[] lines;
	return bRes;
}
//---------------------------------------------------------------------------------
// trim white spaces from left and right
Settings::conststringpair Settings::parsePair(const char *left, const char *right)
{
	conststringpair p;
	p.left=trim((char*)left);
	p.right=trim((char*)right);
	return p;
}
//---------------------------------------------------------------------------------
bool Settings::parseConstant(const char *pszSectionName, Settings::conststringpair &p)
{
	bool res=true;
	NumberFormat *f=new NumberFormat();
	const char *pszMultiplier=0, *pszFormat=0;
	char *left, *right;
	
	// backup the original strings for error reporting
	size_t len=strlen(p.left);
	left=new char[len + 1];
	memcpy(left, p.left, len + 1);
	len=strlen(p.right);
	right=new char[len + 1];
	memcpy(right, p.right, len + 1);
	
	// the left operand (constant name)
	if(*p.left==0){
		error(ErrorType::Error, E_BadConstantDeclaration, "Section \"%s\": line: '%s = %s':\nMissing constant name.", pszSectionName, left, right);
		res=false;
	}
	else if(strlen(p.left) > 1){
		error(ErrorType::Error, E_BadConstantDeclaration, "Section \"%s\": line: '%s = %s':\nConstant name must be exactly 1 character.", pszSectionName, left, right);
		res=false;
	}
	else if(!((*p.left | 0x20) > 'a') && ((*p.left | 0x20) < 'z')){
		error(Error, E_BadConstantDeclaration, "Section \"%s\": Constant \"%c\": constant name must be a upper or lowercase character (A - Z).", pszSectionName, *p.left);
		res=false;
	}
	if(res==false){
		delete[] left; delete[] right;
		return res;
	}
	f->type=*p.left;
	
	// the right operand (constant multiplier and format)
	pszMultiplier=p.right;
	char *pos=strchr(p.right, ',');
	if(pos){
		*pos=0;
		pszFormat=trim(++pos);
	}	
	if(pszMultiplier==0 || *pszMultiplier==0){
		error(ErrorType::Error, E_BadConstantDeclaration, "Section \"%s\": line: '%s = %s':\nMissing constant multiplier.", pszSectionName, left, right);
		res=false;
	}
	else if(pszFormat==0 || *pszFormat==0){	
		error(ErrorType::Error, E_BadConstantDeclaration, "Section \"%s\": line: '%s = %s':\nMissing output format specifier.", pszSectionName, left, right);
		res=false;
	}
	else{
		f->multiplier=atof(pszMultiplier);
		if(f->multiplier==0){
			error(ErrorType::Warning, W_ConstantAlwaysZero, "Section \"%s\": line: '%s = %s':\nMultiplier is 0. Result will be always zero. Is this the intent?", pszSectionName, left, right);
		}
	}
	if(res==true){
		if(stricmp(pszFormat, "integer")==0)
			f->outformat=NumberFormat::Integer;
		else if(stricmp(pszFormat, "float")==0)
			f->outformat=NumberFormat::Float;
		else{
			error(ErrorType::Error, E_BadConstantDeclaration, "Section \"%s\": line: '%s = %s':\nUnknown output format '%s'.", pszSectionName, left, right, pszFormat);
			res=false;
		}
	}
	delete[] left;
	delete[] right;
	
	if(res)
		m_constants.push_back(f);
	
	return res;
}
//---------------------------------------------------------------------------------
bool Settings::parseFormat(const char *pszSectionName, conststringpair &p)
{
	bool bRes;
	switch(p.left[0]){
		case 0:
			error(ErrorType::Error, E_BadFormatDeclaration, "Section \"%s\": invalid line format: '%s = %s':\nID expected.", pszSectionName, p.left, p.right);
			bRes=false;
			break;
		case '$':
			bRes=parseStatFormat(pszSectionName, p);
			break;
		case 'N': // don't read the N constants - we use algorithm instead if INI
			bRes=true;
			break;
		default:
			error(Warning, W_LineIgnored, "Section \"%s\": ignoring line '%s = %s':\nUnknown ID type.", pszSectionName, p.left, p.right);
			bRes=true;
		
	}
	return bRes;
}
//---------------------------------------------------------------------------------
bool Settings::parseStatFormat(const char *pszSectionName, conststringpair &p)
{
	char *left, *right;
	bool bRes=true;
	// backup the original strings for error reporting
	size_t len=strlen(p.left);
	left=new char[len + 1];
	memcpy(left, p.left, len + 1);
	len=strlen(p.right);
	right=new char[len + 1];
	memcpy(right, p.right, len + 1);
	
	StatFormat *f=new StatFormat();
	
	if(p.left[0]==0){
		error(ErrorType::Error, E_BadFormatDeclaration, "Section \"%s\": line '%s = %s':\nMissing ID.", pszSectionName, left, right);
		bRes=false;
	}
	else{
		if(p.left[0]=='$') p.left++;
		f->id=hextoi(p.left);
	}
	if(bRes){
		char *ch=(char*)p.right;
		if(*ch!='"'){
			error(ErrorType::Error, E_BadFormatDeclaration, "Section \"%s\": line '%s = %s':\nExpected begining of string after \"%s =\".", pszSectionName, left, right, left);
			bRes=false;
		}
		else{
			ch++;
			while(*ch!=0 && *ch!='"') ch++;
			if(*ch==0){
				error(ErrorType::Error, E_BadFormatDeclaration, "Section \"%s\": line '%s = %s':\nExpected end of string after \"%s\".", pszSectionName, left, right, right);
				bRes=false;
			}
			else{
				*ch=0;
				bRes=parseFormatString(pszSectionName, f, (char*)p.right + 1);
				if(bRes) formats.push_back(f);
			}
		}
	}
	
	return bRes;
}
//---------------------------------------------------------------------------------
bool isnum(char c)
{
	return (c >= '0' && c <= '9');
}
//---------------------------------------------------------------------------------
bool Settings::parseFormatString(const char *pszSectionName, Settings::StatFormat *f, char *format)
{
	bool bRes=true;
	
	char *ch, *start;
	start=format;
	
	// if format begins with - a warning will be issued if such format is used in bob file
	if(*start!=0 && *start=='-') {
		start++;
		f->issueWarning=true;
	}
	// skip any spaces after the '-'
	while(*start==' ') start++;
	
	while(*start!=0){
		ch=start;
		if(isnum(*ch)==false){
			error(Error, E_BadFormatDeclaration, "Section \"%s\": line id $%x:\nMissing number at start of '%s'.", pszSectionName, f->id, format);
			return false;
		}
		else{
			while(isnum(*ch)) ch++;
			if(*ch==0){
				error(Error, E_BadFormatDeclaration, "Section \"%s\": line id $%x:\nMissing constant after '%s'.", pszSectionName, f->id, ch);
				return false;
			}
			else{
				StatFormat::token *t=new StatFormat::token();
				char old=*ch;
				*ch=0;
				t->count=atoi(start);
				t->type=old;
				*ch=old;
				
				t->numFormat=findNumberFormat(t->type);
				if(t->numFormat==0){
					error(Error, E_BadFormatDeclaration, "Section \"%s\": line id $%x:\nConstant \"%c\" not defined in '%s'.", pszSectionName, f->id, t->type, format);
					delete t;
					return false;
				}
				f->tokens.push_back(t);
				// increase the cumulative count so it's easy to tell how many numbers is needed 
				// to match the format
				f->cumulativeTokenCount+=t->count; 
				
				start=ch+1;
				while(*ch!=0 && isnum(*ch)==false) ch++;
				old=*ch;
				*ch=0;
				size_t size=ch-start;
				if(size){
					t->data=new char[size + 1];
					memcpy(t->data, start, size + 1);
				}
				*ch=old;
				start=ch;
			}
		}
	}
	return bRes;
}
//---------------------------------------------------------------------------------
Settings::NumberFormat * Settings::findNumberFormat(char ch)
{
	char c;
	for(NumberFormatList::iterator &it=m_constants.begin(); it!=m_constants.end(); ++it){
		c=it->type;
		if(c==ch) return *it;
	}
	return NULL;
}
//---------------------------------------------------------------------------------
#endif // defined(X2BC_USE_INI_FORMATS)