#include "document.h"

#include <strstream>
#include <fstream>

#include "attribute.h"
#include "element.h"

namespace kLibrary
{ 
		XMLDocument::XMLDocument(void)
		{
			//If not parsing XML, nothing needs to be done
		}
		
		XMLDocument::XMLDocument(const std::string& input, const inputType mode)
		{
			//Parse the specified XML
			open(input, mode);
		}
		
		void XMLDocument::open(const std::string& input, const inputType mode)
		{
			//Call the open function which specificly handles the mode being used.
			switch (mode)
			{
				case XML_XML:	//XML is being passed in
					openString(input);
					break;
				
				case XML_File:	//A file should be loaded
					openFile(input);
					break;
				
				default:		//Unknown type...do nothing
					break;
			}
		}
		
		void XMLDocument::openString(const std::string& XMLDocument)
		{
			//Clear the elements tree to prevent stacking XML document
			_elements.clear();
			
			//Begin the parsing recursion
			_parseString(XMLDocument, _elements);
		}
		
		void XMLDocument::openFile(const std::string& filename)
		{
			//Read the file in
			_elements.clear();
			std::ifstream file(filename.c_str());
			file.seekg(0, std::ios::end);
			std::streamsize length = file.tellg();
			file.seekg(0, std::ios::beg);
			char* buff = new char[length + 2];
			file.get(buff, length, '\0');
			buff[length + 1] = '\0';
			
			//Begin the parsing recursion
			_parseString(std::string(buff), _elements);
			
			//Clean-up
			delete [] buff;
		}
		
		const XML::elements& XMLDocument::getElements(void) const
		{
			return _elements;
		}
		
		const XML::elementIterator XMLDocument::getFirstElement(void) const
		{
			return _elements.begin();
		}
		
		const XML::elementIterator XMLDocument::getEndElement(void) const
		{
			return _elements.end();
		}

		const XML::elementIterator XMLDocument::findFirstChildElement(const std::string& elementName) const
		{
			XML::elementIterator cur = _elements.begin();
			const XML::elementIterator end = _elements.end();

			for(; cur != end; cur++)
				if (strcmp(cur->element.c_str(), elementName.c_str()) == 0)
					return cur;

			return end;
		}

		const std::streamsize XMLDocument::_parseString(const std::string& input, XML::elements& output)
		{
			//Generate a stream from the input
			std::streamsize cLength = std::streamsize(input.length() + 1);
			char* cString = new char[cLength + 1];
			strcpy_s(cString, cLength, input.c_str());
			std::strstream stream(cString, std::streamsize(cLength), std::ios_base::in);
			
			//Store some space for us
			XML::Element out;
			XML::Attribute tAttribute;
			
			char* cbuff = new char[cLength];
			char* endtag = 0;
			char *lbegin, *lend;
			char tend;
			const char* const cend = cbuff + cLength;
			bool noEndTag = false;
			
			//Find element
			stream.getline(cbuff, cLength, '>');
			
				//Find first non-whitespace
			lbegin = cbuff;
			while ((lbegin[0] == ' ') || (lbegin[0] == '\t') || (lbegin[0] == '\n'))
				lbegin++;
			
			if (lbegin[0] != '<')
			{
				//Unable to parse the XML
				delete [] cString;
				delete [] cbuff;
				return 0;
			}
			
				//Find end of tag name
			lend = lbegin;
			do
				lend++;
			while ((lend[0] != ' ') && (lend[0] != '>') && (lend[0] != '\0'));
			tend = lend[0];
			lend[0] = '\0';
			
				//Save element name
			out.element = std::string(++lbegin);
			lend[0] = tend;
			
			//Seperate attributes
			lbegin = lend;
			while ((lbegin[0] != '\0') && (lbegin[0] != '>') && (lbegin < cend))
			{
				//Find first non-whitespace
				while ((lbegin[0] == ' ') || (lbegin[0] == '\t') || (lbegin[0] == '\n') && (lbegin < cend))
					lbegin++;
				
				//Find end of attribute name
				lend = lbegin;
				do
					lend++;
				while ((lend[0] != '=') && (lend[0] != '>') && (lend[0] != ' ') && (lend[0] != '\0') && (lend[0] != '\t') && (lend[0] != '\n') && (lend < cend));
				tend = lend[0];
				lend[0] = '\0';
				
				//Handle self-closed tags (ended with a '/>' or a '?>' or a '!>)
				if ((strcmp(lbegin, "/") == 0) || (strcmp(lbegin, "?") == 0) || (strcmp(lbegin, "!") == 0))
				{
					lend[0] = tend;
					noEndTag = true;
					break;
				}
				tAttribute.name = std::string(lbegin);
				
				lend[0] = tend;
				
				//Find the equals...theres a space between the name and the equals, or no equals
				if (lend[0] != '=')
				{
					do
						lend++;
					while ((lbegin[0] == ' ') || (lbegin[0] == '\t') || (lbegin[0] == '\n') && (lbegin < cend));
				}
				
				//Handle non-well formed XML (no attribute value)
				if (lend[0] != '=')
					tAttribute.value = tAttribute.name;
				else
				{
					//Ignore white space between the equals and the value
					lbegin = lend;
					do
						lbegin++;
					while ((lbegin[0] == ' ') || (lbegin[0] == '\t') || (lbegin[0] == '\n') && (lbegin < cend));
					
					//Handle both quations and no quotations
					lend = lbegin;
					if ((lbegin[0] == '"') || (lbegin[0] == '\''))
					{
						//Find end quotation
						do
							lend++;
						while ((lend[0] != '"') && (lend[0] != '\'') && (lend[0] != '\0') && (lend < cend));
						lbegin++;
					}
					else
						//Find first white space
						do
							lend++;
						while ((lend[0] != ' ') && (lend[0] != '\t') && (lend[0] != '\0') && (lend[0] != '\n') && (lend < cend));
						
					tend = lend[0];
					lend[0] = '\0';
					
					//Store the value
					tAttribute.value = std::string(lbegin);
					
					lend[0] = tend;
					lend++;
				}
				
				//Store the attribute
				out.attributes.push_back(tAttribute);
				lbegin = lend;
			}
			
			//Find value
			if (!noEndTag)
			{
				//Calculate end tag
				{
					endtag = new char[cLength + 3];
					endtag[0] = '<';
					endtag[1] = '/';
					endtag[2] = 0;

					const char* s = out.element.c_str();
					char* t = endtag;
					t += 2;
					const char* e = endtag + 2 + cLength;
					const char* E = s + out.element.length();
					while ((t < e) && (s < E) && (s[0] != '\0'))
					{
						*t = *s;
						s++;
						t++;
					}
					*t = 0;
				}
				
				//Init varibles
				stream.getline(cbuff, cLength, '<');
				stream.seekg(-1, std::ios_base::cur);
				std::string tVal(cbuff);
				
				//Find end tag
				do
				{
					stream.getline(cbuff, cLength, '>');
					lbegin = cbuff;
					while ((lbegin[0] == ' ') || (lbegin[0] == '\t') || (lbegin[0] == '\n'))
						lbegin++;
					if (strcmp(lbegin, endtag) != 0)
					{
						tVal += cbuff;
						tVal += '>';
					}
					else
						break;
				} while (stream.good() && (!stream.eof()));
				
				//Do some clean-up
				delete [] out.valBuff;
				delete out.value;
				
				/*Check for and parse children from the value*/
				lbegin = cbuff;
				out.buffLength = tVal.length() + 1;
				out.valBuff = new char[out.buffLength + 1];
				strcpy_s(out.valBuff, out.buffLength, tVal.c_str());
				std::strstream t;
				out.value = new std::strstream(out.valBuff, std::streamsize(strlen(out.valBuff)));
				std::streamsize change = 0;
				do
				{
					change  = _parseString(tVal, out.children);
					tVal.erase(0, change);
				} while (change != 0);
			}
			
			//Add this element to the output array
			output.push_back(out);
			
			//Check for special tags (<!...!> and <?...?>)
			if ((out.element.c_str()[0] == '!') || (out.element.c_str()[0] == '?'))
			{
				//If so, continue parsing
				stream.get(cbuff, std::streamsize(cLength), '\0');
				std::streamsize change = 0;
				lbegin = cbuff;
				do
				{
					change  = _parseString(std::string(lbegin), output);
					lbegin += change;
				} while (change != 0);
			}
			
			//Finish clean-up
			delete [] cString;
			delete [] cbuff;
			if (endtag != 0)
				delete [] endtag;
			
			//And return the amount of string handled.
			return stream.tellg();
		}
}