//----------------------------------------------------------------------------------
// <copyright file="LqrSMACParserFormatter-ConfigItem.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>
#include <boost/foreach.hpp>
#include "nsextend.h"

using namespace std;

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

inline void DoThrow(const ConfigStream::Token& token, const ConfigStream& stm, const string& caption)
{
	stringstream msgstm;
	msgstm << stm.GetPathName() << "(" << token.lineNum << "): " << caption;
	throw ConfigFileParserError(msgstm.str());
}

inline bool Expect(ConfigStream& stm, ConfigStream::Token& token, ConfigStream::TokenType expected, bool throwOnEOF = true)
{
	if (!stm.Read(token))
	{
		if (throwOnEOF)
		{
			stringstream msgstm;
			msgstm << stm.GetPathName() << "(" << token.lineNum << "): unexpected end of stream";
			throw UnexpectedEndOfStream(msgstm.str());
		}
		else
			return false;
	}

	if (token.tokenType != expected)
	{
		stringstream msgstm;
		msgstm << "expected token type " << ConfigStream::TokenType_ToString(expected)
		       << " but received type " << ConfigStream::TokenType_ToString(token.tokenType);

		DoThrow(token, stm, msgstm.str());
	}

	return true;
}

inline bool Expect(ConfigStream& stm, ConfigStream::Token& token, ConfigStream::TokenType expected1, ConfigStream::TokenType expected2, bool throwOnEOF = true)
{
	if (!stm.Read(token))
	{
		if (throwOnEOF)
		{
			stringstream msgstm;
			msgstm << stm.GetPathName() << "(" << token.lineNum << "): unexpected end of stream";
			throw UnexpectedEndOfStream(msgstm.str());
		}
		else
			return false;
	}

	if ((token.tokenType != expected1) && (token.tokenType != expected2))
	{
		stringstream msgstm;
		msgstm << "expected token type (" << ConfigStream::TokenType_ToString(expected1) << ","
										  << ConfigStream::TokenType_ToString(expected2)
			   << ") but received type " << ConfigStream::TokenType_ToString(token.tokenType);

		DoThrow(token, stm, msgstm.str());
	}

	return true;
}

inline bool Expect(ConfigStream& stm, ConfigStream::Token& token, ConfigStream::TokenType expected1, ConfigStream::TokenType expected2, ConfigStream::TokenType expected3, bool throwOnEOF = true)
{
	if (!stm.Read(token))
	{
		if (throwOnEOF)
		{
			stringstream msgstm;
			msgstm << stm.GetPathName() << "(" << token.lineNum << "): unexpected end of stream";
			throw UnexpectedEndOfStream(msgstm.str());
		}
		else
			return false;
	}

	if ((token.tokenType != expected1) && (token.tokenType != expected2) && (token.tokenType != expected3))
	{
		stringstream msgstm;
		msgstm << "expected token type (" << ConfigStream::TokenType_ToString(expected1) << ","
										  << ConfigStream::TokenType_ToString(expected2) << ","
										  << ConfigStream::TokenType_ToString(expected3)
			   << ") but received type " << ConfigStream::TokenType_ToString(token.tokenType);

		DoThrow(token, stm, msgstm.str());
	}

	return true;
}

ConfigItem::ConfigItem()
{
}

ConfigItem::~ConfigItem()
{
}

bool ConfigItem::Initialize(ConfigStream& stm, bool nested)
{
	ConfigStream::Token mainToken;

	if (!Expect(stm, mainToken, ConfigStream::TT_SYMBOL, false))
		return false;

	category = mainToken.data;

	ConfigStream::Token attName, attEquals, attValue;

	while(nested
			? Expect(stm, attName, ConfigStream::TT_SYMBOL, ConfigStream::TT_SEMICO)
			: Expect(stm, attName, ConfigStream::TT_SYMBOL, ConfigStream::TT_LCURL, ConfigStream::TT_SEMICO)
		, attName.tokenType == ConfigStream::TT_SYMBOL)
	{
		Expect(stm, attEquals, ConfigStream::TT_EQUALS);
		Expect(stm, attValue, ConfigStream::TT_QUOTED);

		string keyName = boost::to_upper_copy(attName.data);
		AttributeMap::iterator found = attributes.find(keyName);

		if (found != attributes.end())
		{
			stringstream msgstm;
			msgstm << "Duplicate definition of attribute name '" << attName.data << "'";

			DoThrow(attName, stm, msgstm.str());
		}

		AttributePair attPair(attName.data, attValue.data);
		attributes[keyName] = attPair;
	}

	if (attributes.find("NAME") == attributes.end())
		DoThrow(mainToken, stm, "no 'name' attribute was defined");

	if (attName.tokenType == ConfigStream::TT_LCURL)
	{
		subConfig.reset(new PtrList);

		while(Expect(stm, mainToken, ConfigStream::TT_SYMBOL, ConfigStream::TT_RCURL)
		    , mainToken.tokenType == ConfigStream::TT_SYMBOL)
		{
			stm.Push(mainToken);

			Ptr ptr;
			ptr.reset(new ConfigItem);
			ptr->Initialize(stm, true);

			subConfig->push_back(ptr);
		}

		Expect(stm, mainToken, ConfigStream::TT_SEMICO);
	}

	return true;
}

inline string Escape(const string& input)
{
	stringstream output;

	BOOST_FOREACH(char c, input)
		if (isprint(c))
			output << c;
		else
			output << "\\x" << utos(c, 16, 2);

	return output.str();
}

std::ostream& operator<<(std::ostream& stm, const ConfigItem& ci)
{
	stm << "Category " << ci.category;

	AttributeMap::const_iterator it = ci.attributes.begin();

	while(it != ci.attributes.end())
	{
		const pair<string,AttributePair>& item = *it;

		stm << " " << Escape(item.second.first) << "=\"" << Escape(item.second.second) << "\"";
		++it;
	}

	if (ci.subConfig)
	{
		cout << " {";

		BOOST_FOREACH(ConfigItem::Ptr ptr, *ci.subConfig)
			stm << endl << "\t" << *ptr;

		cout << endl << "}";
	}

	cout << ";";

	return stm;
}

}
}
}
}
