#include <cctype>
#include <fstream>
#include <sstream>
#include <iomanip>
#include <cassert>
#include "Tokenizer.h"

//----< constructor may be called with no argument >-----------

Toker::Toker(const string& src, bool isFile)
	: prevprevChar(0), prevChar(0), currChar(0), nextChar(0),
	doReturnComments(false), numLines(0), braceCount(0), pIn(0),
	_state(default_state)
{
	if (src.length() > 0)
	{
		if (!attach(src, isFile))
		{
			string temp = string("can't open ") + src;
			throw runtime_error(temp.c_str());
		}
	}
	scTok = "()[]{};.\n";
	if (_mode == xml)
		scTok = "<>!" + scTok;
}
//----< destructor >-------------------------------------------

Toker::~Toker()
{
	if (pIn)
	{
		pIn->clear();
		ifstream* pFs = dynamic_cast<ifstream*>(pIn);
		if (pFs)
		{
			pFs->close();
		}
	}
}
//----< set mode of tokenizing, e.g., code or xml >------------

void Toker::setMode(Toker::mode md)
{
	_mode = md;
	scTok = "()[]{};.\n";
	if (_mode == xml)
		scTok = "<>!" + scTok;
}
//----< set mode of tokenizing, e.g., code or xml >------------

void Toker::setSingleCharTokens(string tokChars)
{
	_mode = custom;
	scTok = tokChars;
}
//----< attach tokenizer to a source file or string >----------

bool Toker::attach(const string& name, bool srcIsFile)
{
	// If a stream already exists and it's a file, we need to clear and close it safely
	if (pIn && srcIsFile)
	{
		pIn->clear();// Clear any error flags

		try {
			// Safely cast to ifstream and close the file if possible
			ifstream* pFs = dynamic_cast<ifstream*>(pIn);
			if (pFs)
			{
				pFs->close(); 
			}
		}
		catch (const bad_cast& e) {
			// Catch a possible invalid cast error and print debug info
			cerr << "Toker::attach(): Bad cast to ifstream*. Message: " << e.what() << endl;
			return false;
		}
	}
	// Create a new input stream depending on the source type
	if (srcIsFile)
		pIn = new ifstream(name.c_str());// Open file for input
	else
		pIn = new istringstream(name.c_str());// Create stream from string content

	// Check if stream creation failed 
	if (!pIn)
		return false;
	// Return the stream's status 
	return pIn->good();
}
//----< peek function that works with multiple putbacks >------

int Toker::peek()
{
	if (putbacks.size() > 0)
		return putbacks[putbacks.size() - 1];
	else
		return pIn->peek();

}
//----< multiple putBack that won't break peek >---------------

void Toker::putback(int ch)
{
	putbacks.push_back(ch);
	nextChar = ch;
	currChar = prevChar;
	prevChar = prevprevChar;
}
//----< get consistent with peek and putback >-----------------

int Toker::get()
{
	if (putbacks.size() > 0)
	{
		char ch = putbacks.front();
		putbacks.pop_back();
		return ch;
	}
	return pIn->get();
}
//
//----< extract character from attached stream >---------------

bool Toker::getChar()
{
	char oldNext = nextChar;
	prevprevChar = prevChar;
	prevChar = currChar;
	currChar = this->get();
	nextChar = this->peek();
	assert(currChar == oldNext || oldNext == 0);
	if (currChar == '\n')
		++numLines;
	if (currChar == '{' && _state == default_state)
		++braceCount;
	if (currChar == '}' && _state == default_state)
		--braceCount;
	return !pIn->eof();
}
//----< is this char a single char token? >--------------------

bool Toker::isSingleCharTok(char ch)
{
	if (scTok.find(ch) < scTok.length())
		return true;
	return false;
}
//----< remove contiguous white space except for newline >-----

void Toker::stripWhiteSpace()
{
	if (nextChar == '\n')
		return;
	while (isspace(nextChar) && nextChar != '\n')
	{
		if (isalnum(currChar))
			return;
		getChar();
	}
}
//----< is this an indentifier character? >--------------------

bool Toker::isIdentifierChar(char ch)
{
	if (isalpha(ch) || ch == '_' || isdigit(ch))
		return true;
	return false;
}
//----< is this the end of a token? >--------------------------

bool Toker::isTokEnd()
{
	if (isspace(nextChar))
		return true;
	if (isSingleCharTok(nextChar) || isSingleCharTok(currChar))
		return true;
	if (isIdentifierChar(currChar) && !isIdentifierChar(nextChar))
		return true;
	if (!isIdentifierChar(currChar) && isIdentifierChar(nextChar))
		return true;
	if (isFileEnd())
		return true;
	return false;
}
//----< is this the beginning of a comment? >------------------

bool Toker::isBeginComment()
{
	if (prevChar != '\\' && currChar == '/' && nextChar == '*')
	{
		aCppComment = false;
		return true;
	}
	if (prevChar != '\\' && currChar == '/' && nextChar == '/')
	{
		aCppComment = true;
		return true;
	}
	return false;
}
//----< is this the end of a comment? >------------------------

bool Toker::isEndComment()
{
	if (aCppComment && currChar != '\\' && nextChar == '\n')
		return true;
	if (!aCppComment && prevChar != '\\' && currChar == '*' && nextChar == '/')
		return true;
	return false;
}
//----< return comment as a token >----------------------------

string Toker::eatComment()
{
	_state = comment_state;
	string tok(1, currChar);
	while (!isEndComment() && pIn->good())
	{
		getChar();
		tok.append(1, currChar);
	}

	if (!aCppComment)
	{
		getChar();
		tok.append(1, currChar);
	}
	_state = default_state;
	return tok;
}
//----< is this the beginning of a quote? >--------------------

bool Toker::isBeginQuote()
{
	if (prevChar != '\\' && currChar == '\'')
	{
		aSingleQuote = true;
		return true;
	}
	if (prevChar != '\\' && currChar == '\"')
	{
		aSingleQuote = false;
		return true;
	}
	return false;
}
//----< is this the end of quote? >----------------------------

bool Toker::isEndQuote()
{
	if (prevChar == '\\' || currChar != '\\')
	{
		if (aSingleQuote && nextChar == '\'')
			return true;
		if (!aSingleQuote && nextChar == '\"')
			return true;
	}
	return false;
}
//----< return single or double quote as token >---------------

string Toker::eatQuote()
{
	_state = quote_state;
	string tok(1, currChar);
	while (!isEndQuote())
	{
		getChar();
		tok.append(1, currChar);
	}
	getChar();
	tok.append(1, currChar);
	_state = default_state;
	return tok;
}
//----< read token from attached file >------------------------

string Toker::getTok()
{
	string tok = "";
	stripWhiteSpace();
	if (isSingleCharTok(nextChar))
	{
		getChar();
		tok.append(1, currChar);
		return tok;
	}
	do
	{
		if (isFileEnd())
			return tok;

		getChar();
		if (isBeginComment())
		{
			if (tok.length() > 0)
			{
				this->putback(currChar);
				return tok;
			}
			tok = eatComment();
			if (doReturnComments)
				return tok;
			else
			{
				tok = "";
				continue;
			}
		}
	/*	if (isBeginQuote())
		{
			if (tok.length() > 0)
			{
				this->putback(currChar);
				return tok;
			}
			tok = eatQuote();
			return tok;
		}*/
		//if (!isspace(currChar))
			tok.append(1, currChar);
	} while (!isTokEnd() || tok.length() == 0);
	return tok;
}
