//////////////////////////////////////////////////////////////////////
// configmgr.cpp: implementation of the ConfigFile class.
//
//////////////////////////////////////////////////////////////////////
#include "const.h"

#ifdef _VXWORKS_
#include <ctype.h>
#include <iolib.h>
#include <stdio.h>
#define _S_IWRITE	O_WRONLY	
#define _S_IREAD	O_RDONLY

extern "C" {
int _chmod(const char* szPath, int nMode) {
	/*int fd;
	fd = open (szPath, O_RDONLY, 0);
	if(fd != 0)
		return ioctl (fd, FIOATTRIBSET, nMode);
	return -1;*/
	return 0;
}
}
#endif
#ifdef _WIN32_
#include <io.h>
#include <sys/stat.h>
#define ERROR -1
#endif

#include "configfile.h"
#include "const.h"
#include "config.h"
#include "debug.h"
#ifdef LOG
#include "errorlog.h"
#endif

#undef TEST  //define TEST to debug config file parsing

// maximum number of parms ConfigItem will hold + descriptor
#define MAXTOKENS (NUM_PARMS + 1) 
#define COMMENT_CHAR '#'

static char configFileDir[] = CONFIG_DIR;

ConfigFile::ConfigFile()
{
	memset(_file, 0, sizeof(_file));
}
ConfigFile::~ConfigFile()
{
	emptyConfigItems();

	/* Change file back to read/write: */   
	if( _chmod( _file, _S_IWRITE ) == -1 )
		return;
}

Status ConfigFile::initialize(const char * filename)
{
	FILE *stream;
	Status s;
	char msg[128];

	sprintf(_file, "%s\\%s", configFileDir, filename);
	stream= fopen(_file, "r");
	if(stream == NULL) {
#ifdef LOG
		sprintf(msg,"ConfigFile::initialize() config file %s either missing or invalid", _file);		
		ErrorLog::logError(msg);
#else
		DPRINTF("%s\n",msg);
#endif
		return ERROR;
	}
	
	s = parse(stream);	
	fclose(stream);

	/* Make file read-only: */  
	if( _chmod(_file, _S_IREAD ) == -1 ) {
#ifdef LOG
		sprintf(msg,"ConfigFile::initialize() cannot change mode for file %s to READ ONLY\n", _file);   
		ErrorLog::logError(msg);
#else
		DPRINTF("%s\n",msg);
#endif

		return ERROR;
	}

	return OK;
}

MBool ConfigFile::searchForItem(const char * name,  ConfigItemWrapper &item)
{
	// Iterator is used to loop through the vector.	
	// TODO: maintain interator position - faster consequentive lookups
	CONFIGITEM::const_iterator it = _configItems.begin();
    for(; it != _configItems.end(); ++it) {
        const ConfigItemWrapper &bw = (*it).cfgitem;
		if(bw() != 0 && !strcmp(bw()->name(), name) )
		{
			item = bw;
			return TRUE;
		}
    }
	return FALSE;
}
Status ConfigFile::parse(FILE *stream)
{
	// Empty config items in case re-parsing an existing file, and replacing
	// existing config items
	emptyConfigItems();

	char line[250];
	char comment[250];
	char *ptr;
	int pos = 0;
	
	// Note that lineNo is one-based
	for (int lineNo = 1; 
		// Get file position to use later for restoring config data		
		 pos = ftell (stream), 
		 fgets(line, sizeof(line), stream) != NULL; lineNo++)
	{					
		// Strip off trailing newline and carriage return
		// Strip off trailing newline and carriage return
		if (line[strlen(line) - 2] == '\r')
			line[strlen(line) - 2] = '\0';
		else if (line[strlen(line) - 1] == '\n')
			line[strlen(line) - 1] = '\0';

		// Strip off comments, and save for file write		
		*comment = '\0';
		if ((ptr =strchr(line, COMMENT_CHAR)) != NULL) {
			strcpy (comment, ptr);
			*ptr = '\0';	//end cmd string so we don't try to parse comments further
		}

		// Look for non-blank characters 
		Boolean allBlanks = TRUE;
		for (ptr = line; *ptr != '\0'; ptr++)
		{
			if (*ptr != ' ' && *ptr != '\t') {
				allBlanks = FALSE;
			}
		}

		// If line is all blank, then skip it
		if (allBlanks)
			continue;
		else
		{
			char cmd[128];
			char quote[64];
			char *argv[MAXTOKENS];
			int argc = 0, quotestart = 0, quotefound = 0;
			char *token;

		   	token = strtok( line, " ,\t\n\r" );
			memset(cmd, '\0', sizeof(cmd));		

			while(token != 0)
			{				
				strcpy(cmd, token);

				char *ptr2;			
				// Case-fold
				for (ptr2 = cmd; *ptr2 != '\0'; ptr2++) {
					char c = toupper(*ptr2);
					*ptr2 = c;
				}
				if (argc >= MAXTOKENS) 			
					return ERROR;

				if(strstr(token, "\"")) { //if a quote is found, flag it
					if(!quotestart) {
						memset(quote, '\0', sizeof(quote));		
						quotestart = 1;
					}
					else { //quote already started, so this must be ending quote
						quotestart = 0;
						strcat(quote, token);
						quotefound = 1;
					}
				}
				
				if(quotestart) {
					strcat(quote, token);
					strcat(quote, " ");
				}					

				if(!quotestart){
					if(!quotefound)
						argv[argc++] = token;
					else
						argv[argc++] = quote;
				}

				token = strtok(NULL, " ,\t\n\r");	//search for delimiters
				
			}//end while token not found	
		
		//parameters start at index 1, name is at index 0
		const char **argvs = (const char **)&argv[1]; 
		struct ConfigFileItem i;		
		if(argc > 1){
				i.cfgitem = ConfigItemWrapper(new ConfigItem(argv[0],argc - 1, argvs));
				i.pos = pos;
				if(strlen(comment))
					i.comment = strdup(comment);
				else
					i.comment = 0;

				_configItems.push_back(i);
		}

		}//end else not all blanks	

	}// Still have a new line to parse

#ifdef TEST
	dump();
#endif
	return OK;
}


void ConfigFile::write()
{
	if(_file == 0) {
		DPRINTF("Invalid config file pointer, cannot write to config file");
		return;
	}

	static const char *tmpName = "lrs.$$$";	//temporary file name
	char *tmpPath = (char *)malloc (strlen(_file)+strlen(tmpName)+1);
	if (!tmpPath) {
	  printf ("No memory for temp path name!\n");
	  return;
    }
	strcpy (tmpPath, _file);
	char *fileName = strrchr (tmpPath, '\\');	//look for last path delimiter
	if (fileName) fileName++; else fileName=tmpPath;
	strcpy (fileName, tmpName);					//replace specified file name

//printf ("Output path = %s\n", tmpPath);

#ifdef TEST
	dump(); 
#endif

	// Open existing file for reading
	FILE *input = fopen(_file, "r");	
	const char newline = '\n';
		
	if(input == NULL) {
		printf("Error opening %s in  ConfigFile::write()\n", _file);
		free ((void *)tmpPath);
	}
	else
	{
	  FILE *output = fopen (tmpPath, "w");  //open temp file for output
	  if (output == NULL) {
		printf("Error opening %s in ConfigFile::write()\n", tmpPath);
	  }else{	
		printf("Writing config file items to %s\n", _file);

		Nat16 j = 0;
		Int32 offset = 0;
		char line[256];

		// Return if there are no config items.
		if(_configItems.size() == 0) {
			free (tmpPath);
			return;
		}
	
		// Iterator is used to loop through the vector.	
		CONFIGITEM::const_iterator it = _configItems.begin();		

		// first, copy the leading comment lines from input to output		
		while (ftell (input) < (*it).pos && fgets(line, sizeof(line), input) != NULL) {
//printf ("HEADER: %s", line);	
			  if(fputs(line, output) == EOF) {
writeError:
				printf ("Error writing %s\n", tmpPath);
				fclose (input); fclose (output);
				free (tmpPath);
				return;
			  }
		}

		for(; it != _configItems.end(); ++it) 	{	

			const ConfigItemWrapper& bw = (*it).cfgitem;

			// Format the config item name first followed by a white space
			offset = sprintf(line, "%s ", bw()->name());

			if(bw()->nitems() > 0)		{		
				// Format comma delimited parameters
				for(j=0; j< bw()->nitems() - 1;j++)
					offset += sprintf(line + offset, "%s,",bw()->parm(j));

				offset += sprintf(line + offset, "%s",bw()->parm(j));
			}
				
//if((*it).comment != 0) printf ("COMMENT: %s\n", (*it).comment);

			if((*it).comment != 0)
				sprintf(line + offset, "\t%s\n",(*it).comment);
			else
				sprintf(line + offset, "\n");

			// Write line
//printf ("BODY: %s", line);	
			if(fputs(line, output) == EOF) goto writeError;
		}
		
#ifdef _VXWORKS_
		ioctl ((int)output, FIOSYNC, 0);
#else
		fflush(output);
#endif
		fclose(input);
		fclose(output);		

		
		/* mark config file as write: */  
		if( _chmod(_file, _S_IWRITE) == -1 )
			printf( "Cannot change mode for file %s to WRITE\n", _file);

		//remove the original config file and replace it with the updated one

		if (remove (_file) == -1)
		    printf("Unable to remove %s\n", _file);

		 if(rename (tmpPath, _file) != 0)
			 printf("Unable to rename %s to %s\n", tmpPath, _file);

		/* mark config file as read-only: */  
		if( _chmod(_file, _S_IREAD ) == -1 )
			printf( "Cannot change mode for file %s to READONLY\n", _file);

		free (tmpPath);   
	  }
	}
}

void ConfigFile::replaceItem(const ConfigItemWrapper item)
{
	if(item() != 0)
	{
	// Iterator is used to loop through the vector.	
	CONFIGITEM::iterator it = _configItems.begin();
	for(; it != _configItems.end(); ++it) {
        const ConfigItem* bw = (*it).cfgitem();
		if(bw != 0 && !strcmp(bw->name(), item()->name()) ){
			(*it).cfgitem = item;
			return;
		}
    }
	}
}

void ConfigFile::dump()
{
	CONFIGITEM::const_iterator it = _configItems.begin();
	Nat16 i;
    for(; it != _configItems.end(); ++it) {
        const ConfigItem* bw = (*it).cfgitem();
		if((*it).comment != 0)
			printf("name:%s\tfilepos:%d\tcomment:%s",bw->name(), (*it).pos, (*it).comment);
		else
			printf("name:%s\tfilepos:%d",bw->name(), (*it).pos);

		for(i=0; i<bw->nitems(); i++)
			printf(" %s,",bw->parm(i));

	printf("\n");
	}
}
void ConfigFile::emptyConfigItems()
{
	// Iterator is used to loop through the vector.	
	CONFIGITEM::iterator it = _configItems.begin();
    for(; it != _configItems.end(); ++it)
	if((*it).comment != 0)
		free((void *)(*it).comment);

	_configItems.clear();	
}

MBool ConfigFile::createCfgItem(const char *name, const char *argvs)
{	
	struct ConfigFileItem i;		
	
	if(argvs != 0)
		i.cfgitem = ConfigItemWrapper(new ConfigItem(name, 1, &argvs));
	else
		i.cfgitem = ConfigItemWrapper(new ConfigItem(name, 0, 0));

	if(i.cfgitem() == 0)
		return FALSE;

	i.pos = 0; //not used
	i.comment = 0; //not used
	_configItems.push_back(i);
	return TRUE;	
}
