#pragma once

#include "volumeshop.h"

#include "Exception.h"

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

class Element;
class AttributeWriter;
class AttributeReader;

class Attribute
{
	friend class Element;

	friend std::ostream & operator<< (std::ostream & os, const Attribute & attAttribute);
	friend std::istream & operator>> (std::istream & is, Attribute & attAttribute);

	friend class AttributeWriter;
	friend class AttributeReader;

public:

	Attribute()
	{
	};

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

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

	~Attribute()
	{
	};

	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();
	};

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

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

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

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

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

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

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

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

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

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

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

private:

	std::string m_strName;
	std::string m_strValue;
};

class AttributeReader
{
public:

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

	~AttributeReader()
	{
	};

	const bool read(Attribute &attAttribute)
	{
		std::string strName;
		std::string strValue;

		for ( unsigned int i = 0; !m_isStream.eof(); i++ )
		{
			char c;
			m_isStream.get(c);

			switch (c)
			{
			case '=':
				{
					if (strName.length() == 0)
					{
						std::cerr << "Found invalid attribute name." << std::endl;
						return false;
					}
					break;
				}

			case ' ':
				{
					if (strName.length())
					{
						if (strValue.length() > 1)
						{
							strValue += c;
						}
					}
					break;
				}

			case '\"':
			case '\'':
				{
					if (strValue.length() && strValue.c_str()[0] == c)
					{
						// it is closing value
						if (strValue[strValue.length()-1] == ' ')
						{
							strValue = strValue.substr(1, strValue.length()-2);
						}
						else
						{
							strValue = strValue.substr(1);
						}

						attAttribute = Attribute(decode(strName));
						attAttribute = decode(strValue);
						return true;
					}
					else
					{
						strValue += c;
					}
					break;
				}

			default:
				{
					if (strValue.length())
						strValue += c;
					else
						strName += c;
				}
			}
		}
		return false;
	};

private:

	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)
			{
				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
							{
								std::cerr << "Found invalid entity reference: " << strReference << strResult << std::endl;
								throw Exception("Found invalid entity reference: %s%s",strReference.c_str(),strResult.c_str());
							}

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

							strResult = strBegin;
						}
					}
				}
			}
		}

		return strResult;
	};

private:

	std::istream &m_isStream;
};

class AttributeWriter
{
public:
	AttributeWriter(std::ostream &os) : m_osStream(os)
	{
	};

	~AttributeWriter()
	{
	};

	const bool write(const Attribute & attAttribute)
	{
		m_osStream << encode(attAttribute.GetName()) << "=" << "'" << encode(attAttribute.GetValue()) << "'";
		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 Attribute & attAttribute)
{
	AttributeWriter w(os);
	w.write(attAttribute);
	return os;
}

inline std::istream & operator>> (std::istream & is, Attribute & attAttribute)
{
	AttributeReader r(is);
	r.read(attAttribute);
	return is;
}