//------------------------------------------------------------------------------------
// <copyright file="LqrSMACParserFormatter-ConfigStream.cpp" company="LiquidRobotics">
//
// Copyright (c) 2014 Liquid Robotics, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// This software is associated with a release.  Liquid Robotics makes no
// representation that the design, interfaces or component distribution will
// remain unchanged in future releases.
//
// </copyright>
//
// <summary>
// </summary>
//
// <owner></owner>
//------------------------------------------------------------------------------------

#include "LqrSMACParserFormatter.h"
#include <sstream>
#include <boost/algorithm/string.hpp>

using namespace std;

namespace LiquidRobotics {
namespace SMACFramework {
namespace PacketParserFormatter {
namespace Detail {

inline void ThrowLex(const string& pathName, const int lineNum, const string& caption)
{
	stringstream stm;
	stm << pathName << "(" << lineNum << "): " << caption;

	throw LexicalAnalysisError(stm.str());
}

ConfigStream::ConfigStream(const string& _pathName)
: lineNum(1)
, pathName(_pathName)
, fp(NULL)
, complete(false)
{
}

ConfigStream::~ConfigStream()
{
	if (fp)
		fclose(fp);
}

const string& ConfigStream::GetPathName() const
{
	return pathName;
}

bool ConfigStream::Read(Token& result)
{
	if (!tokenQueue.empty())
	{
		result = tokenQueue.front();
		tokenQueue.pop_front();
		return true;
	}

	if (!complete)
	{
		if (fp == NULL)
		{
			stringstream cmdstm;
			cmdstm << "./PFCompile < \"" << pathName << "\"";

			string cmd = cmdstm.str();

			if ((fp = popen(cmd.c_str(), "r")) == NULL)
			{
				stringstream msgstm;
				msgstm << "Could not open lexical analyzer with command '" << cmd << "'";
				throw FailedToOpenLexicalAnalyzer(msgstm.str());
			}
		}
	}

	string tokenTypeName;
	bool restart;

	do
	{
		restart = false;
		result.tokenType = TT_NONE;
		result.lineNum = lineNum;

		if (ReadLine(tokenTypeName, false))
		{
			bool escapedSecond = false;
			string upcasedTypeName = boost::to_upper_copy(tokenTypeName);

			if (!upcasedTypeName.compare("COMMENT1") || !upcasedTypeName.compare("COMMENT2"))
				result.tokenType = TT_COMMENT;
			else if (!upcasedTypeName.compare("ERROR"))
				result.tokenType = TT_ERROR;
			else if (!upcasedTypeName.compare("EQUALS"))
				result.tokenType = TT_EQUALS;
			else if (!upcasedTypeName.compare("LCURL"))
				result.tokenType = TT_LCURL;
			else if (!upcasedTypeName.compare("LINE"))
			{
				result.tokenType = TT_LINE;
				++lineNum;
			}
			else if (!upcasedTypeName.compare("RCURL"))
				result.tokenType = TT_RCURL;
			else if (!upcasedTypeName.compare("SEMICO"))
				result.tokenType = TT_SEMICO;
			else if (!upcasedTypeName.compare("SYMBOL"))
				result.tokenType = TT_SYMBOL;
			else if (!upcasedTypeName.compare("QUOTED"))
			{
				result.tokenType = TT_QUOTED;
				escapedSecond = true;
			}
			else
			{
				stringstream msgstm;
				msgstm << "Lexical analyzer returned unexpected type '" << tokenTypeName << "'";
				ThrowLex(pathName, lineNum, msgstm.str());
			}

			if (!ReadLine(result.data, false, escapedSecond))
			{
				stringstream msgstm;
				msgstm << "Lexical analyzer did not return a data parameter for '" << tokenTypeName << "'";
				ThrowLex(pathName, lineNum, msgstm.str());
			}

			restart = HandleReceivedData(result);
		}
		else
			return false;

	} while(restart);

	return true;
}

bool ConfigStream::ReadLine(string& result, bool upcase, bool unescape)
{
	if ((fp == NULL) || complete)
		return false;

	result.reserve(1024);
	result.resize(0);

	bool inEscape = false;
	char escChar = '\0';
	int escCount = 0;
	char ch;
	bool err;

	while(ch = fgetc(fp), !(err = ferror(fp) || feof(fp)))
	{
		if (ch == '\r')
		{
			ch = fgetc(fp);
			err = ferror(fp) || feof(fp);

			if (!err && (ch != '\n'))
				ungetc(ch, fp);

			break;
		}

		else if (ch == '\n')
			break;

		else if (unescape && (ch == '%'))
		{
			if (inEscape)
			{
				stringstream msgstm;
				msgstm << "Lexical analyzer escape sequence is " << escCount << " bytes long, but 2 bytes were expected";

				ThrowLex(pathName, lineNum, msgstm.str());
			}
			else
			{
				inEscape = true;
				escCount = 0;
				escChar = '\0';
			}
		}

		else if (inEscape)
		{
			ch = toupper(ch);
			++escCount;
			escChar <<= 4;

			if ((ch >= '0') && (ch <= '9'))
				escChar += ch-'0';

			else if ((ch >= 'A') && (ch <= 'F'))
				escChar += ch-'A'+'\xA';

			else
			{
				inEscape = false;

				stringstream msgstm;
				msgstm << "Lexical analyzer escape sequence character '" << ch << "' is out of hex digit range";

				ThrowLex(pathName, lineNum, msgstm.str());
			}

			if (escCount == 2)
			{
				inEscape = false;
				if (upcase) escChar = toupper(escChar);
				result.push_back(escChar);
			}
		}

		else
		{
			if (upcase) ch = toupper(ch);
			result.push_back(ch);
		}
	}

	if (err)
	{
		fclose(fp);
		fp = NULL;
		complete = true;
	}

	if (inEscape)
		ThrowLex(pathName, lineNum, "Lexical analyzer escape sequence incomplete at line end");

	return !err || result.length();
}

bool ConfigStream::HandleReceivedData(const Token& token)
{
	if (token.tokenType == TT_ERROR)
		ThrowLex(pathName, lineNum, token.data);

	return (token.tokenType == TT_LINE) || (token.tokenType == TT_COMMENT);
}

void ConfigStream::Push(const Token& token)
{
	tokenQueue.push_front(token);
}

string ConfigStream::TokenType_ToString(TokenType tt)
{
	string result;

	switch(tt)
	{
	case TT_NONE:		result = "NONE";		break;
	case TT_COMMENT:	result = "COMMENT";		break;
	case TT_ERROR:		result = "ERROR";		break;
	case TT_EQUALS:		result = "EQUALS";		break;
	case TT_LCURL:		result = "L-CURL";		break;
	case TT_LINE:		result = "LINE";		break;
	case TT_RCURL:		result = "R-CURL";		break;
	case TT_SEMICO:		result = "SEMICO";		break;
	case TT_SYMBOL:		result = "SYMBOL";		break;
	case TT_QUOTED:		result = "QUOTED";		break;

	default:
	{
		stringstream stm;
		stm << tt << "?";

		result = stm.str();
		break;
	}
	}

	return result;
}

}
}
}
}
