/** @file xmlencoder.h
*	XML encoder/decoder templates
*	@author Chris Burns (cburns@ea.com)
*
*	CTUI
*	(c) 2013 Electronic Arts Inc.
*/

#ifndef EABASETYPESXML_XMLENCODER_H_
#define EABASETYPESXML_XMLENCODER_H_

#include "EABaseTypes/factory.h"
#include "EABaseTypes/datatypes.h"

#include "EABaseTypes/string.h"

#include "UTFXml/XmlCallbackReader.h"

#include "EASTLICA/vector.h"

#include "eaassert/eaassert.h"

namespace EA
{
    namespace XML
    {
        class XmlWriter;
    }
    
	namespace Types
	{
		/*! @class XmlEncoder xmlencoder.h	"EABaseTypesXml/xmlencoder.h"
		*	@brief Encodes data from a Decoder as XML
		*/
		class XmlEncoder
		{
		public:
		
			/*!
			*	XmlEncoder Constructor
			*	@param[in]	writer	The writer to write the XML to
			*/
			XmlEncoder(EA::Allocator::ICoreAllocator* allocator, EA::XML::XmlWriter *writer);
			~XmlEncoder();

			// Encoder template interface
			void Reset();

			void BeginArray();
			void EndArray();

			void BeginArrayValue(size_t index) {}
			void EndArrayValue(size_t index) {}

			void BeginObject();
			void EndObject();

			void BeginObjectValue(const char* key, size_t length);
			void EndObjectValue(const char* key, size_t length);

			void Boolean(bool boolean);
			void Number(EA::BaseTypeFloat number);
			void String(const char* str, size_t length);
			void Function(FunctionIn function);
			void UserData(UserDataIn userData);
			void Null();

		private:

            EA::Allocator::ICoreAllocator*  mAllocator;
			EA::XML::XmlWriter*             mWriter;
		};

		/*! @class XmlDecoderParams xmlencoder.h	"EABaseTypesXml/xmlencoder.h"
		*	@brief Decoder params for XML
		*/
		struct XmlDecoderParams
		{
			XmlDecoderParams(Types::Factory* factory, EA::XML::XmlCallbackReader* callbackReader):
				mFactory(factory), mCallbackReader(callbackReader) 
			{
			}

			Types::Factory*		        mFactory;
			EA::XML::XmlCallbackReader*	mCallbackReader;
		};

		/*! @class XmlDecoder xmlencoder.h	"EABaseTypesXml/xmlencoder.h"
		*	@brief Decoder template for XML
		*	@tparam Encoder	The encoder type
		*/
		template<typename Encoder>
		class XmlDecoder
		{
		public:

			// Decoder template interface
			static void Decode(void* e, void* p)
			{
				Encoder* encoder = static_cast<Encoder*>(e);
				XmlDecoderParams* params = static_cast<XmlDecoderParams*>(p);
			
				ContentHandler contentHandler(params->mFactory, encoder);

				if(!params->mCallbackReader->Parse(&contentHandler))
                {
                    encoder->Reset();
					encoder->Null();
                    
                    const char* error = EA::XML::GetXmlReaderResultString(params->mCallbackReader->GetResultCode());

					encoder->String(error, strlen(error));
                }
                else
                {
                    encoder->String("", 0);
                }
			}

		private:

			/*! @class ReaderCallback xmlencoder.h	"EABaseTypesXml/xmlencoder.h"
			*	@brief EA::XML::IContentHandler implementation that writes into an encoder
			*/
			class ContentHandler : public EA::XML::IContentHandler
			{
			public:

				ContentHandler(Factory* factory, Encoder* encoder)
					: mFactory(factory)
					, mIndexStack(factory->GetAllocator())
					, mKeyStack(factory->GetAllocator())
					, mEncoder(encoder)
				{
				}

				virtual bool StartElement(const char* pElementName, const char** pAttributes, size_t nAttributeCount)
                {
                    EA::RawString elementName(mFactory->GetAllocator(), pElementName);
                    
                    if(elementName == "Array")
                    {
                        BeginArray();
                    }
                    else if(elementName == "Object")
                    {
                        BeginObject();
                    }
                    else if(elementName == "Value")
                    {
                        EA_ASSERT(nAttributeCount == 1);
                        BeginObjectValue(pAttributes[1]);
                    }
                    else if(elementName == "Boolean")
                    {
                        EA_ASSERT(nAttributeCount == 1);
                        
                        int value = 0;
                        sscanf(pAttributes[1], "%d", &value);
                        Boolean(value != 0);
                    }
                    else if(elementName == "Number")
                    {
                        EA_ASSERT(nAttributeCount == 1);
                        
                        double value = 0;
                        sscanf(pAttributes[1], "%lf", &value);
                        Number(static_cast<EA::BaseTypeFloat>(value));
                    }
                    else if(elementName == "String")
                    {
                        EA_ASSERT(nAttributeCount == 1);
                        
                        String(pAttributes[1]);
                    }
                    else if(elementName == "Function")
                    {
                        Null();
                    }
                    else if(elementName == "UserData")
                    {
                        Null();
                    }
                    else if(elementName == "Null")
                    {
                        Null();
                    }

					return true;
                }
                
                virtual bool EndElement(const char* pElementName)
                {
					EA::RawString elementName(mFactory->GetAllocator(), pElementName);

                    if(elementName == "Array")
                    {
                        EndArray();
                    }
                    else if(elementName == "Object")
                    {
                        EndObject();
                    }

					return true;
                }

				void BeginObject()
				{
					BeginValue();

					mEncoder->BeginObject();

					mIndexStack.push_back(-1);
				}

				void BeginObjectValue(const char* name)
				{
                    size_t length = strlen(name);
                    
					mEncoder->BeginObjectValue(name, length);

					EA::String key(mFactory, name);
					mKeyStack.push_back(key);
				}

				void EndObject()
				{ 
					mIndexStack.pop_back();

					mEncoder->EndObject();

					EndValue();
				}

				void BeginArray()
				{ 
					BeginValue();

					mEncoder->BeginArray();

					mIndexStack.push_back(0);
				}

				void EndArray()
				{ 
					mIndexStack.pop_back();

					mEncoder->EndArray();

					EndValue();
				}

				void Number(EA::BaseTypeFloat value)
				{
					BeginValue();

					mEncoder->Number(value);

					EndValue();
				}

				void Boolean(bool value)
				{ 
					BeginValue();

					mEncoder->Boolean(value);

					EndValue();
				}

				void String(const char* value)
				{
					BeginValue();

					mEncoder->String(value, strlen(value));

					EndValue();
				}
		   
				void Null()
				{
					BeginValue();

					mEncoder->Null();

					EndValue();
				}

			private:

				void BeginValue()
				{
					IndexStack::reverse_iterator it = mIndexStack.rbegin();

					if(it != mIndexStack.rend() && (*it) >= 0)
					{
						size_t val = static_cast<size_t>((*it));
						mEncoder->BeginArrayValue(val);
					}
				}

				void EndValue()
				{
					IndexStack::reverse_iterator it = mIndexStack.rbegin();

					if(it != mIndexStack.rend())
					{
						if((*it) >= 0)
						{
							size_t val = static_cast<size_t>((*it));
							mEncoder->EndArrayValue(val);

							(*it)++;
						}
                        else
						{
							const EA::String& key = mKeyStack.back();

							mEncoder->EndObjectValue(key.c_str(), key.length());

							mKeyStack.pop_back();
						}
					}
				}

				typedef EASTLICA::Vector<int> IndexStack;
				typedef EASTLICA::Vector<EA::String> KeyStack;

				Factory*	mFactory;
				IndexStack	mIndexStack;
				KeyStack	mKeyStack;

				Encoder*	mEncoder;
			};
		};
	}
}

#endif /* EABASETYPESXML_XMLENCODER_H_ */
