//-------------------------------------------------------------------------------
// <copyright file="EventBase.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 EventBase class, which is a common base class for all event
//  classes.
// </summary>
//
// <owner>Mike Cookson</owner>
//-------------------------------------------------------------------------------

#include "EventBase.h"
#include <sstream>
#include <string>
#include <boost/foreach.hpp>

using namespace std;

namespace LiquidRobotics {
namespace EventLib {

IMPLEMENT_INNER_EXCEPTION(EventBase,InvalidField,std::runtime_error);
IMPLEMENT_INNER_EXCEPTION(EventBase,IORNotSet,std::runtime_error);
IMPLEMENT_INNER_EXCEPTION(EventBase,MissingRequiredField,std::runtime_error);
IMPLEMENT_INNER_EXCEPTION(EventBase,PropertyNotFound,std::runtime_error);
IMPLEMENT_INNER_EXCEPTION(EventBase,PropertyValueNotSupported,std::runtime_error);

const string EventBase::SenderIORTag = "SENDERIOR";

static const string IORNotSet_text("SenderIOR has not been set");

inline string PropertyNotFound_text(const string& propertyName)
{
	stringstream stm;
	stm << "Named property (" << propertyName << ") was not found in event";

	return stm.str();
}

inline string PropertyValueNotSupported_text(const string& propertyName)
{
	stringstream stm;
	stm << "Named property (" << propertyName << ") found in event, but type of value is not supported";

	return stm.str();
}

EventBase::EventBase(IObjectBroker& _broker)
: broker(_broker)
{
}

EventBase::EventBase(const EventSettings& _eventSettings, IObjectBroker& _broker)
: eventSettings(_eventSettings)
, broker(_broker)
{

}

EventBase::EventBase(const EventName& _eventName, const EventType& _eventType, IObjectBroker& _broker)
: eventSettings(_eventName, _eventType)
, broker(_broker)
{
}

EventBase::EventBase(const EventName& _eventName, const EventType& _eventType, const IOR& _senderIOR, IObjectBroker& _broker)
: eventSettings(_eventName, _eventType)
, senderIOR(_senderIOR)
, broker(_broker)
{
}

EventBase::EventBase(const EventBase& right)
: broker(right.broker)
{
	Copy(right);
}

EventBase& EventBase::operator=(const EventBase& other)
{
	if (this != &other)
		Copy(other);

	return *this;
}

IEventBase::PropertyValuePtr EventBase::CreateValue()
{
	PropertyValuePtr result;
	result.reset(new PropertyValueType(broker));
	return result;
}

ostream& EventBase::Describe(ostream& stm) const
{
	stm << eventSettings;

	return stm;
}

string EventBase::Describe() const
{
	stringstream msg;
	Describe(msg);
	return msg.str();
}

ostream& EventBase::DescribeGeneric(ostream& stm) const
{
	stm << eventSettings;

	if (senderIOR.length())
		stm << " ior=" << senderIOR;

	stm << " properties={";

	for (PropertyLookup::const_iterator cit = properties.begin(); cit != properties.end(); ++cit)
	{
		stm << " (" << cit->first << ",";

		if (cit->second)
			cit->second->Describe(stm, true);
		else
			stm << "nil";

		stm << ")";
	}

	return stm;
}

string EventBase::DescribeGeneric() const
{
	stringstream msg;
	Describe(msg);
	return msg.str();
}

IEventBase::Ptr EventBase::Duplicate() const
{
	IEventBase::Ptr ptr;
	ptr.reset(new EventBase(broker));

	((EventBase*) ptr.get())->Copy(*this);
	return ptr;
}

const EventName& EventBase::GetEventName() const
{
	return eventSettings.GetName();
}

void EventBase::SetEventName(const EventName& newValue)
{
	eventSettings.SetName(newValue);
}

const EventSettings& EventBase::GetEventSettings() const
{
	return eventSettings;
}

void EventBase::SetEventSettings(const EventSettings& newValue)
{
	eventSettings = newValue;
}

const EventType& EventBase::GetEventType() const
{
	return eventSettings.GetType();
}

void EventBase::SetEventType(const EventType& newValue)
{
	eventSettings.SetType(newValue);
}

bool EventBase::ContainsSenderIOR() const
{
	return senderIOR.length() != 0;
}

const IOR& EventBase::GetSenderIOR() const
{
	if (senderIOR.length())
		return senderIOR;
	else
		throw IORNotSet(IORNotSet_text);
}

void EventBase::SetSenderIOR(const IOR& newValue)
{
	senderIOR = newValue;
}

void EventBase::ClearProperties()
{
	properties.clear();
}

void EventBase::ClearProperty(const PropertyNameType& propertyName)
{
	PropertyLookup::iterator it = properties.find(propertyName);

	if (it == properties.end())
		throw PropertyNotFound(PropertyNotFound_text(propertyName));

	properties.erase(it);
}

bool EventBase::ContainsProperty(const PropertyNameType& propertyName, bool throwOnUnsupportedType) const
{
	PropertyLookup::const_iterator it = properties.find(propertyName);

	if ((it != properties.end()) && throwOnUnsupportedType)
		if (it->second->GetStorageType() == PropertyValueType::ST_UNSUPPORTED)
			throw PropertyValueNotSupported(PropertyValueNotSupported_text(propertyName));

	return it != properties.end();
}

EventBase::PropertyValueType& EventBase::GetProperty(const PropertyNameType& propertyName, bool throwOnUnsupportedType)
{
	PropertyLookup::iterator it = properties.find(propertyName);

	if (it == properties.end())
		throw PropertyNotFound(PropertyNotFound_text(propertyName));

	if (throwOnUnsupportedType)
		if (it->second->GetStorageType() == PropertyValueType::ST_UNSUPPORTED)
			throw PropertyValueNotSupported(PropertyValueNotSupported_text(propertyName));

	return *it->second;
}

const EventBase::PropertyValueType& EventBase::GetProperty(const PropertyNameType& propertyName, bool throwOnUnsupportedType) const
{
	PropertyLookup::const_iterator it = properties.find(propertyName);

	if (it == properties.end())
		throw PropertyNotFound(PropertyNotFound_text(propertyName));

	if (throwOnUnsupportedType)
		if (it->second->GetStorageType() == PropertyValueType::ST_UNSUPPORTED)
			throw PropertyValueNotSupported(PropertyValueNotSupported_text(propertyName));

	return *it->second;
}

IEventBase::PropertyNameArrayPtr EventBase::GetPropertyNames() const
{
	PropertyNameArrayPtr result;

	result.reset(new PropertyNameArrayType);

	BOOST_FOREACH(PropertyLookup::value_type val, properties)
		result->push_back(val.first);

	return result;
}

void EventBase::SetProperty(const PropertyNameType& propertyName, const PropertyValueType& newValue)
{
	PropertyValueType::Ptr ptr;

	ptr.reset(new PropertyValueType(newValue));
	properties[propertyName] = ptr;
}

EventBase::CORBA_StructuredEvent_var EventBase::CreateStructuredEvent() const
{
	CORBA_StructuredEvent_var se = new CORBA_StructuredEvent;

	se->header.fixed_header.event_name = ::CORBA::string_dup(eventSettings.GetName().c_str());
	eventSettings.GetType().CopyToCORBAEventType(se->header.fixed_header.event_type);

	if (!senderIOR.empty())
	{
		se->filterable_data.length(1);
		se->filterable_data[0].name = ::CORBA::string_dup(SenderIORTag.c_str());
		se->filterable_data[0].value <<= senderIOR.c_str();
	}
	else
		se->filterable_data.length(0);

	se->header.variable_header.length((_CORBA_ULong) properties.size());

	_CORBA_ULong idx = 0;

	BOOST_FOREACH(const PropertyLookup::value_type& propIn, properties)
	{
		CosNotification::Property& propOut = se->header.variable_header[idx++];

		propOut.name = ::CORBA::string_dup(propIn.first.c_str());
		propIn.second->CopyOut(propOut.value);
	}

	return se;
}

void EventBase::InitFromStructuredEvent(const CORBA_StructuredEvent& structEvent)
{
	ClearProperties();

	eventSettings.SetName((const char*) structEvent.header.fixed_header.event_name);

	EventType newType;
	newType.InitFromCORBAEventType(structEvent.header.fixed_header.event_type);
	eventSettings.SetType(newType);

	const _CORBA_ULong filterablePropCount = structEvent.filterable_data.length();
	bool foundSenderIOR = false;

	for (_CORBA_ULong idx = 0; idx < filterablePropCount; ++idx)
	{
		CosNotification::Property propIn = structEvent.filterable_data[idx];

		if (!SenderIORTag.compare(propIn.name))
		{
			const char* iorstr;
			bool extracted = propIn.value >>= iorstr;

			if (!extracted)
			{
				stringstream stm;
				stm << "Filterable field '" << SenderIORTag << "' was present, but could not be converted to string";

				throw InvalidField(stm.str());
			}

			else if (!*iorstr)
			{
				stringstream stm;
				stm << "Filterable field '" << SenderIORTag << "' was present, but empty";

				throw InvalidField(stm.str());
			}

			else if (foundSenderIOR)
			{
				stringstream stm;
				stm << "Duplicate definition of filterable field '" << SenderIORTag << "'";

				throw InvalidField(stm.str());
			}

			else
			{
				senderIOR = iorstr;
				foundSenderIOR = true;
			}
		}
		else
		{
			stringstream stm;
			stm << "Unexpected field '" << propIn.name << "' found in event filterable header";

			throw InvalidField(stm.str());
		}
	}

	const _CORBA_ULong varPropCount = structEvent.header.variable_header.length();

	for (_CORBA_ULong idx = 0; idx < varPropCount; ++idx)
	{
		CosNotification::Property propIn = structEvent.header.variable_header[idx];
		const char* propName = (const char*) propIn.name;

		if (properties.find(propName) == properties.end())
		{
			PropertyValueType::Ptr newValue;
			newValue.reset(new PropertyValueType(propIn.value, broker));

			properties[propName] = newValue;;
		}
		else
		{
			stringstream stm;
			stm << "Duplicate field '" << propName << "' found in event variable header";

			throw InvalidField(stm.str());
		}
	}
}

void EventBase::Copy(const EventBase& other)
{
	eventSettings = other.eventSettings;
	senderIOR = other.senderIOR;

	BOOST_FOREACH(PropertyLookup::value_type prop, other.properties)
	{
		PropertyValueType::Ptr newProp;
		newProp.reset(new PropertyValueType(*prop.second));

		properties[prop.first] = newProp;
	}
}

}
}
