/*Parses a simple line-oriented ASCII parameter file with format:
 <key>  =  <value> 
Whitespace and empty lines are ignored.  ! and # start comments anywhere.  
Strings may be quoted.

  Orion Sky Lawlor, olawlor@acm.org, 10/25/2001
*/
#include "paramFile.h"
#include <ctype.h>
#include <string.h>

void parameterFile::reset(void) //Start file over again
{
	if (forWrite) return;
	if (f!=NULL) return;
	fseek(f,0,SEEK_SET);
}
bool parameterFile::eatWhite(void) {
	while (isspace(*cur)) cur++;
	return *cur!=0;
}
bool parameterFile::nextLine(void)//Advance to the next nonempty line
{
	if (forWrite) return false;
	if (f==NULL) return false;
	while (NULL!=fgets(buf,1024,f)) {
		//Eliminate all comment characters
		for (int i=0;buf[i];i++) 
			if (buf[i]=='#' || buf[i]=='!') {
				buf[i]=0;
				break;
			}
		cur=&buf[0];
		if (!eatWhite()) continue; //Blank line
		return true; //A non-blank, non-comment line
	}
	return false; //Found EOF
}

bool parameterFile::isKey(const char *key)//True if current data matches key
{
	while (*key!=0 && *cur==*key) {
		cur++;
		key++;
	}
	return *key==0;
}
char *parameterFile::fieldParse(void) {//Skips equal sign, returns unquoted field
	eatWhite();
	if (*cur++!='=') return NULL;
	eatWhite();
	char quote=0;
	if (*cur=='"' || *cur=='\'')
		quote=*cur++;
	char *ret=cur;
	//Skip over body of field
	while (*cur!=quote && *cur!=0) cur++;
	if (quote) 
		*cur=0;	//Just end the field here
	else { //Back over all whitespace
		while (isspace(*(--cur))) *cur=0;
	}
	return ret;
}
char *parameterFile::findKey(const char *key) //Return the data associated with the next occurence of key, or NULL
{
	//Hope we find the key next...
	while (nextLine())
		if (isKey(key)) return fieldParse();
	//If not reset and try again...
	reset();
	while (nextLine())
		if (isKey(key)) return fieldParse();
	//Otherwise it isn't there
	return NULL;
}

//----------- external interface -------------------
parameterFile::parameterFile(const char *paramFileName, bool forWrite_) 
	:forWrite(forWrite_)
{
	if (paramFileName==NULL) f=NULL;
	else f=fopen(paramFileName,forWrite?"w":"r");
	if (f==NULL) forWrite=false;//Hack: pretend we're reading if the file can't be opened
}
parameterFile::~parameterFile() {
	if (f) fclose(f);
}
bool parameterFile::operator() (const char *key,const char *desc,bool defaultValue)
{
	if (forWrite) fprintf(f,"  %s = %s      ! %s\n",key,defaultValue?"true":"false",desc);
	const char *field=findKey(key);
	if (field==NULL) return defaultValue; //Key couldn't be found in file
	switch(field[0]) {
	case '0': case 'f': case 'F': case 'n': case 'N':
		return false;
	case '1': case 't': case 'T': case 'y': case 'Y':
		return true;
	default:
		return defaultValue; //Should return error code or throw exception here
	};
}
int parameterFile::operator() (const char *key,const char *desc,int defaultValue)
{
	if (forWrite) fprintf(f,"  %s = %d      ! %s\n",key,defaultValue,desc);
	const char *field=findKey(key);
	if (field==NULL) return defaultValue; //Key couldn't be found in file
	int ret;
	if (1!=sscanf(field,"%d",&ret)) return defaultValue; //Should be error or exception
	return ret;
}
double parameterFile::operator() (const char *key,const char *desc,double defaultValue)
{
	if (forWrite) fprintf(f,"  %s = %f      ! %s\n",key,defaultValue,desc);
	const char *field=findKey(key);
	if (field==NULL) return defaultValue; //Key couldn't be found in file
	double ret;
	if (1!=sscanf(field,"%lf",&ret)) return defaultValue; //Should be error or exception
	return ret;	
}

const char *parameterFile::operator() (const char *key,const char *desc,const char *defaultValue)
{
	if (forWrite) fprintf(f,"  %s = '%s'      ! %s\n",key,defaultValue,desc);
	const char *field=findKey(key);
	if (field==NULL) return defaultValue;
	char *ret=new char[strlen(field)+1]; //<- memory leak!
	strcpy(ret,field);
	return ret;
}

