//-----------------------------------------------------------------------------------
// <copyright file="TelemetryFieldList.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 TelemetryFieldList, a list of event field format configuration
//   names (in a library) and event field names that can be used to format an event
//   into a byte stream.
// </summary>
//
// <owner>Mike Cookson</owner>
//-----------------------------------------------------------------------------------

#include "TelemetryFieldList.h"
#include "TelemetryFieldLibrary.h"
#include "EPVFormatConfig.h"
#include "EventPropertyValue.h"
#include "IEventBase.h"
#include <list>
#include <sstream>
#include <boost/archive/xml_iarchive.hpp>
#include <boost/archive/xml_oarchive.hpp>
#include <boost/foreach.hpp>
#include <boost/serialization/list.hpp>
#include <boost/serialization/nvp.hpp>
#include <boost/serialization/utility.hpp>

using namespace std;
using namespace LiquidRobotics::EventLib;
using namespace LiquidRobotics::SMACFramework::XmlDOM;
using namespace boost::archive;
using namespace boost::serialization;

template<typename T>
typename list<T>::iterator GetPositionAtIndex(list<T>& __list, size_t idx)
{

	if (idx >= __list.size())
		throw invalid_argument("Index out of list range");

	typename list<T>::iterator it;

	for (it = __list.begin(); idx--; ++it)
		;

	return it;
}

template<typename T>
typename list<T>::const_iterator GetPositionAtIndex(const list<T>& __list, size_t idx)
{

	if (idx >= __list.size())
		throw invalid_argument("Index out of list range");
	else
	{
		typename list<T>::const_iterator it;

		for (it = __list.begin(); idx--; ++it)
			;

		return it;
	}
}

namespace LiquidRobotics {
namespace PayloadInterface {

TelemetryFieldList::TelemetryFieldList()
{
}

TelemetryFieldList::TelemetryFieldList(const TelemetryFieldList& other)
{
	BOOST_FOREACH(const FieldList::value_type& item, other.fields)
	{
		ListEntry newItem;

		newItem.DeepCopy(item);
		fields.push_back(newItem);
	}
}

const TelemetryFieldList& TelemetryFieldList::operator=(const TelemetryFieldList& other)
{
	if (this != &other)
	{
		fields.clear();

		BOOST_FOREACH(const FieldList::value_type& item, other.fields)
		{
			ListEntry newItem;

			newItem.DeepCopy(item);
			fields.push_back(newItem);
		}
	}

	return *this;
}

int TelemetryFieldList::Compare(const TelemetryFieldList& v1, const TelemetryFieldList& v2)
{
	int result;
	FieldList::const_iterator i1 = v1.fields.begin();
	FieldList::const_iterator i2 = v2.fields.begin();
	bool end1, end2;

	while(end1 = (i1 == v1.fields.end()),
		  end2 = (i2 == v2.fields.end()),
		  result = (end1 && !end2) ? 1 : ((!end1 && end2) ? -1 : (end1 ? ListEntry::Compare(*i1, *i2) : 0)),
		  !result && !end1)
	{
		++i1;
		++i2;
	}

	return result;
}

bool TelemetryFieldList::operator==(const TelemetryFieldList& other) const
{
	return Compare(*this, other) == 0;
}

bool TelemetryFieldList::operator<(const TelemetryFieldList& other) const
{
	return Compare(*this, other) < 0;
}

bool TelemetryFieldList::operator>(const TelemetryFieldList& other) const
{
	return Compare(*this, other) > 0;
}

void TelemetryFieldList::Add(const string& libFieldName, size_t nextIndex)
{
	ListEntry le(libFieldName, StringPtr());

	if (nextIndex == (size_t) -1)
		fields.push_back(le);
	else if (nextIndex < fields.size())
		fields.insert(GetPositionAtIndex<ListEntry>(fields, nextIndex), le);
	else
		throw invalid_argument("TelemetryFieldList::Add index out of range");
}

void TelemetryFieldList::Add(const string& libFieldName, const string& eventFieldName, size_t nextIndex)
{
	StringPtr sptr;
	sptr.reset(new string(eventFieldName));

	ListEntry le(libFieldName, sptr);

	if (nextIndex == (size_t) -1)
		fields.push_back(le);
	else if (nextIndex < fields.size())
		fields.insert(GetPositionAtIndex<ListEntry>(fields, nextIndex), le);
	else
		throw invalid_argument("TelemetryFieldList::Add index out of range");
}

void TelemetryFieldList::Clear()
{
	fields.clear();
}

size_t TelemetryFieldList::Count() const
{
	return fields.size();
}

TelemetryFieldList::ListEntry::ListEntry()
{
}

TelemetryFieldList::ListEntry::ListEntry(const string& lib, StringPtr evt)
: libField(lib)
, eventField(evt)
{
}

int TelemetryFieldList::ListEntry::Compare(const ListEntry& v1, const ListEntry& v2)
{
	int result;

	if (!(result = v1.libField.compare(v2.libField)))
	{
		if (v1.eventField && !v2.eventField)
			result = 1;
		else if (!v1.eventField && v2.eventField)
			result = -1;
		else if (v1.eventField)
			result = v1.eventField->compare(*v2.eventField);
	}

	return result;
}

bool TelemetryFieldList::ListEntry::operator==(const ListEntry& other) const
{
	return !Compare(*this, other);
}

bool TelemetryFieldList::ListEntry::operator<(const ListEntry& other) const
{
	return Compare(*this, other) < 0;
}

bool TelemetryFieldList::ListEntry::operator>(const ListEntry& other) const
{
	return Compare(*this, other) > 0;
}

void TelemetryFieldList::ListEntry::DeepCopy(const ListEntry& other)
{
	libField = other.libField;

	if (other.eventField)
		eventField.reset(new string(*other.eventField));
	else
		eventField.reset();
}

void TelemetryFieldList::ListEntry::Load(XmlNode& node, const XmlNVPBase& nvp)
{
	LoadFromNode(node, XmlNVPBase(nvp.Name(), "libField"), libField);
	LoadFromNode(node, XmlNVPBase(nvp.Name(), "eventField"), eventField);
}

void TelemetryFieldList::ListEntry::Save(XmlNode& node, const XmlNVPBase& nvp) const
{
	SaveToNode(node, XmlNVPBase(nvp.Name(), "libField"), libField);
	SaveToNode(node, XmlNVPBase(nvp.Name(), "eventField"), eventField);
}

template<class Archive>
void TelemetryFieldList::ListEntry::load(Archive& ar, const unsigned int version)
{
	ar >> BOOST_SERIALIZATION_NVP(libField);

	bool literal;
	ar >> BOOST_SERIALIZATION_NVP(literal);

	if (literal)
		eventField.reset();
	else
	{
		eventField.reset(new string());
		ar >> make_nvp("eventField", *eventField);
	}
}

template<class Archive>
void TelemetryFieldList::ListEntry::save(Archive& ar, const unsigned int version) const
{
	ar << BOOST_SERIALIZATION_NVP(libField);

	bool literal = !eventField;
	ar << BOOST_SERIALIZATION_NVP(literal);

	if (!literal)
		ar << make_nvp("eventField", *eventField);
}

void TelemetryFieldList::Load(xml_iarchive& arch)
{
	arch >> make_nvp("fields", fields);
}

void TelemetryFieldList::Remove(size_t index)
{
	if (index >= fields.size())
		throw invalid_argument("TelemetryFieldList::Remove index is out of range");

	fields.erase(GetPositionAtIndex<ListEntry>(fields, index));
}

void TelemetryFieldList::Save(xml_oarchive& arch) const
{
	arch << make_nvp("fields", fields);
}

bool TelemetryFieldList::ToBinary(const TelemetryFieldLibrary& lib, const IEventBase& evt, ByteArray& result, bool append) const
{
	if (!append)
		result.clear();

	try
	{
		BOOST_FOREACH(const ListEntry& le, fields)
		{
			const EPVFormatConfig& fmt = lib.Lookup(le.libField);

			if (le.eventField)
			{
				const IEventBase::PropertyValueType& prop = evt.GetProperty(*le.eventField);
				prop.ToBinary(result, fmt, true);
			}
			else if (fmt.GetContentAlgorithm() != EPVFormatConfig::CA_LITERAL)
			{
				stringstream msg;
				msg << "TelemetryFieldList::ToBinary: library field " << le.libField << " does not name a literal";
				throw msg.str();
			}
			else
				fmt.GetLiteral().ToBinary(result, fmt, true);
		}

		return true;
	}
	catch(...)
	{
		return false;
	}
}

std::ostream& TelemetryFieldList::Describe(std::ostream& stm) const
{
	stm << "fields={";

	BOOST_FOREACH(const ListEntry& item, fields)
	{
		stm << " { '" << item.libField << "'";

		if (item.eventField)
			stm << "/'" << *item.eventField << "'";

		stm << " }";
	}

	return stm;
}

string TelemetryFieldList::GetEventFieldName(size_t idx) const
{
	if (idx >= fields.size())
		throw invalid_argument("(TelemetryFieldList::GetEventFieldName) Index out of range");

	FieldList::const_iterator it;

	for (it = fields.begin(); idx--; ++it)
		;

	return it->eventField ? *it->eventField : "";
}

string TelemetryFieldList::GetLibFieldName(size_t idx) const
{
	if (idx >= fields.size())
		throw invalid_argument("(TelemetryFieldList::GetLibFieldName) Index out of range");

	FieldList::const_iterator it;

	for (it = fields.begin(); idx--; ++it)
		;

	return it->libField;
}

void TelemetryFieldList::Load(XmlNode& node, const XmlNVPBase& nvp)
{
	node >> XmlNVP<FieldList>("fields", fields);
}

void TelemetryFieldList::Save(XmlNode& node, const XmlNVPBase& nvp) const
{
	node << XmlNVP<FieldList>("fields", fields);
}

ostream& operator<<(ostream& stm, const TelemetryFieldList& fieldList)
{
	return fieldList.Describe(stm);
}

}
}
