//-------------------------------------------------------------------------------
// <copyright file="LqrSMACXmlDOM.h" 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>
//   Declarations for utility functions and classes that enable data to be stored
//   and retrieved from an XML document.
// </summary>
//
// <owner>Mike Cookson</owner>
//-------------------------------------------------------------------------------

#ifndef __LQRSMACXMLDOM_H__
#define __LQRSMACXMLDOM_H__

#include "LqrSMACDeclareException.h"
#include "LqrSMACSharedLib.h"
#include "pugixml.hpp"
#include <list>
#include <map>
#include <set>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>
#include <stdint.h>
#include <boost/algorithm/string.hpp>
#include <boost/shared_ptr.hpp>

namespace LiquidRobotics {
namespace SMACFramework {

class ByteArray;

namespace XmlDOM {

typedef pugi::xml_document XmlDocument;				// the class representing the full XML document (derived from XmlNode)
typedef pugi::xml_node XmlNode;						// the class representing any node in the hierarchy
typedef pugi::xml_attribute XmlAttribute;			// the class representing attributes inside of a node tag

// Xml element tag used for enumerable collection elements
extern SHARED_LIB_PUBLIC const std::string StandardElementTag;

// Exceptions
DECLARE_EXCEPTION(XmlAttributeNotFound,std::runtime_error);
DECLARE_EXCEPTION(XmlChildNodeNotFound,std::runtime_error);
DECLARE_EXCEPTION(XmlOptionalAttributeNotFound,std::runtime_error);
DECLARE_EXCEPTION(XmlOptionalChildNodeNotFound,std::runtime_error);

//////////////////////////
//// XmlNVPBase Class ////
//////////////////////////

class SHARED_LIB_PUBLIC XmlNVPBase
{
public:

	typedef std::string string;

	enum Flags
	{
		NONE = 0,					// defaults
		HEX = 1						// stores numeric data in hexadecimal and allows read-back in hex if starting with 0x
	};

	XmlNVPBase(const string& _name, const string& _attrName = defaultAttributeName, Flags _flags = NONE)
	: name(_name)
	, attrName(_attrName)
	, flags(_flags)
	{
	}

	const string& AttributeName() const	{ return attrName; }
	const string& Name() const			{ return name; }
	Flags GetFlags() const				{ return flags; }

	static const string defaultAttributeName;

protected:

	const string name;
	const string attrName;
	const Flags flags;

};

///////////////////////////////
//// XmlCNCValueBase Class ////
///////////////////////////////

class SHARED_LIB_PUBLIC XmlCNCValueBase
{
public:

};

////////////////////////////////////
//// XmlCNCValue Template Class ////
////////////////////////////////////

template<typename T>
class SHARED_LIB_PUBLIC XmlCNCValue : public XmlCNCValueBase
{
public:

	XmlCNCValue(T& _val)
	: initConst(false)
	, val(_val)
	{
	}

	XmlCNCValue(const T& _val)
	: initConst(true)
	, val(_val)
	{
	}

	T& Value()								{ if (initConst) throw std::runtime_error("Initialized as constant, but used as non-constant"); return const_cast<T&>(val); }
	const T& Value() const					{ return val;  }

protected:

	bool initConst;
	const T& val;
};

//////////////////////////////////////////////
//// XmlNVPDefaultAlocator Template Class ////
//////////////////////////////////////////////

#if 0
template<typename T>
class SHARED_LIB_PUBLIC XmlNVPDefaultAlocator
{
public:

	boost::shared_ptr<T> operator()()
	{
		boost::shared_ptr<T> result;

		result.reset(new T);
		return result;
	}
};
#endif

///////////////////////////////
//// XmlNVP Template Class ////
///////////////////////////////

template<typename T>
class SHARED_LIB_PUBLIC XmlNVP
: public XmlNVPBase
, public XmlCNCValue<T>
{
public:

	typedef std::string string;

	XmlNVP(const string& _name, T& _val, const string& _attrName = defaultAttributeName, Flags _flags = NONE)
	: XmlNVPBase(_name, _attrName, _flags)
	, XmlCNCValue<T>(_val)
	{
	}

	XmlNVP(const string& _name, const T& _val, const string& _attrName = defaultAttributeName, Flags _flags = NONE)
	: XmlNVPBase(_name, _attrName, _flags)
	, XmlCNCValue<T>(_val)
	{
	}

	XmlNVP(const XmlNVPBase& other, T& _val)
	: XmlNVPBase(other.Name(), other.AttributeName(), other.GetFlags())
	, XmlCNCValue<T>(_val)
	{
	}

	XmlNVP(const XmlNVPBase& other, const T& _val)
	: XmlNVPBase(other.Name(), other.AttributeName(), other.GetFlags())
	, XmlCNCValue<T>(_val)
	{
	}
};

/////////////////////////////////
//// IXmlDOMPersistent Class ////
/////////////////////////////////

class IXmlDOMPersistent
{
public:
	virtual void Load(XmlNode& node, const XmlNVPBase& nvp) = 0;
	virtual void Save(XmlNode& node, const XmlNVPBase& nvp) const = 0;
};

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

class SHARED_LIB_PUBLIC XmlBoolean
: public XmlCNCValue<bool>
{
public:

	XmlBoolean(bool& val);
	XmlBoolean(const bool& val);
};

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

class SHARED_LIB_PUBLIC XmlBooleanVector
: public XmlCNCValue<std::vector<bool> >
{
public:

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

//////////////////////////////////////
//// XmlEnumerable Template Class ////
//////////////////////////////////////

template<typename T>
class SHARED_LIB_PUBLIC XmlEnumerable
: public XmlCNCValue<T>
{
public:

	XmlEnumerable(T& val) : XmlCNCValue<T>(val) { }
	XmlEnumerable(const T& val) : XmlCNCValue<T>(val) { }
};

////////////////////////////////////
//// Methods at namespace scope ////
////////////////////////////////////

SHARED_LIB_PUBLIC XmlAttribute AppendAttribute(const std::string& attrName, XmlNode& node);
SHARED_LIB_PUBLIC XmlNode AppendChildNode(const std::string& nodeName, XmlNode& node, pugi::xml_node_type nodeType = pugi::node_element);
SHARED_LIB_PUBLIC XmlAttribute GetAttribute(const std::string& attrName, XmlNode& node, bool throwIfNotFound = true, bool optional = false);
SHARED_LIB_PUBLIC XmlNode GetChildNode(const std::string& nodeName, XmlNode& node, bool throwIfNotFound = true, bool optional = false);
SHARED_LIB_PUBLIC XmlAttribute GetLoadAttribute(XmlNode& node, const XmlNVPBase& nvp, bool optional = true);
SHARED_LIB_PUBLIC XmlAttribute GetSaveAttribute(XmlNode& node, const XmlNVPBase& nvp);
SHARED_LIB_PUBLIC void LoadFromDocument(IXmlDOMPersistent& instance, XmlDocument& doc);
SHARED_LIB_PUBLIC void LoadFromFile(const std::string& fileName, IXmlDOMPersistent& instance, bool saveContentIfFileEmpty = true);
SHARED_LIB_PUBLIC void LoadFromFile(const std::string& fileName, XmlDocument& doc);
SHARED_LIB_PUBLIC void SaveToDocument(const IXmlDOMPersistent& instance, XmlDocument& doc);
SHARED_LIB_PUBLIC void SaveToFile(const std::string& fileName, const IXmlDOMPersistent& instance);
SHARED_LIB_PUBLIC void SaveToFile(const std::string& fileName, XmlDocument& doc);

SHARED_LIB_PUBLIC void LoadFromNode(XmlNode& node, const XmlNVPBase& nvp, char& val, bool optional = false);
SHARED_LIB_PUBLIC void LoadFromNode(XmlNode& node, const XmlNVPBase& nvp, uint8_t& val, bool optional = false);
SHARED_LIB_PUBLIC void LoadFromNode(XmlNode& node, const XmlNVPBase& nvp, int8_t& val, bool optional = false);
SHARED_LIB_PUBLIC void LoadFromNode(XmlNode& node, const XmlNVPBase& nvp, uint16_t& val, bool optional = false);
SHARED_LIB_PUBLIC void LoadFromNode(XmlNode& node, const XmlNVPBase& nvp, int16_t& val, bool optional = false);
SHARED_LIB_PUBLIC void LoadFromNode(XmlNode& node, const XmlNVPBase& nvp, uint32_t& val, bool optional = false);
SHARED_LIB_PUBLIC void LoadFromNode(XmlNode& node, const XmlNVPBase& nvp, int32_t& val, bool optional = false);
SHARED_LIB_PUBLIC void LoadFromNode(XmlNode& node, const XmlNVPBase& nvp, float& val, bool optional = false);
SHARED_LIB_PUBLIC void LoadFromNode(XmlNode& node, const XmlNVPBase& nvp, double& val, bool optional = false);
SHARED_LIB_PUBLIC void LoadFromNode(XmlNode& node, const XmlNVPBase& nvp, std::string& val, bool optional = false);
SHARED_LIB_PUBLIC void LoadFromNode(XmlNode& node, const XmlNVPBase& nvp, XmlBoolean& val, bool optional = false);
SHARED_LIB_PUBLIC void LoadFromNode(XmlNode& node, const XmlNVPBase& nvp, XmlBooleanVector& val, bool optional = false);
SHARED_LIB_PUBLIC void LoadFromNode(XmlNode& node, const XmlNVPBase& nvp, ByteArray& val, bool optional = false);
SHARED_LIB_PUBLIC void LoadFromNode(XmlNode& node, const XmlNVPBase& nvp, IXmlDOMPersistent& val, bool optional = false);

template<typename Tx, typename Ty>
void LoadFromNode(XmlNode& node, const XmlNVPBase& nvp, std::pair<Tx,Ty>& val, bool optional = false);
template<typename T>
void LoadFromNode(XmlNode& node, const XmlNVPBase& nvp, boost::shared_ptr<T>& val, bool optional = false);
template<typename T>
void LoadEnumerableFromNode(XmlNode& node, const XmlNVPBase& nvp, T& val, bool optional = false);
template<typename Tkey,typename Tval>
void LoadFromNode(XmlNode& node, const XmlNVPBase& nvp, std::map<Tkey,Tval>& val, bool optional = false);
template<typename T>
void LoadFromNode(XmlNode& node, const XmlNVPBase& nvp, std::list<T>& val, bool optional = false);
template<typename T>
void LoadFromNode(XmlNode& node, const XmlNVPBase& nvp, std::set<T>& val, bool optional = false);
template<typename T>
void LoadFromNode(XmlNode& node, const XmlNVPBase& nvp, std::vector<T>& val, bool optional = false);
template<typename T>
void LoadFromNode(XmlNode& node, const XmlNVPBase& nvp, XmlEnumerable<T>& collection, bool optional = false);

template<typename Tx, typename Ty>
void LoadFromNode(XmlNode& node, const XmlNVPBase& nvp, std::pair<Tx,Ty>& val, bool optional)
{
	XmlNode first = GetChildNode("first", node);
	XmlNode second = GetChildNode("second", node);

	LoadFromNode(first, nvp, val.first);
	LoadFromNode(second, nvp, val.second);
}

template<typename T>
void LoadFromNode(XmlNode& node, const XmlNVPBase& nvp, boost::shared_ptr<T>& val, bool optional)
{
	XmlAttribute nilAttr = node.attribute("nil");

	if (nilAttr && nilAttr.as_bool())
		val.reset();
	else
	{
		if (!val)
			val.reset(new T);

		try
		{
			LoadFromNode(node, nvp, *val, true);
		}
		catch(XmlOptionalAttributeNotFound& ex)
		{
			val.reset();
		}
		catch(XmlOptionalChildNodeNotFound& ex)
		{
			val.reset();
		}
	}
}

template<typename T>
void LoadEnumerableFromNode(XmlNode& node, const XmlNVPBase& nvp, T& val, bool optional)
{
	using namespace std;
	val.clear();

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

		if (boost::iequals(nodeName, StandardElementTag))
		{
			typename T::value_type temp;

			LoadFromNode(n, nvp, temp);
			val.push_back(temp);
		}
		else
			throw runtime_error("Unexpected child node found in enumerable");
	}
}

template<typename Tkey,typename Tval>
void LoadFromNode(XmlNode& node, const XmlNVPBase& nvp, std::map<Tkey,Tval>& val, bool optional)
{
	using namespace std;
	val.clear();

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

		if (boost::iequals(nodeName, StandardElementTag))
		{
			pair<Tkey,Tval> temp;

			LoadFromNode(n, nvp, temp);
			val[temp.first] = temp.second;
		}
		else
			throw runtime_error("Unexpected child node found in enumerable");
	}
}

template<typename T>
void LoadFromNode(XmlNode& node, const XmlNVPBase& nvp, std::list<T>& val, bool optional)
{
	LoadEnumerableFromNode<std::list<T> >(node, nvp, val);
}

template<typename T>
void LoadFromNode(XmlNode& node, const XmlNVPBase& nvp, std::set<T>& val, bool optional)
{
	LoadEnumerableFromNode<std::set<T> >(node, nvp, val);
}

template<typename T>
void LoadFromNode(XmlNode& node, const XmlNVPBase& nvp, std::vector<T>& val, bool optional)
{
	LoadEnumerableFromNode<std::vector<T> >(node, nvp, val);
}

template<typename T>
void LoadFromNode(XmlNode& node, const XmlNVPBase& nvp, XmlEnumerable<T>& collection, bool optional)
{
	using namespace std;
	T& val = collection.Value();

	val.clear();

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

		if (boost::iequals(nodeName, StandardElementTag))
		{
			typename T::value_type temp;

			LoadFromNode(n, nvp, temp);
			val.push_back(temp);
		}
		else
			throw runtime_error("Unexpected child node found in enumerable");
	}
}

SHARED_LIB_PUBLIC void SaveToNode(XmlNode& node, const XmlNVPBase& nvp, const char& val);
SHARED_LIB_PUBLIC void SaveToNode(XmlNode& node, const XmlNVPBase& nvp, const uint8_t& val);
SHARED_LIB_PUBLIC void SaveToNode(XmlNode& node, const XmlNVPBase& nvp, const int8_t& val);
SHARED_LIB_PUBLIC void SaveToNode(XmlNode& node, const XmlNVPBase& nvp, const uint16_t& val);
SHARED_LIB_PUBLIC void SaveToNode(XmlNode& node, const XmlNVPBase& nvp, const int16_t& val);
SHARED_LIB_PUBLIC void SaveToNode(XmlNode& node, const XmlNVPBase& nvp, const uint32_t& val);
SHARED_LIB_PUBLIC void SaveToNode(XmlNode& node, const XmlNVPBase& nvp, const int32_t& val);
SHARED_LIB_PUBLIC void SaveToNode(XmlNode& node, const XmlNVPBase& nvp, const float& val);
SHARED_LIB_PUBLIC void SaveToNode(XmlNode& node, const XmlNVPBase& nvp, const double& val);
SHARED_LIB_PUBLIC void SaveToNode(XmlNode& node, const XmlNVPBase& nvp, const std::string& val);
SHARED_LIB_PUBLIC void SaveToNode(XmlNode& node, const XmlNVPBase& nvp, const XmlBoolean& val);
SHARED_LIB_PUBLIC void SaveToNode(XmlNode& node, const XmlNVPBase& nvp, const XmlBooleanVector& val);
SHARED_LIB_PUBLIC void SaveToNode(XmlNode& node, const XmlNVPBase& nvp, const ByteArray& val);
SHARED_LIB_PUBLIC void SaveToNode(XmlNode& node, const XmlNVPBase& nvp, const IXmlDOMPersistent& val);
template<typename Tx, typename Ty>
void SaveToNode(XmlNode& node, const XmlNVPBase& nvp, const std::pair<Tx,Ty>& val);
template<typename T>
void SaveToNode(XmlNode& node, const XmlNVPBase& nvp, const boost::shared_ptr<T>& val);
template<typename T>
void SaveEnumerableToNode(XmlNode& node, const XmlNVPBase& nvp, const T& val);
template<typename Tkey,typename Tval>
void SaveToNode(XmlNode& node, const XmlNVPBase& nvp, const std::map<Tkey,Tval>& val);
template<typename T>
void SaveToNode(XmlNode& node, const XmlNVPBase& nvp, const std::list<T>& val);
template<typename T>
void SaveToNode(XmlNode& node, const XmlNVPBase& nvp, const std::set<T>& val);
template<typename T>
void SaveToNode(XmlNode& node, const XmlNVPBase& nvp, const std::vector<T>& val);
template<typename T>
void SaveToNode(XmlNode& node, const XmlNVPBase& nvp, const XmlEnumerable<T>& val);

template<typename Tx, typename Ty>
void SaveToNode(XmlNode& node, const XmlNVPBase& nvp, const std::pair<Tx,Ty>& val)
{
	XmlNode first = AppendChildNode("first", node);
	SaveToNode(first, nvp, val.first);

	XmlNode second = AppendChildNode("second", node);
	SaveToNode(second, nvp, val.second);
}


template<typename T>
void SaveToNode(XmlNode& node, const XmlNVPBase& nvp, const boost::shared_ptr<T>& val)
{
	if (val)
		SaveToNode(node, nvp, *val);
	else
	{
		XmlAttribute attr = AppendAttribute("nil", node);

		attr.set_value(true);
	}
}

template<typename T>
void SaveEnumerableToNode(XmlNode& node, const XmlNVPBase& nvp, const T& val)
{
	for (typename T::const_iterator it = val.begin(); it != val.end(); ++it)
	{
		XmlNode n = AppendChildNode(StandardElementTag.c_str(), node);
		SaveToNode(n, nvp, *it);
	}
}

template<typename Tkey,typename Tval>
void SaveToNode(XmlNode& node, const XmlNVPBase& nvp, const std::map<Tkey,Tval>& val)
{
	SaveEnumerableToNode<std::map<Tkey,Tval> >(node, nvp, val);
}

template<typename T>
void SaveToNode(XmlNode& node, const XmlNVPBase& nvp, const std::list<T>& val)
{
	SaveEnumerableToNode<std::list<T> >(node, nvp, val);
}

template<typename T>
void SaveToNode(XmlNode& node, const XmlNVPBase& nvp, const std::set<T>& val)
{
	SaveEnumerableToNode<std::set<T> >(node, nvp, val);
}

template<typename T>
void SaveToNode(XmlNode& node, const XmlNVPBase& nvp, const std::vector<T>& val)
{
	SaveEnumerableToNode<std::vector<T> >(node, nvp, val);
}

template<typename T>
void SaveToNode(XmlNode& node, const XmlNVPBase& nvp, const XmlEnumerable<T>& val)
{
	for (typename T::const_iterator it = val.begin(); it != val.end(); ++it)
	{
		XmlNode n = AppendChildNode("Element", node);
		SaveToNode(n, nvp, *it);
	}
}

#if 0
template<typename T>
XmlNode& operator<<(XmlNode& node, const XmlNVP<T>& nvp)
{
	XmlNode childNode = node.child(nvp.Name().c_str());

	if (!childNode)
		childNode = AppendChildNode(nvp.Name(), node);

	SaveToNode(childNode, nvp, nvp.Value());
	return node;
}
#endif

template<typename T>
XmlNode& operator<<(XmlNode& node, const XmlNVP<T>& nvp)
{
	XmlNode childNode = node.child(nvp.Name().c_str());

	if (!childNode)
		childNode = AppendChildNode(nvp.Name(), node);

	SaveToNode(childNode, nvp, nvp.Value());
	return node;
}

#if 0
template<typename T>
XmlNode& operator>>(XmlNode& node, XmlNVP<T>& nvp)
{
	XmlNode childNode = GetChildNode(nvp.Name(), node);

	LoadFromNode(childNode, nvp, nvp.Value());
	return node;
}
#endif

namespace Operators
{
	template<typename T>
	void Insertion(XmlNode& node, XmlNVP< boost::shared_ptr<T> >& nvp)
	{
		XmlNode childNode = GetChildNode(nvp.Name(), node, false);

		if (childNode)
			LoadFromNode(childNode, (const XmlNVPBase&) nvp, nvp.Value());
		else
			nvp.Value().reset();
	}

	template<typename T>
	void Insertion(XmlNode& node, XmlNVP<T>& nvp)
	{
		XmlNode childNode = GetChildNode(nvp.Name(), node);

		LoadFromNode(childNode, (const XmlNVPBase&) nvp, nvp.Value());
	}
}

template<typename T>
XmlNode& operator>>(XmlNode& node, XmlNVP<T> nvp)
{
	Operators::Insertion(node, nvp);
	return node;
}

}
}
}

#endif
