//--------------------------------------------------------------------------------
// <copyright file="LqrSMACXmlDOM.cpp" company="LiquidRobotics">
//
// Copyright (c) 2014 Liquid Robotics, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// This software is associated with a release.  Liquid Robotics makes no
// representation that the design, interfaces or component distribution will
// remain unchanged in future releases.
//
// </copyright>
//
// <summary>
//   Implementation of utility functions and classes that enable data to be stored
//   and retrieved from an XML document.
// </summary>
//
// <owner>Mike Cookson</owner>
//--------------------------------------------------------------------------------

#include "LqrSMACXmlDOM.h"
#include "LqrSMACByteArray.h"
#include "LqrSMACGetErrorDescription.h"
#include "nsextend.h"
#include <boost/algorithm/string.hpp>
#include <cerrno>
#include <sstream>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdint.h>

using namespace std;
using namespace pugi;

namespace LiquidRobotics {
namespace SMACFramework {
namespace XmlDOM {

const string StandardElementTag = "Element";
const string XmlNVPBase::defaultAttributeName = "";

IMPLEMENT_EXCEPTION(XmlAttributeNotFound,std::runtime_error);
IMPLEMENT_EXCEPTION(XmlChildNodeNotFound,std::runtime_error);
IMPLEMENT_EXCEPTION(XmlOptionalAttributeNotFound,std::runtime_error);
IMPLEMENT_EXCEPTION(XmlOptionalChildNodeNotFound,std::runtime_error);

//////////////////////////
//// XmlBoolean Class ////
//////////////////////////

XmlBoolean::XmlBoolean(bool& val)
: XmlCNCValue<bool>(val)
{
}

XmlBoolean::XmlBoolean(const bool& val)
: XmlCNCValue<bool>(val)
{
}

////////////////////////////////
//// XmlBooleanVector Class ////
////////////////////////////////

XmlBooleanVector::XmlBooleanVector(std::vector<bool>& val)
: XmlCNCValue<std::vector<bool> >(val)
{
}

XmlBooleanVector::XmlBooleanVector(const std::vector<bool>& val)
: XmlCNCValue<std::vector<bool> >(val)
{
}

XmlAttribute AppendAttribute(const std::string& attrName, XmlNode& node)
{
	XmlAttribute attr = node.append_attribute(attrName.c_str());

	if (!attr)
	{
		stringstream msg;
		msg << "Failed to append attribute '" << attrName << "'";
		throw runtime_error(msg.str());
	}

	return attr;
}

XmlNode AppendChildNode(const std::string& nodeName, XmlNode& node, pugi::xml_node_type nodeType)
{
	XmlNode newNode = node.append_child(nodeType);

	if (!newNode)
	{
		stringstream msg;
		msg << "Failed to append child node '" << nodeName << "'";
		throw runtime_error(msg.str());
	}
	else
	{
		newNode.set_name(nodeName.c_str());
		return newNode;
	}
}

XmlAttribute GetAttribute(const std::string& attrName, XmlNode& node, bool throwIfNotFound, bool optional)
{
	XmlAttribute attr = node.attribute(attrName.c_str());

	if (!attr && throwIfNotFound)
	{
		stringstream msg;
		msg << "Expected attribute '" << attrName << "' not found";

		if (optional)
			throw XmlOptionalAttributeNotFound(msg.str());
		else
			throw XmlAttributeNotFound(msg.str());
	}

	return attr;
}

XmlNode GetChildNode(const std::string& nodeName, XmlNode& node, bool throwIfNotFound, bool optional)
{
	XmlNode found = node.child(nodeName.c_str());

	if (!found && throwIfNotFound)
	{
		stringstream msg;
		msg << "Expected child node '" << nodeName << "' not found";

		if (optional)
			throw XmlOptionalChildNodeNotFound(msg.str());
		else
			throw XmlChildNodeNotFound(msg.str());
	}

	return found;
}

void LoadFromDocument(IXmlDOMPersistent& instance, XmlDocument& doc)
{
	doc >> XmlNVP<IXmlDOMPersistent>("root", instance);
}

void LoadFromFile(const string& fileName, XmlDocument& doc)
{
	pugi::xml_parse_result result = doc.load_file(fileName.c_str());

	if (!result)
		throw runtime_error(result.description());
}

void LoadFromFile(const std::string& fileName, IXmlDOMPersistent& instance, bool saveContentIfFileEmpty)
{
	struct stat statBuf;
	int status = stat(fileName.c_str(), &statBuf);
	int errSave = errno;

	if (!status)
	{
		XmlDocument doc;

		if (!statBuf.st_size && saveContentIfFileEmpty)
		{
			SaveToDocument(instance, doc);
			SaveToFile(fileName.c_str(), doc);
		}
		else
		{
			LoadFromFile(fileName.c_str(), doc);
			LoadFromDocument(instance, doc);
		}
	}
	else
	{
		stringstream msg;

		msg << "LoadFromFile could not stat '" << fileName << "'"
		    << " (errno=" << errSave << " '" << GetErrorDescription(errSave) << "')";
		throw invalid_argument(msg.str());
	}
}

void SaveToDocument(const IXmlDOMPersistent& instance, XmlDocument& doc)
{
	doc << XmlNVP<IXmlDOMPersistent>("root", instance);
}

void SaveToFile(const std::string& fileName, const IXmlDOMPersistent& instance)
{
	XmlDocument doc;

	SaveToDocument(instance, doc);
	SaveToFile(fileName, doc);
}

void SaveToFile(const string& fileName, XmlDocument& doc)
{
	if (!doc.save_file(fileName.c_str(), "   "))
		throw runtime_error("SaveToFile failed");
}

XmlAttribute GetLoadAttribute(XmlNode& node, const XmlNVPBase& nvp, bool optional)
{
	string attName = nvp.AttributeName();

	if (!attName.length()) attName = "value";
	return GetAttribute(attName, node, true, optional);
}

XmlAttribute GetSaveAttribute(XmlNode& node, const XmlNVPBase& nvp)
{
	string attName = nvp.AttributeName();

	if (!attName.length()) attName = "value";

	XmlAttribute attr = GetAttribute(attName, node, false);

	if (!attr)
		attr = AppendAttribute(attName, node);

	return attr;
}

std::pair<XmlNode,XmlAttribute> GetSaveNodeAndAttribute(XmlNode& node, const XmlNVPBase& nvp)
{
	std::pair<XmlNode,XmlAttribute> result;
	result.first = GetChildNode(nvp.Name(), node, false);
	if (!result.first)
		result.first = AppendChildNode(nvp.Name(), node);
	string attName = nvp.AttributeName();
	if (!attName.length()) attName = "value";
	result.second = GetAttribute(attName, result.first, false);
	if (!result.second)
		result.second = AppendAttribute(attName, result.first);
	return result;
}

template<typename T>
void LoadNumberFromNode(XmlNode& node, XmlNVP<T>& nvp, bool optional)
{
	const T zero = 0;
	const T maybeNegative = (T) ~zero;
	const bool isSigned = maybeNegative < zero;

	XmlAttribute foundAttr = GetLoadAttribute(node, nvp, optional);
	string data0 = foundAttr.value();
	string data = data0;
	T sign = 1;

	if (boost::starts_with(data, "-"))
	{
		if (isSigned)
		{
			sign = -1;
			data.erase(0, 1);
		}
		else
			throw runtime_error("Unexpected negative sign in unsigned value");
	}

	int radix = 10;

	if (boost::istarts_with(data, "0x"))
	{
		radix = 16;
		data.erase(0, 2);
	}

	// note that I'm not using lexical_cast<> here because it fails for single-byte numbers
	// (the << and >> operators convert single-byte numbers to character literals)

	char* endptr = NULL;
	typedef uint32_t ResultType;
	typedef int32_t SignedResultType;
	ResultType result = strtoul(data.c_str(), &endptr, radix);

	if (endptr && *endptr)
	{
		stringstream stm;
		stm << "Invalid data in conversion from '" << data0 << "' to " << typeid(T).name();
		throw runtime_error(stm.str());
	}

	if (isSigned && (((SignedResultType) result) < 0))
	{
		stringstream stm;
		stm << "Overflow in numeric conversion of '" << data0 << "' to " << typeid(T).name();
		throw runtime_error(stm.str());
	}

	if ((sizeof(T) < sizeof(result)) && (result & (~0 << 8*(sizeof(result)-sizeof(T)))))
	{
		stringstream stm;
		stm << "Overflow in numeric conversion of '" << data0 << "' to " << typeid(T).name() << " (2)";
		throw runtime_error(stm.str());
	}

	result *= sign;
	nvp.Value() = (T) result;
}

template<typename T>
void SaveNumberToNode(XmlNode& node, const XmlNVP<T>& nvp)
{
	const T zero = 0;
	const T maybeNegative = (T) ~zero;
	const bool isSigned = maybeNegative < zero;

	XmlAttribute newAttr = GetSaveAttribute(node, nvp);

	stringstream data;
	T val = nvp.Value();

	if (isSigned && (val < 0))
	{
		data << "-";
		val = -val;
	}

	if (nvp.GetFlags() & XmlNVPBase::HEX)
		data << "0x" << ultos(val, 16, 2*sizeof(T));
	else
		data << ultos(val, 10);

	string resultingValue = data.str();
	newAttr.set_value(resultingValue.c_str());
}
//void LoadNumberFromNode(XmlNode& node, XmlNVP<T>& nvp, bool isSigned)

void LoadFromNode(XmlNode& node, const XmlNVPBase& nvp, char& val, bool optional)
{ XmlAttribute a = GetLoadAttribute(node, nvp, optional); val = *a.value(); }
void LoadFromNode(XmlNode& node, const XmlNVPBase& nvp, uint8_t& val, bool optional)
{ XmlNVP<uint8_t> nvp0(nvp, val); LoadNumberFromNode(node, nvp0, optional); }
void LoadFromNode(XmlNode& node, const XmlNVPBase& nvp, int8_t& val, bool optional)
{ XmlNVP<int8_t> nvp0(nvp, val); LoadNumberFromNode(node, nvp0, optional); }
void LoadFromNode(XmlNode& node, const XmlNVPBase& nvp, uint16_t& val, bool optional)
{ XmlNVP<uint16_t> nvp0(nvp, val); LoadNumberFromNode(node, nvp0, optional); }
void LoadFromNode(XmlNode& node, const XmlNVPBase& nvp, int16_t& val, bool optional)
{ XmlNVP<int16_t> nvp0(nvp, val); LoadNumberFromNode(node, nvp0, optional); }
void LoadFromNode(XmlNode& node, const XmlNVPBase& nvp, uint32_t& val, bool optional)
{ XmlNVP<uint32_t> nvp0(nvp, val); LoadNumberFromNode(node, nvp0, optional); }
void LoadFromNode(XmlNode& node, const XmlNVPBase& nvp, int32_t& val, bool optional)
{ XmlNVP<int32_t> nvp0(nvp, val); LoadNumberFromNode(node, nvp0, optional); }
void LoadFromNode(XmlNode& node, const XmlNVPBase& nvp, float& val, bool optional)
{ val = GetLoadAttribute(node, nvp, optional).as_float(); }
void LoadFromNode(XmlNode& node, const XmlNVPBase& nvp, double& val, bool optional)
{ val = GetLoadAttribute(node, nvp, optional).as_double(); }
void LoadFromNode(XmlNode& node, const XmlNVPBase& nvp, string& val, bool optional)
{ XmlAttribute a = GetLoadAttribute(node, nvp, optional); val = a.value(); }
void LoadFromNode(XmlNode& node, const XmlNVPBase& nvp, XmlBoolean& val, bool optional)
{ val.Value() = GetLoadAttribute(node, nvp, optional).as_bool(); }

void LoadFromNode(XmlNode& node, const XmlNVPBase& nvp, XmlBooleanVector& val, bool optional)
{
	vector<bool>& v = val.Value();

	v.clear();

	for (XmlNode n = node.first_child(); n; n = n.next_sibling())
	{
		string nodeName = n.name();

		if (boost::iequals(nodeName, "Element"))
		{
			bool temp;

			XmlBoolean t2(temp);
			LoadFromNode(n, nvp, t2);

			v.push_back(temp);
		}
		else
			throw runtime_error("Unexpected child node found in enumerable");
	}
}

void LoadFromNode(XmlNode& node, const XmlNVPBase& nvp, ByteArray& val, bool optional)
{
	XmlAttribute a = GetLoadAttribute(node, nvp, optional);
	string asStr = a.value();
	const string::size_type len = asStr.length();

	if (len & 1)
		throw runtime_error("ByteArray length cannot be an odd number");

	val.resize(0);
	val.reserve(len/2);

	for (string::size_type idx = 0; idx < len; idx += 2)
	{
		string temp = asStr.substr(idx, 2);
		char* endptr;

		val.push_back((ByteArray::byte) strtoul(temp.c_str(), &endptr, 16));
	}
}

void LoadFromNode(XmlNode& node, const XmlNVPBase& nvp, IXmlDOMPersistent& val, bool optional)
{
	val.Load(node, nvp);
}

void SaveToNode(XmlNode& node, const XmlNVPBase& nvp, const char& val)					{ string temp(1, val); GetSaveAttribute(node, nvp).set_value(temp.c_str()); }
void SaveToNode(XmlNode& node, const XmlNVPBase& nvp, const uint8_t& val)				{ XmlNVP<uint8_t> nvp0(nvp, val); SaveNumberToNode(node, nvp0); }
void SaveToNode(XmlNode& node, const XmlNVPBase& nvp, const int8_t& val)				{ XmlNVP<int8_t> nvp0(nvp, val); SaveNumberToNode(node, nvp0); }
void SaveToNode(XmlNode& node, const XmlNVPBase& nvp, const uint16_t& val)				{ XmlNVP<uint16_t> nvp0(nvp, val); SaveNumberToNode(node, nvp0); }
void SaveToNode(XmlNode& node, const XmlNVPBase& nvp, const int16_t& val)				{ XmlNVP<int16_t> nvp0(nvp, val); SaveNumberToNode(node, nvp0); }
void SaveToNode(XmlNode& node, const XmlNVPBase& nvp, const uint32_t& val)				{ XmlNVP<uint32_t> nvp0(nvp, val); SaveNumberToNode(node, nvp0); }
void SaveToNode(XmlNode& node, const XmlNVPBase& nvp, const int32_t& val)				{ XmlNVP<int32_t> nvp0(nvp, val); SaveNumberToNode(node, nvp0); }
void SaveToNode(XmlNode& node, const XmlNVPBase& nvp, const float& val)					{ GetSaveAttribute(node, nvp).set_value(val); }
void SaveToNode(XmlNode& node, const XmlNVPBase& nvp, const double& val)				{ GetSaveAttribute(node, nvp).set_value(val); }
void SaveToNode(XmlNode& node, const XmlNVPBase& nvp, const string& val)				{ GetSaveAttribute(node, nvp).set_value(val.c_str()); }
void SaveToNode(XmlNode& node, const XmlNVPBase& nvp, const XmlBoolean& val)			{ GetSaveAttribute(node, nvp).set_value(val.Value() ? "true" : "false"); }

void SaveToNode(XmlNode& node, const XmlNVPBase& nvp, const XmlBooleanVector& val)
{
	const vector<bool>& v = val.Value();

	for (vector<bool>::const_iterator it = v.begin(); it != v.end(); ++it)
	{
		XmlNode n = AppendChildNode("Element", node);
		SaveToNode(n, nvp, XmlBoolean(*it));
	}
}

void SaveToNode(XmlNode& node, const XmlNVPBase& nvp, const ByteArray& val)
{
	stringstream stm;

	for (ByteArray::const_iterator it = val.begin(); it != val.end(); ++it)
		stm << utos(*it, 16, 2);

	string content = stm.str();
	GetSaveAttribute(node, nvp).set_value(content.c_str());
}

void SaveToNode(XmlNode& node, const XmlNVPBase& nvp, const IXmlDOMPersistent& val)
{
	val.Save(node, nvp);
}

}
}
}
