/*  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]";

	fprintf(stderr, "XMLDBG: length=%ld data='%.30s'\n", length, pData ? pData : "(null)"); fflush(stderr);

	// 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 = "";
		}
	}

	fprintf(stderr, "XMLDBG: %zu lines split\n", m_lines.size()); fflush(stderr);
	// 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;
	int iterations = 0;
	while(!bHasText)
	{
		m_currentline++;
		if(++iterations > 100000) { fprintf(stderr, "XMLDBG: FindNextLine infinite loop at line %d\n", m_currentline); fflush(stderr); }

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