//=========== (C) Copyright 1999 Valve, L.L.C. All rights reserved. ===========
//
// The copyright to the contents herein is the property of Valve, L.L.C.
// The contents may be used and/or copied only with the written permission of
// Valve, L.L.C., or in accordance with the terms and conditions stipulated in
// the agreement/contract under which the contents have been supplied.
//
// Purpose:  Implementation of CTextFile. See TextFile.h for details
//
// $Workfile:     $
// $Date:         $
//
//------------------------------------------------------------------------------------------------------
// $Log: $
//
// $NoKeywords: $
//=============================================================================

#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include "TFStatsApplication.h"
#include "util.h"
#include "TextFile.h"

//------------------------------------------------------------------------------------------------------
// Function:	CTextFile::init
// Purpose:	initializes a CTextFile object
// Input:	filename - name of the file that this object will represent
//				eliminateComments - if true, C++ style comments will not be handed
//				back as tokens to the user of this object
// Output:	
//------------------------------------------------------------------------------------------------------
void CTextFile::init(const char* filename,bool eliminateComments)
{
	this->filename=filename;
	//only support one push back
	fWordPushed=false;
	noComments=eliminateComments;
	memset(wordBuf,0,BUF_SIZE);
	theFile=fopen(filename,"rt");

	
	//three different delim sets
	//use this when reading strings
	stringDelims="\"";
	//use this when reading normal file stuff
	normalDelims=" \t{}=\n\r;\"";
	//use this when you want to discard a block
	blockDelims="}";
	//set to default
	delims=normalDelims;
}

//------------------------------------------------------------------------------------------------------
// Function:	CTextFile::isDelim
// Purpose:	returns true if the given char is in the current delimiter set
// Input:	c - the character to test
// Output:	Returns true on success, false on failure.
//------------------------------------------------------------------------------------------------------
bool CTextFile::isDelim(char c)
{
	for (int i=0;delims[i] != 0; i++)
	{
		if (c==delims[i])
			return true;
	}
	return false;
}

//------------------------------------------------------------------------------------------------------
// Function:	CTextFile::discardBlock
// Purpose:	advances the file ptr to the character after the next }
//------------------------------------------------------------------------------------------------------
void CTextFile::discardBlock()
{
	delims=blockDelims;
	getToken();
	delims=normalDelims;
	discard("}");
}

//------------------------------------------------------------------------------------------------------
// Function:	CTextFile::getToken
// Purpose:	gets and returns the next token in the file
// Output:	const char*
//------------------------------------------------------------------------------------------------------
const char* CTextFile::getToken()
{
	return getToken(wordBuf);
}

//------------------------------------------------------------------------------------------------------
// Function:	CTextFile::getToken
// Purpose:	gets and returns the next token in the file, with an arbitrary buffer
// Input:	outputBuf - the buffer to put the token in
// Output:	const char*
//------------------------------------------------------------------------------------------------------
const char* CTextFile::getToken(char* outputBuf)
{
	if (fWordPushed)
	{
		fWordPushed=false;
		strcpy(outputBuf,wordBuf);
		return wordBuf;
	}
	readToken:
	char c=getNextNonWSChar();
	if (!theFile || feof(theFile)) return NULL;
	if (isDelim(c) && !(isspace(c)))
	{
		outputBuf[0]=c;
		outputBuf[1]=0;
		return outputBuf;
	}

	int write=0;
	while (!isDelim(c))
	{
		if (c=='\\')
		{
			c=fgetc(theFile);
			if (!theFile&& feof(theFile))
				break;
		}
		
		//these are both in the normal delimiter set, so this case won't happen unless we're reading a string
		//which is exactly the behaviour we want
		if (c=='\n' || c=='\r')
		{
			outputBuf[write]=0;
			g_pApp->fatalError("new line in string constant (or unterminated string constant):\n\"%s\"",outputBuf);
		}
		outputBuf[write++]=c;
		c=fgetc(theFile);
		
		if (feof(theFile))
			break;
	}
	
	if (theFile && !feof(theFile))
		fseek(theFile,-1,SEEK_CUR); //seek to before the delimiter

	outputBuf[write]=0;

	if (outputBuf[0] == '/' && outputBuf[1] == '/')
	{
		while (fgetc(theFile)!='\n');
		goto readToken;
	}
	
	return outputBuf;
}

//------------------------------------------------------------------------------------------------------
// Function:	CTextFile::getNextNonWSChar
// Purpose:	gets and returns  the next non whitespace character
// Output:	char
//------------------------------------------------------------------------------------------------------
char CTextFile::getNextNonWSChar()
{
	char c;
top:
	c=' ';
	while (theFile && !feof(theFile) && isspace(c))
		c=fgetc(theFile);
	
	if (!theFile) return 0;
	if (feof(theFile)) 	return 0;
	
	if (!noComments)
		return c;

	//check for comments
	if (c=='/')
	{
		char c2=fgetc(theFile);
		if (c2=='/') //found comment?  
		{
			//discard and start over
			getLine();
			goto top;
		}
		else
		{
			fseek(theFile,-1,SEEK_CUR);
			return c;
		}
	}
	else 
	{
		return c;
	}

}

//------------------------------------------------------------------------------------------------------
// Function:	CTextFile::readString
// Purpose:	reads and returns the contents of the file up to the next "
// Output:	const char*
//------------------------------------------------------------------------------------------------------
const char* CTextFile::readString()
{
	if (fWordPushed)
	{
		fWordPushed=false;
		return wordBuf;
	}
	char c=getNextNonWSChar();
	
	if (c=='\"')
	{
		delims=stringDelims;
		getToken();		//handles escaping stuff
		delims=normalDelims;
		if (wordBuf[0]=='\"' && wordBuf[1]==0)
			wordBuf[0]=0;
		else
			discard("\"");
	}
	else
	{
		fseek(theFile,-1,SEEK_CUR);
		getToken();
	}


	return wordBuf;
}

//------------------------------------------------------------------------------------------------------
// Function:	CTextFile::readString
// Purpose:	reads and returns the contents of the file up to the next "
// Output:	const char*
//------------------------------------------------------------------------------------------------------
const char* CTextFile::readString(char* buf)
{
	if (fWordPushed)
	{
		fWordPushed=false;
		strcpy(buf,wordBuf);
		return buf;
	}
	char c=getNextNonWSChar();
	if (eof()) return NULL;
	if (c=='\"')
	{
		delims=stringDelims;
		getToken(buf);		//handles escaping stuff
		delims=normalDelims;
		if (buf[0]=='\"' && buf[1]==0)
			buf[0]=0;
		else
			discard("\"");
	}
	else
	{
		fseek(theFile,-1,SEEK_CUR);
		getToken(buf);
	}


	return buf;
}

//------------------------------------------------------------------------------------------------------
// Function:	CTextFile::getLine
// Purpose:	reads and returns the contents of the file up to the next \r or \n 
// Output:	const char*
//------------------------------------------------------------------------------------------------------
const char* CTextFile::getLine()
{
	fgets(wordBuf,BUF_SIZE,theFile);
	return wordBuf;
}

//------------------------------------------------------------------------------------------------------
// Function:	CTextFile::discard
// Purpose:	discards the next token, also checks to see if it is the same as the token
//		the user of the object was expecting to discard. 
// Input:	test - the token that is expected to be discarded.
// Output:	Returns true on success, false on failure.
//------------------------------------------------------------------------------------------------------
bool CTextFile::discard(char* test)
{
	if (!theFile || feof(theFile))
		return true;

	char wordBuf2[BUF_SIZE];
	getToken(wordBuf2);

	int result = stricmp(wordBuf2,test);
	if (result !=0)
		g_pApp->fatalError("While parsing %s, expecting \"%s\", got \"%s\"",filename.c_str(),test,wordBuf2);
	
	return true;
}

//------------------------------------------------------------------------------------------------------
// Function:	CTextFile::peekNext
// Purpose:	peeks at the next token.  gets then immediately pushes it back
//		destroys any pushed back words from before.
// Output:	char*
//------------------------------------------------------------------------------------------------------
char* CTextFile::peekNext()
{
	fpos_t startpos;
	fgetpos(theFile,&startpos);

	const char* c=getToken(peekBuf);
	if (!c)
		return NULL;
	//pushBack(c);
	fseek(theFile,startpos,SEEK_SET);
	return peekBuf;
}

//------------------------------------------------------------------------------------------------------
// Function:	CTextFile::peekNextString
// Purpose:	peeks at the next string.  gets then immediately pushes it back
//		destroys any pushed back words from before.
// Output:	char*
//------------------------------------------------------------------------------------------------------
char* CTextFile::peekNextString()
{
	if (!theFile)
		return NULL;
	fpos_t startpos;
	fgetpos(theFile,&startpos);
	

	const char* c=readString(peekBuf);
	if (!c)
		return NULL;
	//pushBack(c);
	fseek(theFile,startpos,SEEK_SET);
	return peekBuf;
}

//------------------------------------------------------------------------------------------------------
// Function:	CTextFile::pushBack
// Purpose: "pushes" a token back into the stream.  Only can have one pushed back
//		token at a time. getting a token or peeking will destroy the pushed back token
// Input:	pushTok - the token to push back.
//------------------------------------------------------------------------------------------------------
void CTextFile::pushBack(const char* pushTok)
{
	if (pushTok != wordBuf)
		strncpy(wordBuf,pushTok,BUF_SIZE);
	fWordPushed=true;
}

//------------------------------------------------------------------------------------------------------
// Function:	CTextFile::reset
// Purpose:	resets the file ptr to the beginning of the file
//------------------------------------------------------------------------------------------------------
void CTextFile::reset()
{
	fseek(theFile,0,SEEK_SET);
}

//------------------------------------------------------------------------------------------------------
// Function:	CTextFile::~CTextFile
// Purpose: destructor	
//------------------------------------------------------------------------------------------------------
CTextFile::~CTextFile()
{
	if (theFile)
		fclose(theFile);
#ifndef WIN32
		chmod(filename.c_str(),PERMIT);
#endif

} 



bool CTextFile::eof()
{
	bool retval=false;
	
	//test current pos first
	retval = (!theFile || feof(theFile));
	if (retval)
		return true;

	//now see if only whitespace is left
	
	fpos_t beforecheck;
	fgetpos(theFile,&beforecheck);
	char c=getNextNonWSChar();

	retval = (!theFile || feof(theFile));

	fseek(theFile,beforecheck,SEEK_SET);
	return retval;
}
	

int CTextFile::readInt()
{
	readString();
	return atoi(wordBuf);
}

unsigned long CTextFile::readULong()
{
	readString();
	return strtoul(wordBuf,NULL,10);
}