/*******************************************************************
*
*  DESCRIPTION: class macroExpansion
*
*  AUTHOR: Daniel A. Rodriguez
*
*  EMAIL: mailto://drodrigu@dc.uba.ar
*
*  DATE: 25/07/1999 (v2)
*
*******************************************************************/

/** include files **/
#include <unistd.h>
#include <stdlib.h>
#include <macroexp.h>	// class macroExpansion
#include <strutil.h>	// string utils

#define TAM_BUFFER	32000

#define MACRO_HEADER		'#'
#define COMENTARIO_HEADER	'%'
#define MACRO_INCLUDE		"#INCLUDE"
#define MACRO_MACRO		"#MACRO"
#define MACRO_BEGINMACRO	"#BEGINMACRO"
#define MACRO_ENDMACRO		"#ENDMACRO"


#ifdef __WINDOWS__			// Ugh!!!
	#define	SEPARATOR		'\\'
	#define	SEPARATOR_STR		"\\"
	#define	SEPARATOR_HEADER	".\\"
#else					// We're in Unix !!!!!!!
	#define	SEPARATOR		'/'
	#define	SEPARATOR_STR		"/"
	#define	SEPARATOR_HEADER	"./"
#endif



macroExpansion	*instanceMacroExpansion = NULL;


macroExpansion::macroExpansion(std::string file)
:fileName(file)
,newFileName("")
{
	MASSERTMSG( file != "", "Can't create a MacroExpansion with an invalid filename.\n");
}

char *advanceWhileChar( char *line, char c )
{
	while( *line != 0 && *line == c)
		line++;

	return line;
}

int isStringSimilar( char *line, long posLine, char *cadena )
{
	line = advanceWhileChar( line + posLine, ' ' );

	if (*line == 0)
		return 0;

	std::string	sAux(line);

	if (upcase(substringLenght(sAux, strlen(cadena))) == cadena)
		return 1;
	return 0;
}

inline int isStringSimilar( char *line, char *cadena )
{
	return isStringSimilar(line, 0, cadena);
}

long advanceMacro(char *line, long posLine)
{
	while ( *(line + posLine) != ')' )
		posLine++;

	return posLine;
}

macroExpansion::~macroExpansion()
{
	if (newFileName != "")
		unlink( trimSpaces(newFileName) );
}

int isInTheRootDirectory(const char *fileName, char separator)
{
	register long	cant = 0;

	while (*fileName != 0)
	{
		if (*fileName == separator)
			cant++;

		fileName++;
	}
	return cant == 1;
}

std::string getFileName(const char *pathName)
{
	// Search for the begginig of the fileName
	int	posi = strlen(pathName) - 1;

	while (pathName[posi] != SEPARATOR)
		posi--;

	// Found the separator
	std::string	sAux = SEPARATOR_STR;

	posi++;
	while (pathName[posi] != 0)
		sAux += pathName[posi++];

	return sAux;
}

void includePathEnvironment(std::string *fileName)
{
	if (getenv("TEMP") != NULL)
		*fileName = getenv("TEMP") + getFileName(fileName->c_str());
	else if (getenv("TMP") != NULL)
		*fileName = getenv("TMP") + getFileName(fileName->c_str());
	else
		*fileName = SEPARATOR_HEADER + getFileName(fileName->c_str());
}

void cambiarSiEstaEnElRaiz(std::string *fileName)
{
	if (isInTheRootDirectory(fileName->c_str(), SEPARATOR))
		includePathEnvironment(fileName);
}

std::string macroExpansion::expand()
{
	newFileName = std::string(tmpnam(NULL));
	//HENRY compile fix not working
	//newFileName = std::string(mkstemp(NULL));
	cambiarSiEstaEnElRaiz(&newFileName);

	FILE	*fileIn, *fileOut;
	char	line[TAM_BUFFER], oldLine[TAM_BUFFER], auxBuffer[TAM_BUFFER];
	register long	posiRead, posiAux, maxPosiRead = 0;
	register int	huboMacro = 0;

	fileIn = fopen( trimSpaces(fileName), "r" );
	MASSERTMSG( fileIn != NULL, "Can't open the file " + fileName );

	fileOut = fopen( trimSpaces(newFileName), "w+" );
	MASSERTMSG( fileOut != NULL, "Can't open temporary file." );

	while (!feof(fileIn))
	{
		fgets( line, TAM_BUFFER, fileIn );
		maxPosiRead = strlen(line);
		MASSERTMSG( maxPosiRead < TAM_BUFFER - 1, "The file '" + fileName + "' has a line too long");

		if (!feof(fileIn))
		{
			strcpy(auxBuffer, "");		// Clear the buffer
			posiRead = posiAux = 0;
			huboMacro = 0;

			if (line[0] == COMENTARIO_HEADER)
				;
			else if (isInclude(line, posiRead))
				addIncludeFile( getData(line, MACRO_INCLUDE) );
			else
			{
				while (posiRead < maxPosiRead)
				{
					if (line[posiRead] == COMENTARIO_HEADER)
					{
						posiRead = maxPosiRead;
						auxBuffer[posiAux++] = '\n';
					}
					else if (line[posiRead] == MACRO_HEADER)
					{
						if (isMacro(line, posiRead))
						{
							// First: Makes a flush of the auxBuffer
							fwrite(auxBuffer, 1, posiAux, fileOut);
							posiAux = 0;
	
							// Now: Adds the contents of the macro
							putMacroExpansion( fileOut, upcase(getData(line, posiRead, MACRO_MACRO)) );
							posiRead = advanceMacro(line, posiRead);
							huboMacro = 1;
						}
						else
							// Add the char to auxBuffer
							auxBuffer[posiAux++] = line[posiRead];
					}
					else
						// Add the char to auxBuffer
						auxBuffer[posiAux++] = line[posiRead];
	
					posiRead++;
				}

				// Flush the buffer
				if (posiAux != 1 || !huboMacro)
					fwrite(auxBuffer, 1, posiAux, fileOut);
			}
			strcpy(oldLine, line);
		}
	}

	if (strcmp(oldLine, line) != 0)
	{
		strcpy(auxBuffer, "");		// Clear the buffer
		posiRead = posiAux = 0;
		huboMacro = 0;

		if (line[0] == COMENTARIO_HEADER)
			;
		else if (isInclude(line, posiRead))
			addIncludeFile( getData(line, MACRO_INCLUDE) );
		else
		{
			while (posiRead != maxPosiRead)
			{
				if (line[posiRead] == COMENTARIO_HEADER)
				{
					posiRead = maxPosiRead;
					auxBuffer[posiAux++] = '\n';
				}
				else if (line[posiRead] == MACRO_HEADER)
				{
					if (isMacro(line, posiRead))
					{
						// First: Makes a flush of the auxBuffer
						fwrite(auxBuffer, 1, posiAux, fileOut);
						posiAux = 0;

						// Now: Adds the contents of the macro
						putMacroExpansion( fileOut, upcase(getData(line, posiRead, MACRO_MACRO)) );
						posiRead = advanceMacro(line, posiRead);
						huboMacro = 1;
					}
					else
						// Add the char to auxBuffer
						auxBuffer[posiAux++] = line[posiRead];
				}
				else
					// Add the char to auxBuffer
					auxBuffer[posiAux++] = line[posiRead];

				posiRead++;
			}
			// Flush the buffer
			if (posiAux != 1 || !huboMacro)
				fwrite(auxBuffer, 1, posiAux, fileOut);
		}
		strcpy(oldLine, line);
	}

	fclose(fileIn);
	fclose(fileOut);

	return newFileName;
}

char *advanceToChar( char *line, char c )
{
	while( *line != 0 && *line != c)
		line++;

	return line;
}

int macroExpansion::isInclude( char *line )
{
	return isStringSimilar( line, MACRO_INCLUDE );
}

int macroExpansion::isInclude( char *line, long posLine )
{
	return isStringSimilar( line, posLine, MACRO_INCLUDE );
}

int macroExpansion::isMacro( char *line, long posLine )
{
	return isStringSimilar( line, posLine, MACRO_MACRO );
}

int macroExpansion::isBeginOfMacro( char *line )
{
	return isStringSimilar( line, MACRO_BEGINMACRO );
}

int macroExpansion::isEndOfMacro( char *line )
{
	return isStringSimilar( line, MACRO_ENDMACRO );
}

std::string macroExpansion::getData( char *line, long posLine, std::string macroCmd)
{
	line = advanceToChar( line + posLine, '(' );

	MASSERTMSG( *line != 0, "Can't find the '(' in the " + macroCmd + " statement.");

	std::string	sAux("");
	line++;

	while( *line != 0 && *line != ')')
	{
		sAux += *line;
		line++;
	}

	MASSERTMSG( *line != 0, "Can't find the ')' in the " + macroCmd + " statement.");

	// Now, sAux has the data required
	return sAux;
}

void macroExpansion::addIncludeFile( std::string file )
{
	includeFiles.push_back( file );	
}

void macroExpansion::putMacroExpansion( FILE *fileOut, std::string macroName )
{
	std::list<std::string>::iterator	cursor;

	for (cursor = includeFiles.begin(); cursor != includeFiles.end(); cursor++)
		if (putMacroExpansionInFile( fileOut, macroName, *cursor))
			return;	// TODO OK

	MASSERTMSG( 0, "The macro '" + macroName + "' is undefined.");
}

int macroExpansion::putMacroExpansionInFile( FILE *fileOut, std::string macroName, std::string macroFile)
{
	FILE	*fileMacro;
	char	macroLine[TAM_BUFFER];

	fileMacro = fopen( trimSpaces(macroFile), "r");
	MASSERTMSG( fileMacro != NULL, "Can't open the macro file '" + macroFile + "'.");

	while (!feof(fileMacro))
	{
		fgets( macroLine, TAM_BUFFER, fileMacro );

		if (isBeginOfMacro(macroLine) && upcase(getData(macroLine, MACRO_BEGINMACRO)) == macroName)	// This is the macro what i'm looking for
			return copyMacro( fileOut, fileMacro, macroLine, macroName );
	}

	fclose(fileMacro);
	return 0;		// The macro isn't in this file.
}

int macroExpansion::copyMacro( FILE *fileOut, FILE *fileIn, char *macroLine, std::string macroName )
{
	while (!feof(fileIn))
	{
		fgets( macroLine, TAM_BUFFER, fileIn );

		if (isEndOfMacro( macroLine ))
		{
			fclose(fileIn);
			return 1;
		}

		fputs( macroLine, fileOut);
	}

	MASSERTMSG( 0, "Can't find the '#EndMacro' statement for the macro '" + macroName + "'.");
	return 0;
}
