//------------------------------------------------------------------------------
// <copyright file="EventTransceiver_Settings.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 EventTransceiver's Settings class
// </summary>
//
// <owner>Mike Cookson</owner>
//------------------------------------------------------------------------------

#include "EventTransceiver_Settings.h"
#include "EventTransceiver.h"
#include "all.hh"
#include <stdexcept>
#include <iostream>
#include <fstream>
#include <sstream>
#include <boost/archive/xml_iarchive.hpp>
#include <boost/archive/xml_oarchive.hpp>
#include <boost/foreach.hpp>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

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

namespace LiquidRobotics {
namespace EventLib {

EventTransceiverSettings::EventTransceiverSettings()
: eventServiceName("name")
, eventServiceKind("kind")
, receiveEnable(true)
, displayReceivedEvents(true)
, fullyDescribeReceivedEvents(true)
{
	aliasToEvent["alias"] = "event";
	eventToAlias["event"] = "alias";
	eventFilterEnabled = true;
	eventNameFilter.insert("event-name");
	EventType eventTypeSample("sample-type", "sample-domain");
	eventTypeFilter.insert(eventTypeSample);
}

EventTransceiverSettings::EventTransceiverSettings(const EventTransceiverSettings& other)
: eventServiceName(other.eventServiceName)
, eventServiceKind(other.eventServiceKind)
, receiveEnable(other.receiveEnable)
, aliasToEvent(other.aliasToEvent.begin(), other.aliasToEvent.end())
, eventToAlias(other.eventToAlias.begin(), other.eventToAlias.end())
, eventFilterEnabled(other.eventFilterEnabled)
, eventNameFilter(other.eventNameFilter.begin(), other.eventNameFilter.end())
, eventTypeFilter(other.eventTypeFilter.begin(), other.eventTypeFilter.end())
, displayReceivedEvents(other.displayReceivedEvents)
, fullyDescribeReceivedEvents(other.fullyDescribeReceivedEvents)
, doNotDisplayList(other.doNotDisplayList.begin(), other.doNotDisplayList.end())
{
}

EventTransceiverSettings& EventTransceiverSettings::operator=(const EventTransceiverSettings& other)
{
	if (this != &other)
	{
		eventServiceName = other.eventServiceName;
		eventServiceKind = other.eventServiceKind;
		receiveEnable = other.receiveEnable;
		aliasToEvent = other.aliasToEvent;
		eventToAlias = other.eventToAlias;
		eventFilterEnabled = other.eventFilterEnabled;
		eventNameFilter = other.eventNameFilter;
		eventTypeFilter = other.eventTypeFilter;
		displayReceivedEvents = other.displayReceivedEvents;
		fullyDescribeReceivedEvents = other.fullyDescribeReceivedEvents;
		doNotDisplayList = other.doNotDisplayList;
	}

	return *this;
}

bool EventTransceiverSettings::Equal(const StringMap& m1, const StringMap& m2)
{
	StringMap::const_iterator i1 = m1.begin();
	StringMap::const_iterator i2 = m2.begin();
	bool equal, e1, e2;

	while(e1 = (i1 == m1.end()),
		  e2 = (i2 == m2.end()),
		  equal = (e1 == e2),
		  equal && !e1)
	{
		equal = !i1->first.compare(i2->first) && !i1->second.compare(i2->second);
		++i1;
		++i2;
	}

	return equal;
}

bool EventTransceiverSettings::Equal(const StringSet& s1, const StringSet& s2)
{
	StringSet::const_iterator i1 = s1.begin();
	StringSet::const_iterator i2 = s2.begin();
	bool equal, e1, e2;

	while(e1 = (i1 == s1.end()),
		  e2 = (i2 == s2.end()),
		  equal = (e1 == e2),
		  equal && !e1)
	{
		equal = !i1->compare(*i2);
		++i1;
		++i2;
	}

	return equal;
}

bool EventTransceiverSettings::Equal(const EventTypeSet& s1, const EventTypeSet& s2)
{
	EventTypeSet::const_iterator i1 = s1.begin();
	EventTypeSet::const_iterator i2 = s2.begin();
	bool equal, e1, e2;

	while(e1 = (i1 == s1.end()),
		  e2 = (i2 == s2.end()),
		  equal = (e1 == e2),
		  equal && !e1)
	{
		equal = (*i1 == *i2);
		++i1;
		++i2;
	}

	return equal;
}

bool EventTransceiverSettings::operator==(const EventTransceiverSettings& other) const
{
	return !eventServiceName.compare(other.eventServiceName)
		&& !eventServiceKind.compare(other.eventServiceKind)
		&& (receiveEnable == other.receiveEnable)
		&& Equal(aliasToEvent, other.aliasToEvent)
		&& Equal(eventToAlias, other.eventToAlias)
		&& (eventFilterEnabled == other.eventFilterEnabled)
		&& Equal(eventNameFilter, other.eventNameFilter)
		&& Equal(eventTypeFilter, other.eventTypeFilter)
		&& (displayReceivedEvents == other.displayReceivedEvents)
		&& (fullyDescribeReceivedEvents == other.fullyDescribeReceivedEvents)
		&& Equal(doNotDisplayList, other.doNotDisplayList);
}

bool EventTransceiverSettings::HaveEventForAlias(const string& aliasName) const
{
	StringMap::const_iterator it = aliasToEvent.find(aliasName);
	return it != aliasToEvent.end();
}

string EventTransceiverSettings::EventForAlias(const string& aliasName) const
{
	StringMap::const_iterator it = aliasToEvent.find(aliasName);

	if (it == aliasToEvent.end())
	{
		stringstream stm;
		stm << "EventTransceiverSettings::EventForAlias: no such alias '" << aliasName << "'";

		throw invalid_argument(stm.str());
	}

	return it->second;
}

bool EventTransceiverSettings::HaveAliasForEvent(const string& eventName) const
{
	StringMap::const_iterator it = eventToAlias.find(eventName);
	return it != eventToAlias.end();
}

string EventTransceiverSettings::AliasForEvent(const string& eventName) const
{
	StringMap::const_iterator it = eventToAlias.find(eventName);

	if (it == eventToAlias.end())
	{
		stringstream stm;
		stm << "EventTransceiverSettings::AliasForEvent: no such event '" << eventName << "'";

		throw invalid_argument(stm.str());
	}

	return it->second;
}

void EventTransceiverSettings::SynchronizeEventToAlias()
{
	eventToAlias.empty();

	BOOST_FOREACH(StringMap::value_type item, aliasToEvent)
		eventToAlias[item.first] = item.second;
}

void EventTransceiverSettings::Load(const string& fileName)
{
	struct stat statBuf;
	int status = stat(fileName.c_str(), &statBuf);
	int errSave = errno;

	if (!status)
	{
		if (!statBuf.st_size)
		{
			EventTransceiverSettings temp;
			temp.Save(fileName);
		}

		ifstream ifs(fileName.c_str());

		if (!ifs.good())
		{
			stringstream msg;
			msg << "EventTransceiverSettings::Load: could not open '" << fileName << "' for read";
			throw invalid_argument(msg.str());
		}

		xml_iarchive ia(ifs);
		ia >> make_nvp("rootlevel", *this);
	}
	else
	{
		char tempBuffer[256];
		strerror_r(errSave, tempBuffer, sizeof(tempBuffer));

		stringstream msg;

		msg << "EventTransceiverSettings::Load: could not stat '" << fileName << "'"
		    << " (errno=" << errSave << " '" << tempBuffer << "')";
		throw invalid_argument(msg.str());
	}
}

void EventTransceiverSettings::Save(const string& fileName) const
{
	ofstream ofs(fileName.c_str());

	if (!ofs.good())
	{
		stringstream msg;
		msg << "EventTransceiverSettings::Save: could not open '" << fileName << "' for write";
		throw invalid_argument(msg.str());
	}

	xml_oarchive oa(ofs);
	oa << make_nvp("rootlevel", *this);
}

bool EventTransceiverSettings::EventFilterEnabled() const
{
	return eventFilterEnabled;
}

bool EventTransceiverSettings::EventNameEnabled(const string& eventName) const
{
	return !eventFilterEnabled
	    || (eventNameFilter.find(eventName) != eventNameFilter.end());
}

bool EventTransceiverSettings::EventTypeEnabled(const EventType& eventType) const
{
	return !eventFilterEnabled
	    || (eventTypeFilter.find(eventType) != eventTypeFilter.end());
}

void EventTransceiverSettings::RemoveAliasForEvent(const string& aliasName)
{
	StringMap::iterator fndAlias = aliasToEvent.find(aliasName);

	if (fndAlias != aliasToEvent.end())
	{
		const string eventName = fndAlias->second;
		StringMap::iterator fndEvent = eventToAlias.find(eventName);

		if (fndEvent != eventToAlias.end())
			eventToAlias.erase(fndEvent);

		aliasToEvent.erase(fndAlias);
	}
}

void EventTransceiverSettings::RemoveAllAliasesForEvents()
{
	aliasToEvent.clear();
	eventToAlias.clear();
}

void EventTransceiverSettings::SetAliasForEvent(const string& aliasName, const string& eventName)
{
	RemoveAliasForEvent(aliasName);
	RemoveEventForAlias(eventName);

	aliasToEvent[aliasName] = eventName;
	eventToAlias[eventName] = aliasName;
}

void EventTransceiverSettings::RemoveEventForAlias(const string& eventName)
{
	StringMap::iterator fndEvent = eventToAlias.find(eventName);

	if (fndEvent != eventToAlias.end())
	{
		const string aliasName = fndEvent->second;
		StringMap::iterator fndAlias = aliasToEvent.find(aliasName);

		if (fndAlias != aliasToEvent.end())
			aliasToEvent.erase(fndAlias);

		eventToAlias.erase(fndEvent);
	}
}

void EventTransceiverSettings::ClearEventFilter()
{
	eventNameFilter.clear();
	eventTypeFilter.clear();
}

void EventTransceiverSettings::SetEventFilterEnabled(bool enabled)
{
	eventFilterEnabled = enabled;
}

void EventTransceiverSettings::EnableEventName(const string& eventName, bool enabled)
{
	if (enabled)
		eventNameFilter.insert(eventName);
	else
	{
		StringSet::iterator it = eventNameFilter.find(eventName);

		if (it != eventNameFilter.end())
			eventNameFilter.erase(it);
	}
}

void EventTransceiverSettings::EnableEventType(const EventType& eventType, bool enabled)
{
	if (enabled)
		eventTypeFilter.insert(eventType);

	else
	{
		EventTypeSet::iterator it = eventTypeFilter.find(eventType);

		if (it != eventTypeFilter.end())
			eventTypeFilter.erase(it);
	}
}

void EventTransceiverSettings::DescribeAliasList(ostream& stm) const
{
	bool first = true;

	BOOST_FOREACH(StringMap::value_type item, aliasToEvent)
	{
		if (first)
			first = false;
		else
			stm << " ";

		stm << "A:'" << item.first << "'=E:'" << item.second << "'";
	}

}

void EventTransceiverSettings::DescribeEventFilter(ostream& stm) const
{
	stm << "EventNames {";

	BOOST_FOREACH(string s, eventNameFilter)
		stm << " '" << s << "'";

	stm << " } EventTypes {";

	BOOST_FOREACH(EventType t, eventTypeFilter)
		stm << " " << t;

}

bool EventTransceiverSettings::DisplayReceivedEvents() const
{
	return displayReceivedEvents;
}

void EventTransceiverSettings::DisplayReceivedEvents(bool newValue)
{
	displayReceivedEvents = newValue;
}

bool EventTransceiverSettings::FullyDescribeReceivedEvents() const
{
	return fullyDescribeReceivedEvents;
}

void EventTransceiverSettings::FullyDescribeReceivedEvents(bool newValue)
{
	fullyDescribeReceivedEvents = newValue;
}

const EventTransceiverSettings::StringSet& EventTransceiverSettings::DoNotDisplayList() const
{
	return doNotDisplayList;
}

void EventTransceiverSettings::DoNotDisplayList(const StringSet& newValue)
{
	doNotDisplayList = newValue;
}

ostream& operator<<(ostream& stm, const EventTransceiverSettings& settings)
{
	stm << "eventServiceName = '" << settings.GetEventServiceName() << "'"
	    << " eventServiceKind = '" << settings.GetEventServiceKind() << "'"
	    << " receiveEnable = " << settings.GetReceiveEnable()
	    << " aliasList = { ";

	settings.DescribeAliasList(stm);

	bool evfEn;

	stm << " }"
	    << " eventFilterEnabled = " << (evfEn = settings.EventFilterEnabled());

	if (evfEn)
	{
		stm << " eventFilter = { ";
		settings.DescribeEventFilter(stm);
		stm << " }";
	}

	stm << " displayReceivedEvents=" << (settings.DisplayReceivedEvents() ? "true" : "false")
	    << " fullyDescribeReceivedEvents=" << (settings.FullyDescribeReceivedEvents() ? "true" : "false")
	    << " doNotDisplayList={";

	const EventTransceiverSettings::StringSet& dnd = settings.DoNotDisplayList();

	for (EventTransceiverSettings::StringSet::const_iterator ci = dnd.begin(); ci != dnd.end(); ++ci)
		stm << " '" << *ci << "'";

	stm << " }";

	return stm;
}

const EventTransceiverSettings::StringSet& EventTransceiverSettings::GetEventNameFilter() const
{
	return eventNameFilter;
}

void EventTransceiverSettings::SetEventNameFilter(const StringSet& newValue)
{
	eventNameFilter = newValue;
}

const EventTransceiverSettings::EventTypeSet& EventTransceiverSettings::GetEventTypeFilter() const
{
	return eventTypeFilter;
}

void EventTransceiverSettings::SetEventTypeFilter(const EventTypeSet& newValue)
{
	eventTypeFilter = newValue;
}

void EventTransceiverSettings::GetCustomFilter(EventManagerCustomFilter& cf) const
{
	cf.enabled = eventFilterEnabled;
	cf.nameList.length(eventNameFilter.size());

	_CORBA_ULong idx = 0;

	for (StringSet::const_iterator ci = eventNameFilter.begin(); ci != eventNameFilter.end(); ++ci)
		cf.nameList[idx++] = ci->c_str();

	cf.typeAndDomainList.length(2*eventTypeFilter.size());
	idx = 0;

	for (EventTypeSet::const_iterator ci = eventTypeFilter.begin(); ci != eventTypeFilter.end(); ++ci)
	{
		cf.typeAndDomainList[idx++] = ci->GetTypeName().c_str();
		cf.typeAndDomainList[idx++] = ci->GetDomainName().c_str();
	}

	cf.aliasToEventName.length(2*aliasToEvent.size());
	idx = 0;

	for (StringMap::const_iterator ci = aliasToEvent.begin(); ci != aliasToEvent.end(); ++ci)
	{
		cf.aliasToEventName[idx++] = ci->first.c_str();
		cf.aliasToEventName[idx++] = ci->second.c_str();
	}
}

void EventTransceiverSettings::SetCustomFilter(const EventManagerCustomFilter& cf)
{
	ClearEventFilter();
	RemoveAllAliasesForEvents();

	eventFilterEnabled = !!cf.enabled;

	const _CORBA_ULong ateLength = cf.aliasToEventName.length();
	const _CORBA_ULong nlLength = cf.nameList.length();
	const _CORBA_ULong tadLength = cf.typeAndDomainList.length();

	for (_CORBA_ULong idx = 0; (idx+1) < ateLength; idx += 2)
	{
		const string aliasName = (const char*) cf.aliasToEventName[idx];
		const string eventName = (const char*) cf.aliasToEventName[idx+1];

		SetAliasForEvent(aliasName, eventName);
	}

	SynchronizeEventToAlias();

	for (_CORBA_ULong idx = 0; idx < nlLength; ++idx)
	{
		const string name = (const char*) cf.nameList[idx];
		eventNameFilter.insert(name);
	}

	for (_CORBA_ULong idx = 0; (idx+1) < tadLength; idx += 2)
	{
		const string typeName = (const char*) cf.typeAndDomainList[idx];
		const string domainName = (const char*) cf.typeAndDomainList[idx+1];

		LiquidRobotics::EventLib::EventType et(typeName, domainName);

		eventTypeFilter.insert(et);
	}
}

}
}
