#pragma once

#include "volumeshop.h"

#include "Attribute.h"
#include "Exception.h"

#include <string>
#include <map>
#include <limits>
#include <strstream>

class Element;
class ElementWriter;
class ElementReader;

class Element
{
	friend std::ostream & operator<< (std::ostream & os, const Element & proElement);
	friend std::istream & operator>> (std::istream & is, Element & proElement);

	friend class ElementWriter;
	friend class ElementReader;

public:

	class ConstantIterator
	{
		friend class Element;

	public:
		ConstantIterator(const Element & eleElement, const Element & eleDefault) : m_eleDefault(eleDefault)
		{
			std::pair < std::multimap<std::string,Element>::const_iterator, std::multimap<std::string,Element>::const_iterator > paiPair = eleElement.m_mulElements.equal_range(eleDefault.GetName());
			m_iteCurrent = paiPair.first;
			m_iteEnd = paiPair.second;
		};

		~ConstantIterator()
		{
		};

		const bool IsAtEnd() const
		{
			return (m_iteCurrent == m_iteEnd);
		};

		ConstantIterator & operator++()
		{
			++m_iteCurrent;
			return *this;
		};

		operator const Element &() const
		{
			if (!IsAtEnd())
				return m_iteCurrent->second;

			return m_eleDefault;
		};

		const Attribute & operator[](const Attribute & attDefault) const
		{
			if (!IsAtEnd())
				return (m_iteCurrent->second)[attDefault];
			
			return m_eleDefault[attDefault];
		};

		ConstantIterator operator[](const Element & eleDefault) const
		{
			if (!IsAtEnd())
				return ((const Element &)m_iteCurrent->second)[eleDefault];

			return ((const Element &)m_eleDefault)[eleDefault];
		};

	private:
		std::multimap<std::string,Element>::const_iterator m_iteCurrent;
		std::multimap<std::string,Element>::const_iterator m_iteEnd;
		const Element & m_eleDefault;
	};

	class VolatileIterator
	{
		friend class Element;

	public:
		VolatileIterator(Element & eleElement, const Element & eleDefault)
		{
			std::pair < std::multimap<std::string,Element>::iterator, std::multimap<std::string,Element>::iterator > paiPair = eleElement.m_mulElements.equal_range(eleDefault.GetName());
			m_iteCurrent = paiPair.first;
			m_iteEnd = paiPair.second;
		};

		~VolatileIterator()
		{
		};

		const bool IsAtEnd() const
		{
			return (m_iteCurrent == m_iteEnd);
		};

		VolatileIterator & operator++()
		{
			++m_iteCurrent;
			return *this;
		};

		operator Element &()
		{
			return m_iteCurrent->second;
		};

		Element & operator=(const Element & eleOther)
		{
			m_iteCurrent->second = eleOther;
			return m_iteCurrent->second;
		};

		const Attribute & operator[](const Attribute & attDefault) const
		{
			return (m_iteCurrent->second)[attDefault];
		};

		Attribute & operator[](const Attribute & attDefault)
		{
			return (m_iteCurrent->second)[attDefault];
		};

		ConstantIterator operator[](const Element & eleDefault) const
		{
			return ((const Element &)m_iteCurrent->second)[eleDefault];
		};

		VolatileIterator operator[](const Element & eleDefault)
		{
			return (m_iteCurrent->second)[eleDefault];
		};

	private:
		std::multimap<std::string,Element>::iterator m_iteCurrent;
		std::multimap<std::string,Element>::iterator m_iteEnd;
	};

	Element()
	{
	};

	Element(const char *pName) : m_strName(std::string(pName))
	{
	};

	Element(const std::string & strName) : m_strName(strName)
	{
	};

	~Element()
	{
	};

	const std::string & GetName() const
	{
		return m_strName;
	};

	const std::string & GetValue() const
	{
		return m_strValue;
	};

	operator bool() const
	{
		return fromString<bool>(m_strValue);
	};

	operator char() const
	{
		return fromString<char>(m_strValue);
	};

	operator unsigned char() const
	{
		return fromString<unsigned char>(m_strValue);
	};

	operator short() const
	{
		return fromString<short>(m_strValue);
	};

	operator unsigned short() const
	{
		return fromString<unsigned short>(m_strValue);
	};

	operator int () const
	{
		return fromString<int>(m_strValue);
	};

	operator unsigned int () const
	{
		return fromString<unsigned int>(m_strValue);
	};

	operator float () const
	{
		return fromString<float>(m_strValue);
	};

	operator double () const
	{
		return fromString<double>(m_strValue);
	};

	operator std::string () const
	{
		return m_strValue;
	};

	operator const char * () const
	{
		return m_strValue.c_str();
	};

	Element & operator=(const bool bValue)
	{
		m_strValue = toString(bValue);
		return *this;
	};

	Element & operator=(const char cValue)
	{
		m_strValue = toString(cValue);
		return *this;
	};

	Element & operator=(const unsigned char uValue)
	{
		m_strValue = toString(uValue);
		return *this;
	};

	Element & operator=(const short sValue)
	{
		m_strValue = toString(sValue);
		return *this;
	};

	Element & operator=(const unsigned short uValue)
	{
		m_strValue = toString(uValue);
		return *this;
	};

	Element & operator=(const int iValue)
	{
		m_strValue = toString(iValue);
		return *this;
	};

	Element & operator=(const unsigned int uValue)
	{
		m_strValue = toString(uValue);
		return *this;
	};

	Element & operator=(const float fValue)
	{
		m_strValue = toString(fValue);
		return *this;
	};

	Element & operator=(const double dValue)
	{
		m_strValue = toString(dValue);
		return *this;
	};

	Element & operator=(const std::string & strValue)
	{
		m_strValue = strValue;
		return *this;
	};

	Element & operator=(const char * pValue)
	{
		m_strValue = std::string(pValue);
		return *this;
	};

	const Attribute & operator[](const Attribute & attDefault) const
	{
		std::map<std::string,Attribute>::const_iterator i = m_mapAttributes.find(attDefault.m_strName);

		if (i != m_mapAttributes.end())
			return i->second;

		return attDefault;
	};

	Attribute & operator[](const Attribute & attDefault)
	{
		std::map<std::string,Attribute>::const_iterator i = m_mapAttributes.find(attDefault.m_strName);

		if (i == m_mapAttributes.end())
			m_mapAttributes[attDefault.m_strName] = attDefault;

		return m_mapAttributes[attDefault.m_strName];
	};

	ConstantIterator operator[](const Element & eleDefault) const
	{
		return ConstantIterator(*this,eleDefault);
	};

	VolatileIterator operator[](const Element & eleDefault)
	{
		if (m_mulElements.find(eleDefault.m_strName) == m_mulElements.end())
			m_mulElements.insert(std::multimap<std::string,Element>::value_type(eleDefault.m_strName,eleDefault));

		return VolatileIterator(*this,eleDefault);
	};

	Element & add(const Element & eleElement)
	{
		std::multimap<std::string,Element>::iterator i = m_mulElements.insert(std::multimap<std::string,Element>::value_type(eleElement.m_strName,eleElement));
		return i->second;
	};

	void remove(const VolatileIterator & i)
	{
		m_mulElements.erase(i.m_iteCurrent);
	};

	Attribute & add(const Attribute & attAttribute)
	{
		return (m_mapAttributes[attAttribute.m_strName] = attAttribute);
	};

	void remove(const Attribute & attAttribute)
	{
		m_mapAttributes.erase(attAttribute.m_strValue);
	};

private:

	std::string m_strName;
	std::string m_strValue;
	std::map<std::string,Attribute> m_mapAttributes;
	std::multimap<std::string,Element> m_mulElements;
};

class ElementReader
{
public:

	ElementReader(std::istream &is) : m_isStream(is)
	{
	};


	~ElementReader()
	{
	};

	const bool read(Element & eleElement)
	{
		std::string strString;
		
		if (retrieve(strString))
			return read(eleElement,strString);

		return false;
	};

private:

	const bool read(Element &eleElement, const std::string & strString)
	{
		const size_t uIndex = strString.find_first_of (" />");

		if (uIndex == std::string::npos)
		{
			std::cerr << "Found invalid start tag: " + strString << std::endl;
			return false;
		}

		std::string strName = decode(strString.substr(1, uIndex-1));

		if (strName.length() == 0)
		{
			std::cerr << "Found invalid element name: " + strString << std::endl;
			return false;
		}

		if (!isalpha(strName.c_str()[0]))
		{
			std::cerr << "Found invalid element name: " + strString << std::endl;
			return false;
		}

		eleElement = Element(strName);

		const size_t endIndex = strString.find_last_not_of (" />");

		std::string anAttributes = strString.substr(uIndex+1, endIndex-uIndex);
		std::istringstream is(anAttributes);

		while (!is.eof())
		{
			AttributeReader attReader(is);
			Attribute attAttribute;

			if (attReader.read(attAttribute))
				eleElement.add(attAttribute);
		}

		if (strString.find("/>") == strString.length()-2)
		{
			// tag is closed
			return true;
		}

		std::string strBuffer;

		for (;;)
		{
			if (!retrieve(strBuffer))
				break;

			if (strBuffer.length() == 0)
			{
				// empty string indicates end of file
				break;
			}

			if (strBuffer.find("<?") == 0)
			{
				// it is an XML description field, ignore it
				continue;
			}

			if (strBuffer.find("<!") == 0)
			{
				// it is an jzXML description field, ignore it
				continue;
			}

			if (strBuffer.find("</") == 0)
			{
				// it is the end of the tag, exit
				return true;
			}

			if (strBuffer.find("<") == 0)
			{
				// it is a nested tag
				Element eleChild;
				
				if (!read(eleChild,strBuffer))
					return false;

				eleElement.add(eleChild);
				continue;
			}

			// it is a value
			eleElement = decode(strBuffer);
		}

		return false;
	};

	/** Retrieves a string from the stream */
	const bool retrieve(std::string & strString)
	{
		strString = m_strBuffer;
		m_strBuffer.erase();

		while (m_isStream.good() && !m_isStream.eof())
		{
			char c;
			m_isStream.get(c);

			if (IsWhitespace(c))
			{
				if ( strString.length() && !IsWhitespace(strString[strString.length()-1]) && !IsStarter(strString[strString.length()-1]))
				{
					// add to a std::string only if last was not separator
					strString += ' ';
				}
				// otherwise just ignore it
			}
			else
			{
				if (IsStarter(c))
				{
					// its start
					if (strString.length())
					{
						// we have something so it is the start of something new
						// save it for future and return
						m_strBuffer = c;

						if (strString.length())
							return validate(strString);

						return true;
					}
					else
					{
						// it is a start
						strString = c;
					}
				}
				else
				{
					if (IsTerminator(c))
					{
						// GCC 3.2 problem
						if (strString.length() && IsStarter(strString.c_str()[0]))
						{
							if ((strString[strString.length()-1] == ' '))
							{
								strString[strString.length()-1] = c;
							}
							else
							{
								strString += c;
							}

							if (strString.length())
								return validate(strString);

							return true;
						}
						else
						{
							strString += c;
						}
					}
					else
					{
						// add to string and continue
						strString += c;
					}
				}
			}
		}

		return false;
	};

	const bool validate(const std::string & strString) const
	{
		if (IsStarter(strString.c_str()[0]))
		{
			if ((strString.length() < 3) || !IsTerminator(strString[strString.length()-1]))
			{
				std::cerr << "Found invalid tag: " + strString << std::endl;
				return false;
			}
		}

		return true;
	};

	const std::string decode(const std::string & strString) const
	{
		std::string strResult (strString);

		for (size_t uStart = strResult.find('&'); uStart != std::string::npos; uStart = strResult.find('&', uStart+1))
		{
			const size_t uEnd = strResult.find(';', uStart);

			if (uEnd == std::string::npos)
			{
				// found invalid entity reference
				std::cerr << "Found invalid entity reference: " + strResult << std::endl;
				throw Exception("Found invalid entity reference: %s.",strResult.c_str());

			}

			std::string strReference = strResult.substr(uStart, uEnd-uStart);
			std::string strBegin = strResult.substr(0, uStart);

			if (strReference == "&lt")
			{
				strBegin += '<';
			}
			else
			{
				if (strReference == "&gt")
				{
					strBegin += '>';
				}
				else
				{
					if (strReference == "&amp")
					{
						strBegin += '&';
					}
					else
					{
						if (strReference == "&apos")
						{
							strBegin += '\'';
						}
						else
						{
							if (strReference == "&quot")
							{
								strBegin += '\"';
							}
							else
							{
								// found invalid entity reference
								std::cerr << "Found invalid entity reference: " << strReference << strResult << std::endl;
								throw Exception("Found invalid entity reference: %s.",strResult.c_str());
							}

							if (uEnd+1 < strResult.length())
							{
								strBegin += strResult.substr(uEnd+1);
							}

							strResult = strBegin;
						}
					}
				}
			}
		}

		return strResult;
	};

	const bool IsStarter(const char c) const
	{		
		static const cStarter = '<';
		return (c == cStarter);
	};

	const bool IsTerminator(const char c) const
	{
		static const char cTerminator = '>';
		return (c == cTerminator);
	};

	const bool IsWhitespace(const char c) const
	{
		static const std::string strWhitespace = " \t\n\r";
		return (strWhitespace.find(c) != std::string::npos);
	};

private:

	std::istream & m_isStream;
	std::string m_strBuffer;

};

class ElementWriter
{
public:

	ElementWriter(std::ostream &os) : m_osStream(os)
	{
	};

	~ElementWriter()
	{
	};

	const bool write(const Element & eleElement)
	{
		write(eleElement,0);
		return true;
	}

private:

	const bool write(const Element & eleElement, const unsigned int uLevel)
	{
		const std::string strIndent(uLevel,'\t');
		const std::string strNextIndent(uLevel+1,'\t');

		if (eleElement.m_mulElements.empty())
		{
			if (eleElement.m_strValue.empty())
			{
				m_osStream << strIndent << "<" << eleElement.m_strName;

				for (std::map<std::string,Attribute>::const_iterator i = eleElement.m_mapAttributes.begin(); i != eleElement.m_mapAttributes.end(); i++)
					m_osStream << " " << i->second;

				m_osStream << "/>" << std::endl;	
			}
			else
			{
				m_osStream << strIndent << "<" << eleElement.m_strName;

				for (std::map<std::string,Attribute>::const_iterator i = eleElement.m_mapAttributes.begin(); i != eleElement.m_mapAttributes.end(); i++)
					m_osStream << " " << i->second;

				m_osStream << ">";
				m_osStream << eleElement.m_strValue;
				m_osStream << "</" << eleElement.m_strName << ">" << std::endl;
			}
		}
		else
		{
			m_osStream << strIndent << "<" << eleElement.m_strName;

			for (std::map<std::string,Attribute>::const_iterator i = eleElement.m_mapAttributes.begin(); i != eleElement.m_mapAttributes.end(); i++)
				m_osStream << " " << i->second;

			m_osStream << ">" << std::endl;

			if (!eleElement.m_strValue.empty())
				m_osStream << strNextIndent << eleElement.m_strValue << std::endl;

			for (std::multimap<std::string,Element>::const_iterator i=eleElement.m_mulElements.begin(); i != eleElement.m_mulElements.end(); i++)
				write(i->second,uLevel+1);

			m_osStream << strIndent << "</" << eleElement.m_strName << ">" << std::endl;
		}

		return true;
	};

private:

	const std::string encode(const std::string & strString) const
	{
		std::string strResult;

		for ( unsigned int i = 0; i < strString.length(); i++)
		{
			switch (strString[i])
			{
			case '&':
				strResult += "&amp;";
				break;

			case '<':
				strResult += "&lt;";
				break;

			case '>':
				strResult += "&gt;";
				break;

			case '\'':
				strResult += "&apos;";
				break;

			case '\"':
				strResult += "&quot;";
				break;

			default:
				strResult += strString[i];
			}
		}
		return strResult;
	}

private:

	std::ostream & m_osStream;
};

inline std::ostream & operator<< (std::ostream & os, const Element & proElement)
{
	ElementWriter w(os);
	w.write(proElement);
	return os;
}

inline std::istream & operator>> (std::istream & is, Element & proElement)
{
	ElementReader r(is);
	r.read(proElement);
	return is;
}