// XMLTree.cpp

#include "BGBase.h"
#include "XMLTree.h"

#define FILE_NUMBER FILE_NO_XMLTREE_CPP

XMLAttribute::XMLAttribute(const BGCPString& _name, const BGCPString& _value)
: VirtualClass("XMLAttribute", XMLAttributeID),
  m_name(_name),
  m_value(_value)
{
}

// Constructor
XMLTreeNode::XMLTreeNode(XMLTreeNode* parent, const BGCPString& name, const BGCPString& text)
	: VirtualClass("XMLTreeNode", XMLTreeNodeID),
	m_name(name),
	m_text(text),
	m_parent(parent)
{
	m_name = m_name.removeWhitespace();
	m_text = m_text.removeWhitespace();
}

// Destructor
XMLTreeNode::~XMLTreeNode()
{
	SET_SCOPE;

	for (size_t i = 0; i < m_childs.size(); i++) {
		delete m_childs[i];
	}

	for (size_t i = 0; i < m_attributes.size(); i++) {
		delete m_attributes[i];
	}

	RETURN;
}

// Get parent
XMLTreeNode* XMLTreeNode::getParent()
{
	return m_parent;
}
const XMLTreeNode* XMLTreeNode::getParent() const
{
	return m_parent;
}

XMLAttribute* XMLTreeNode::addAttribute(const BGCPString& attr, const BGCPString& value)
{
	SET_SCOPE;
	XMLAttribute* newAttr = new XMLAttribute(attr, value);
	m_attributes.push_back(newAttr);
	RETURN newAttr;
}

size_t XMLTreeNode::getAttributeCount() const
{
	return m_attributes.size();
}

bool XMLTreeNode::getAttributeName(size_t i, BGCPString& name) const
{
	if (i < m_attributes.size()) {
		name = m_attributes[i]->m_name;
		return true;
	} else {
		return false;
	}
}

bool XMLTreeNode::getAttributeValueByIndex(size_t i, BGCPString& value) const
{
	if (i < m_attributes.size()) {
		value = m_attributes[i]->m_value;
		return true;
	} else {
		return false;
	}
}

bool XMLTreeNode::isRoot() const NOEXCEPT
{
	return (m_parent == NULL);
}

void XMLTreeNode::setName(const BGCPString& newName)
{
	SET_SCOPE;
	m_name = newName;
	RETURN;
}
const BGCPString& XMLTreeNode::getName() const NOEXCEPT
{
	return m_name;
}

// Access childs
size_t XMLTreeNode::getChildCount() const NOEXCEPT
{
	return m_childs.size();
}
XMLTreeNode* XMLTreeNode::getChild(size_t i) NOEXCEPT
{
	SET_SCOPE;
	if (i < m_childs.size()) {
		RETURN m_childs[i];
	} else {
		RETURN nullptr;
	}
}
const XMLTreeNode* XMLTreeNode::getChild(size_t i) const NOEXCEPT
{
	SET_SCOPE;
	if (i < m_childs.size()) {
		RETURN m_childs[i];
	} else {
		RETURN nullptr;
	}
}
void XMLTreeNode::addChild(XMLTreeNode* newChild)
{
	SET_SCOPE;
	m_childs.push_back(newChild);
	RETURN;
}

const BGCPString& XMLTreeNode::getText() const NOEXCEPT
{
	return m_text;
}

void XMLTreeNode::setText(const BGCPString& text)
{
	m_text = text;
}

// Constructors
XMLTreeConstIterator::XMLTreeConstIterator(const XMLTreeNode* node)
: VirtualClass("XMLTreeConstIterator", XMLTreeConstIteratorID),
  m_currNode(node)
{
}

// Copy constructor
XMLTreeConstIterator::XMLTreeConstIterator(const XMLTreeConstIterator& iter)
: VirtualClass("XMLTreeConstIterator", XMLTreeConstIteratorID)
{
	m_currNode = iter.m_currNode;
}

// Destructor
XMLTreeConstIterator::~XMLTreeConstIterator()
{
}


// XML tree navigation
bool XMLTreeConstIterator::goToParent() NOEXCEPT
{
	if (m_currNode != NULL) {
		SET_SCOPE;
		m_currNode = m_currNode->getParent();
		RETURN true;
	} else {
		return false;
	}
}

bool XMLTreeConstIterator::goToChild(size_t child)
{
	if (m_currNode != NULL) {
		SET_SCOPE;
		if (child < m_currNode->getChildCount()) {
			m_currNode = m_currNode->getChild(child);
			RETURN true;
		} else {
			RETURN false;
		}
	} else {
		return false;
	}
}

bool XMLTreeConstIterator::goToChildByName(const BGCPString& childName)
{
	size_t i = 0;
	bool keepScanning = true;
	while (keepScanning) {
		keepScanning = goToChild(i);
		if (keepScanning) {
			if (nodeNameEquals(childName)) {
				return true;
			} else {
				i++;
				goToParent();
			}
		}
	}

	return false;
}

void XMLTreeConstIterator::goToRoot() NOEXCEPT
{
	while (!isRoot()) {
		goToParent();
	}
}

// Getters
bool XMLTreeConstIterator::getNodeName(BGCPString& name) const
{
	if (m_currNode != nullptr) {
		name = m_currNode->getName();
		return true;
	} else {
		return false;
	}
}

bool XMLTreeConstIterator::getAttributeName(size_t i, BGCPString& str) const
{
	if (m_currNode != NULL) {
		bool result = m_currNode->getAttributeName(i, str);
		return result;
	} else {
		return false;
	}
}

bool XMLTreeConstIterator::getAttributeValueByIndex(size_t i, BGCPString& value) const
{
	if (m_currNode != NULL) {
		if (i < m_currNode->getAttributeCount()) {
			return m_currNode->getAttributeValueByIndex(i, value);
		}
	}
	return false;
}

// Scan the attributes of the current node for the given attrName and puts its value into value
// If nothing is found, value is left unchanged.
bool XMLTreeConstIterator::getAttributeValueByName(const BGCPString& attrName, BGCPString& value) const
{
	for (size_t i = 0; i < getAttributeCount(); i++) {
		BGCPString attrName2;
		if (getAttributeName(i, attrName2)) {
			if (attrName.equalsNoCase(attrName2)) {
				return getAttributeValueByIndex(i, value);
			}
		}
	}
	return false;
}

int64_t XMLTreeConstIterator::getIntAttributeValueByName(const BGCPString& aAttrName, int64_t defaultVal) const
{
	BGCPString str;
	if (getAttributeValueByName(aAttrName, str)) {
		return str.toInt();
	} else {
		return defaultVal;
	}
}

bool XMLTreeConstIterator::hasAttribute(const BGCPString& searchedAttrName) const
{
	SET_SCOPE;
	for (unsigned i = 0; i < getAttributeCount(); i++) {
		BGCPString attrName;
		getAttributeName(i, attrName);
		if (attrName == BGCPString(searchedAttrName)) {
			RETURN true;
		}
	}
	RETURN false;
}

double XMLTreeConstIterator::getDoubleAttributeByName(const BGCPString& attrName, double defaultVal) const
{
	double result = defaultVal;
	BGCPString valueStr;

	if (getAttributeValueByName(attrName, valueStr)) {
		result = valueStr.toDouble();
	}

	return result;
}

BGCPString XMLTreeConstIterator::getText() const
{
	if (m_currNode != NULL) {
		return m_currNode->getText();
	} else {
		return BGCPString();
	}
}

size_t XMLTreeConstIterator::getChildCount() const NOEXCEPT
{
	if (m_currNode != nullptr) {
		return m_currNode->getChildCount();
	} else {
		return 0;
	}
}

size_t XMLTreeConstIterator::getAttributeCount() const NOEXCEPT
{
	if (m_currNode != NULL) {
		return m_currNode->getAttributeCount();
	} else {
		return 0;
	}
}

bool XMLTreeConstIterator::isRoot() const NOEXCEPT
{
	if (m_currNode != NULL) {
		return m_currNode->isRoot(); 
	} else {
		return false;   
	}
}

const XMLTreeNode* XMLTreeConstIterator::getTreeNode() const NOEXCEPT
{
	return m_currNode;
}

// Check if a node name equals the given string (case-insensitive)
bool XMLTreeConstIterator::nodeNameEquals(const BGCPString& str) const 
{
	if (m_currNode != NULL) {
		SET_SCOPE;
		bool result = m_currNode->getName().equalsNoCase(str);
		RETURN result;
	} else {
		return false;   
	}
}

// Add a child to a node, returning its number
size_t XMLTreeIterator::addChild(const BGCPString& name)
{
    XMLTreeNode* newnode = new XMLTreeNode(m_currNode, name);
    if (m_currNode != NULL) {
        m_currNode->addChild(newnode);
    } else {
        m_currNode = newnode;
        m_tree->setRoot(newnode);
    }
    return m_currNode->getChildCount()-1;
}

// Constructors
XMLTreeIterator::XMLTreeIterator(XMLTree* tree, XMLTreeNode* node)
	: VirtualClass("XMLTreeIterator", XMLTreeIteratorID),
	m_currNode(node),
	m_tree(tree)
{
}

XMLTreeIterator::XMLTreeIterator(const XMLTreeIterator& iter)
	: VirtualClass("XMLTreeIterator", XMLTreeIteratorID),
	m_currNode(iter.m_currNode),
	m_tree(iter.m_tree)
{
}

// XML tree navigation
bool XMLTreeIterator::goToParent() NOEXCEPT
{
	SET_SCOPE;
	if (m_currNode != NULL) {
		m_currNode = m_currNode->getParent();
		RETURN true;
	} else {
		RETURN false;
	}
}
bool XMLTreeIterator::goToChild(size_t child)
{
	SET_SCOPE;
	bool result = false;
	if (m_currNode != NULL) {
		if (child < m_currNode->getChildCount()) {
			m_currNode = m_currNode->getChild(child);
			result = true;
		}
	}
	RETURN result;
}

void XMLTreeIterator::goToRoot() NOEXCEPT
{
	while (!isRoot())
		goToParent();
}

BGCPString XMLTreeIterator::getAttributeName(size_t i) const
{
	SET_SCOPE;
	BGCPString result;
	if (m_currNode != NULL) {
		m_currNode->getAttributeName(i, result);
	}

	RETURN result;
}

bool XMLTreeIterator::getAttributeValueByIndex(size_t i, BGCPString& val) const
{
	if (m_currNode != NULL) {
		if (i < m_currNode->getAttributeCount()) {
			return m_currNode->getAttributeValueByIndex(i, val);
		}
	}
	return false;
}

bool XMLTreeIterator::getAttributeValueByName(const BGCPString& aAttrName, BGCPString& val) const
{
	for (unsigned i = 0; i < getAttributeCount(); i++) {
		BGCPString attrName = getAttributeName(i);
		if (attrName == aAttrName) {
			return getAttributeValueByIndex(i, val);
		}
	}
	return false;
}

BGCPString XMLTreeIterator::getText() const
{
	if (m_currNode != nullptr) {
		return m_currNode->getText();
	} else {
		return BGCPString();
	}
}
size_t XMLTreeIterator::getChildCount() const
{
	return m_currNode->getChildCount();
}

size_t XMLTreeIterator::getAttributeCount() const
{
	return m_currNode->getAttributeCount();
}

bool XMLTreeIterator::isRoot() const
{
	return m_currNode->isRoot();
}

const XMLTreeNode* XMLTreeIterator::getTreeNode() const
{
	return m_currNode;
}

// Add a child to a node, returning its number
size_t addChild(const BGCPString& name);

// Add an attribute 
bool XMLTreeIterator::addAttribute(const BGCPString& name, const BGCPString& value)
{
	if (m_currNode != NULL) {
		m_currNode->addAttribute(name, value);
		return true;
	} else {
		return false;
	}
}

void XMLTreeIterator::addAttribute(const BGCPString& name, int value)
{
	BGCPString valueStr = BGCPString::fromInt(value);
	addAttribute(name, valueStr);
}

void XMLTreeIterator::addIntAttribute(const BGCPString& attr, int64_t value)
{
	BGCPString str = BGCPString::fromInt(value);
	addAttribute(attr, str);
}

void XMLTreeIterator::addDoubleAttribute(const BGCPString& attr, double value, int precision)
{
	BGCPString str = BGCPString::fromDouble(value, precision);
	addAttribute(attr, str);
}

// Add text to a node
bool XMLTreeIterator::addText(const BGCPString& text)
{
	if (text.empty() || m_currNode == NULL) {
		return false;
	}

	SET_SCOPE;
	XMLTreeNode* textNode = new XMLTreeNode(m_currNode, "", text);
	m_currNode->addChild(textNode);
	RETURN true;
}

// Create a copy of a part of another tree
void XMLTreeIterator::copyFromChilds(XMLTreeConstIterator& constIter)
{
	if (m_currNode == nullptr) {
		return;
	}

	// Copy node name
	SET_SCOPE;
	BGCPString nodeName;
	constIter.getNodeName(nodeName);
	m_currNode->setName(nodeName);

	// Copy text
	BGCPString text = constIter.getText();
	m_currNode->setText(text);

	// Copy attributes
	for (size_t i = 0; i < constIter.getAttributeCount(); i++) {
		BGCPString attrName, attrValue;
		if (constIter.getAttributeName(i, attrName) && constIter.getAttributeValueByIndex(i, attrValue)) {
			addAttribute(attrName, attrValue);
		}
	}

	// Copy child nodes
	for (size_t i = 0; i < constIter.getChildCount(); i++) {
		constIter.goToChild(i);
		BGCPString childText = constIter.getText();
		if (childText.empty()) {
			size_t newChildNum = addChild(BGCPString()); // Proper name set during recursion
			goToChild(newChildNum);
			copyFromChilds(constIter);
			goToParent();
		} else {
			addText(constIter.getText());
		}
		constIter.goToParent();
	}

	RETURN;
}

// Constructor
XMLTree::XMLTree()
: VirtualClass("XMLTree", XMLTreeID),
  indentStr("\t"),
  root(NULL)
{
}

// Construct with named root node
XMLTree::XMLTree(const BGCPString& rootNodeName)
: VirtualClass("XMLTree", XMLTreeID),
  indentStr("   "),
  root(NULL)
{
	root = new XMLTreeNode(nullptr, rootNodeName);
}


// Destructor
XMLTree::~XMLTree()
{
	safeDelete(root);
}

// Load XML from file
bool XMLTree::loadFromFile(const PathChar* path)
{
	SET_SCOPE;
	BGCPString str;
	if (str.loadFromFile(path)) {
		bool result = parse(str);
		RETURN result;
	} else {
		RETURN false;
	}
}

bool XMLTree::recSave(BGCPString& out, unsigned int indents, XMLTreeNode* node)
{
	SET_SCOPE;
	BGCPString line, LineIndent;
	bool result = true;
	if (!indentStr.empty())
		for (unsigned int i = 0; i < indents; i++)
			LineIndent += indentStr;

	// Save tag node
	BGCPString nodeName = node->getName();
	if (!nodeName.empty()) {
		line = LineIndent;
		line += "<";
		line += nodeName;

		// Save tag node's attributes
		for (size_t i = 0; i < node->getAttributeCount(); i++) {
			BGCPString attrName, attrValue;
			if (node->getAttributeName(i, attrName)
			&&  node->getAttributeValueByIndex(i, attrValue))
			{
				line += " ";
				line += attrName;
				line += " = \"";
				line += attrValue;
				line += "\"";
			}
		}

		// Add child tags
		if (node->getChildCount() != 0) {
			line += ">\n";
			out += line;
			for (unsigned int j = 0; j < node->getChildCount(); j++) {
				XMLTreeNode* child = node->getChild(j);
				result &= recSave(out, indents + 1, child);
			}

			// Add end tag
			if (!nodeName.empty()) {
				line = LineIndent;
				line += "</";
				line += node->getName();
				line += ">\n";
				out += line;
			}
		} else {
			line += "/>\n";
			out += line;
		}
		line.clear();
	}

	// Save text node
	const BGCPString& text = node->getText();
	if (!text.empty()) {
		out += text;
		out += "\n";
	}

	RETURN result;
}

// Copy XML tree nodes
void copyNodesRecursively(XMLTreeConstIterator* inIter, XMLTreeIterator* outIter)
{
	// Text nodes have no child nodes and no attributes
	SET_SCOPE;
	BGCPString newNodeText = inIter->getText();
	if (!newNodeText.empty()) {
		outIter->addText(newNodeText);
		RETURN;
	}

	// Copy current node over
	BGCPString nodeName;
	inIter->getNodeName(nodeName);
	auto newNodeNum = outIter->addChild(nodeName);
	outIter->goToChild(newNodeNum);

	// Cope node attributes
	SET_OP;
	for (size_t i = 0; i < inIter->getAttributeCount(); ++i) {
		BGCPString attrVal, attrName;
		inIter->getAttributeName(i, attrName);
		inIter->getAttributeValueByIndex(i, attrVal);
		outIter->addAttribute(attrName, attrVal);
	}

	// Copy child nodes
	SET_OP;
	for (size_t i = 0; i < inIter->getChildCount(); ++i) {
		inIter->goToChild(i);
		copyNodesRecursively(inIter, outIter);
		inIter->goToParent();
	}
	outIter->goToParent();

	RETURN;
}

bool XMLTree::saveToString(BGCPString& out)
{
	return recSave(out, 0, root);
}

bool XMLTree::saveToFile(const PathChar* fn, WORD cp)
{
	BGCPString data;
	if (saveToString(data)) {
		return data.saveToFile(fn, cp);
	} else {
		return false;
	}
}

// Iterators
// removed - it used the new operator, thus prevented the use of node iterators on the stack
/*XMLTreeConstIterator* XMLTree::getConstIterator()
{
	// Do not allow iterating over an empty XML file
	if (root != NULL) {
		return new XMLTreeConstIterator(root);
	} else {
		return NULL;
	}
}*/

// Root access
void XMLTree::setRoot(XMLTreeNode* newRoot)
{
	safeDelete(root);
	root = newRoot;
}

XMLTreeNode* XMLTree::getRoot()
{
	return root;
}

const XMLTreeNode* XMLTree::getRoot() const
{
	return root;
}

// Parse XML from string
bool XMLTree::parse(const BGCPString& data)
{
	SET_SCOPE;
	safeDelete(root);

	size_t offset = 0;
	const int ReadText = 0;
	const int ReadTag = 1;
	const int ReadAttr = 2;
	const int ReadAttrVal = 3;
	const int ReadEscapeSequence = 4;
	int quotes = 0;
	int mode = 0;
	BGCPString attr;
	BGCPString value;
	BGCPString tag;
	BGCPString text;
	BGCPString esc; // for reading escape sequences
	XMLTreeNode* currNode = NULL;
	size_t dataSize = data.getByteCount();

	while (offset < dataSize)
	{
		// Read text?
		auto cs = data.getCharSize(offset);
		switch (mode) {
		case ReadText:
			if (data.charAtEquals(offset, '<')) {
				SET_OP;
				// Create text node, then stop reading text 
				if (!text.empty() && currNode != NULL) {
					XMLTreeNode* newNode = new XMLTreeNode(currNode, "", text);
					currNode->addChild(newNode);
				}
				mode = ReadTag;
				tag.clear();
				text.clear();
			} else {
				SET_OP;
				if (data.charAtEquals(offset, '&')) {
					mode = ReadEscapeSequence;
					esc.clear();
				} else {
					if ((!data.charAtEquals(offset,' ')
					&&   !data.charAtEquals(offset,9)
					&&   !data.charAtEquals(offset,0x0d)
					&&   !data.charAtEquals(offset,0x0a))
					||  !text.empty())
					{
						text.add(data.getPtr<char>(offset), data.getCP(), cs);
					}
				}
			}
			break;
		case ReadTag:
			if (data.charAtEquals(offset, '/')) {
				SET_OP;
				// Skip to >
				do {
					offset++;
				} while (!data.charAtEquals(offset, '>'));
				// Store non-empty tag ending immediately?
				if (!tag.empty()) {
					XMLTreeNode* newNode = new XMLTreeNode(currNode, tag);
					if (currNode != nullptr) {
						currNode->addChild(newNode);
						currNode = newNode;
					} else {
						setRoot(newNode);
					}
				}
				tag.clear();
				mode = ReadText;
				if (currNode != nullptr) {
					currNode = currNode->getParent();
				}
			} else if (data.charAtEquals(offset, '>')) {
				SET_OP;
				XMLTreeNode* newNode = new XMLTreeNode(currNode, tag, "");
				if (currNode != NULL)
					currNode->addChild(newNode);
				if (root == NULL)
					root = newNode;
				currNode = newNode;
				mode = ReadText;
				tag.clear();
			} else if (!data.charAtEquals(offset, ' ') 
			&& !data.charAtEquals(offset, 0x0d)
			&& !data.charAtEquals(offset, 0x0a)
			&& !data.charAtEquals(offset, 9)) 
			{
				SET_OP;
				tag.add(data.getPtr<char>(offset), data.getCP(), cs);
			} else if (!tag.empty()) {
				SET_OP;
				XMLTreeNode* newNode = new XMLTreeNode(currNode, tag, "");
				if (currNode != NULL)
					currNode->addChild(newNode);
				if (root == NULL)
					root = newNode;
				currNode = newNode;
				mode = ReadAttr;
				attr.clear();
				value.clear();
			}
			break;
		case ReadAttr:
			if (data.charAtEquals(offset, '/')) {
				SET_OP;
				currNode = currNode->getParent();
				// Skip to ">"
				do {
					offset++;
				} while (!data.charAtEquals(offset, '>'));
				tag.clear();
				mode = ReadText;
			} else if (data.charAtEquals(offset, '>')) {
				mode = ReadText;
			} else if (data.charAtEquals(offset, '=')) {
				mode = ReadAttrVal;
			} else if (!data.charAtEquals(offset, ' ') && !data.charAtEquals(offset, 0x0d) && !data.charAtEquals(offset, 0x0a) && !data.charAtEquals(offset, 9)) {
				attr.add(data.getPtr<char>(offset), data.getCP(), cs);
			}

			break;
		case ReadAttrVal:
			SET_OP;
			if (data.charAtEquals(offset, '>') && (quotes % 2 == 0)) {
				mode = ReadText;
				currNode->setName(tag);
				currNode->addAttribute(attr, value);
				attr.clear();
				value.clear();
			} else if (data.charAtEquals(offset, '\"')) {
				++quotes;
			} else {
				if ((data.charAtEquals(offset, ' ') && (quotes % 2 == 0))
				||   data.charAtEquals(offset, 0x0d)
				||   data.charAtEquals(offset, 0x0a))
				{
					// Create attribute only if both attr and value are non-empty
					// edge case 1: tag = "x", here we might think that we're done after encountering the space after the equals sign (currently works correctly)
					// edge case 2: tag="", here value is still empty because there is nothing between the quotes, so the parser doesn't currently support this
					if (!value.empty()) {
					//|| ((quotes > 0) && (quotes % 2 == 0))) { // should fix tag="", but doesn't work ...
						if (!attr.empty()) {
							currNode->addAttribute(attr, value);
							attr.clear();
						}
						value.clear();
						mode = ReadAttr;
					}
				} else if (!data.charAtEquals(offset, '/') || (quotes % 2 != 0)) {
					value.add(data.getPtr<char>(offset), data.getCP(), cs);
				} else { // c=='/'
					currNode->addAttribute(attr, value);
					currNode->setName(tag);
					attr.clear();
					value.clear();
					mode = ReadText;
					currNode = currNode->getParent();
					// Skip to >
					do {
						offset++;
					} while (!data.charAtEquals(offset, '>'));
					mode = ReadText;
				}
			}
			break;
		case ReadEscapeSequence:
			SET_OP;
			if (data.charAtEquals(offset, ';')) {
				if (esc == "amp")
					text += "&";
				else if (esc == "lt")
					text += "<";
				else if (esc == "gt")
					text += ">";
				mode = ReadText;
			} else {
				esc.add(data.getPtr<char>(offset), data.getCP(), cs);
			}
			break;
		}
		offset += cs;
	}

	RETURN true;
}
