/*  DS
 *  Copyright (C) Joakim Kolsjö and Anders Asplund 2005
 *	This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 
 */
 
#include "xmlparser.h"
#include <cstdio>

using namespace std;

namespace DS
{
	XMLElement::XMLElement(int row)
	{
		m_row = row;
	}
	
	XMLElement::~XMLElement()
	{
		for(int i = 0; i < (int)m_ChildElements.size(); i++)
			delete m_ChildElements[i];
		m_ChildElements.clear();
		m_attributes.clear();	
	}

	int XMLElement::GetRow()
	{
		return m_row;
	}
	
	void XMLElement::SetValue(const char* pValue)
	{
		m_value = pValue;
	}
	
	const char* XMLElement::GetAttribute(const char* pKey)
	{
		if(m_attributes[pKey] == "")
			return 0;
		
		return m_attributes[pKey].c_str();
	}
	
	const char* XMLElement::GetValue()
	{
		return m_value.c_str();
	}
	
	std::vector<XMLElement*> XMLElement::GetChildElements()
	{
		return m_ChildElements;
	}
	
	void XMLElement::AddAttribute(const char* pName, const char* pValue)
	{
		m_attributes[pName] = pValue;
	}
	
	void XMLElement::AddChildElement(XMLElement* pElement)
	{
		m_ChildElements.push_back(pElement);
	}
	
	XMLParser::XMLParser()
	{
		m_pRootElement = 0;
	}
	
	XMLParser::~XMLParser()
	{
		if(m_pRootElement)
			delete m_pRootElement;
	}
#if 0
	XMLElement* XMLParser::Load(const char* pFile)
	{
		static char* fn = "[XMLParser::Load]";
		
		// Open file
		FILE* pF = fopen(pFile, "rb");
		if(!pF)
			throw Exception(fn, (string)"Failed to open " + pFile + ".");
		
		// Read data
		fseek(pF, 0, SEEK_END);
		long size = ftell(pF);
		fseek(pF, 0, SEEK_SET);
		
		char* pData = new char[size];
		if(!fread(pData, size, 1, pF))
			throw Exception(fn , (string)"Failed to read from " + pFile + ".");
		
		// Parse and cleanup
		XMLElement* pElement = Parse(pData, size);
		delete pData;
		
		return pElement;
	}
#endif

	XMLElement* XMLParser::Parse(const char* pData, long length)
	{
		static char* fn = "[XMLParser::Parse]";

		// Split data into m_lines
		string temp;
		for(int i = 0; i < (int)length; i++)
		{
			temp += pData[i];
			if(pData[i] == '\n')
			{	
				// Fix the "\n" linebreaks
				unsigned int lbegin = 0;
				unsigned int lend = 0;
				string out;
				for(;;)
				{
					lend = temp.find("\\n", lbegin);
					if(lend != string::npos)
					{
						out += temp.substr(lbegin, lend-lbegin);
						out += '\n';
						lbegin = lend+2;
					}
					else {
							out += temp.substr(lbegin, temp.size()-lbegin);
						break;
					}
				}
				
				m_lines.push_back(out);
				temp = "";
				out = "";
			}
		}

		// Look for XML header
		m_currentline = 0;
		unsigned int loc = m_lines[m_currentline].find("<?");
		if(loc == string::npos)
			throw Exception(fn, "No XML header.");

		FindNextLine();
		m_pRootElement = ParseElement();
		
		return m_pRootElement;
	}
	
	XMLElement* XMLParser::ParseElement()
	{
		static char* fn = "[XMLParser::ParseElement]";
		XMLElement* pElement = new XMLElement(m_currentline+1);
				
		// Look for begin tag
		unsigned int l0 = m_lines[m_currentline].find("<");
		if(l0 == string::npos)
			throw Exception(fn, "\"<\" expected at line " + itoa(m_currentline+1) + ".");
				
		// Look for element value
		unsigned int l1 = m_lines[m_currentline].find_first_of(" ", l0);
		if(l1 == string::npos)
			throw Exception(fn, "space expected after element value at line " + itoa(m_currentline+1) + ".");

		string evalue = m_lines[m_currentline].substr(l0+1, l1 - l0 - 1);
		pElement->SetValue(evalue.c_str());		
		
		// Look for element end
		l0 = l1;
		l1 = m_lines[m_currentline].find(">", l1);
		if(l1 == string::npos)
			throw Exception(fn, "\">\" expected at line " + itoa(m_currentline+1) + ".");
				
		// Get arguments
		string alist = m_lines[m_currentline].substr(l0+1, l1 - l0 - 1);
		
		l0 = 0, l1 = 0;
		for(;;)
		{
			// Find attribute name
			if(l1)
				l0 = alist.find_first_not_of(" ", l1+1);
			else
				l0 = alist.find_first_not_of(" ");
			l1 = alist.find("=", l0+1);
			
			if(l1 == string::npos || l0 == string::npos)
				break;
		
			string aname = alist.substr(l0, l1-l0);
		
			// Find attribute value
			l0 = alist.find("\"", l1) + 1;
			l1 = alist.find("\"", l0+1);
			
			if(l1 == string::npos || l0 == string::npos)
				break;
			
			string avalue = alist.substr(l0, l1-l0);
			
			// Add attribute
			pElement->AddAttribute(aname.c_str(), avalue.c_str());
		}
		
		// Look for endtag  (/>)
		if(m_lines[m_currentline].find("/>") != string::npos)
			return pElement;
		
		for(;;)
		{
			FindNextLine();
			
			// Look for endtag  (</value>)
			if(m_lines[m_currentline].find("</") != string::npos) {
				return pElement;
			}
			else // Handle child element
			{ 
				XMLElement* pChildElement = ParseElement();
				pElement->AddChildElement(pChildElement);
			}
		}	
	}
	
	void XMLParser::FindNextLine()
	{
		bool bHasText = false;
		bool bComment = false;
		int CommentStart = 0;
		while(!bHasText)
		{
			m_currentline++;
			
			if(m_currentline == (int)m_lines.size()) {
				if(bComment)
					throw Exception("[XMLParser::FindNextLine]",
						(string)"Unterminated comment started at line "
						+ itoa(CommentStart) + ".");
				else
					throw Exception("[XMLParser::FindNextLine]", "Reached end of file! (</value> is probably missing somewhere...)");
			}
				
			// Look for non-space characters
			if(m_lines[m_currentline].size())
			{
				for(int i = 0; i < (int) m_lines[m_currentline].size(); i++)
				{
					if(!isspace(m_lines[m_currentline][i]))
					{
						// Check for single line comment
						if(m_lines[m_currentline].find("<!--") != string::npos
							&& m_lines[m_currentline].find("-->") != string::npos)
							break;
						// Check for multiline comment start
						else if(m_lines[m_currentline].find("<!--") != string::npos) {
							CommentStart = m_currentline+1;
							bComment = true;
						}
						// Check for multiline comment stop
						else if(m_lines[m_currentline].find("-->") != string::npos)
							bComment = false;
						// Text found
						else if(!bComment)
							bHasText = true;
						
						break;
					}
				}
			}
		}
	}
}
