//-------------------------------------------------------------------------------
// <copyright file="LqrSMACObjectBroker.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 ObjectBroker class, which wraps common CORBA object broker
//   sequences
// </summary>
//
// <owner>Mike Cookson</owner>
//-------------------------------------------------------------------------------

#include "LqrSMACIdl.h"
#include "LqrSMACObjectBroker.h"
#include "LqrSMACIObjectBroker-inline.h"
#include "LqrSMACObjectBroker-inline.h"
#include <sstream>
#include <stdexcept>
#include <boost/foreach.hpp>
#include <boost/thread.hpp>

using namespace std;
using namespace CORBA;
using namespace DynamicAny;

namespace LiquidRobotics {
namespace SMACFramework {

ObjectBroker::ObjectBroker(bool _server)
: server(_server)
, initialized(false)
, listening(false)
, shutdownQueued(false)
, shuttingDown(false)
, shutDown(false)
, globalORB(ORB::_nil())
{
}

ObjectBroker::~ObjectBroker()
{
	if (initialized)
	{
		Shutdown();

		if (globalORB)
		{
			globalORB->destroy();
			globalORB = NULL;
		}
	}
}

Object_var ObjectBroker::ActivateInstance(PortableServer::Servant pServant)
{
	CheckState("ActivateInstance");

	Object_var resultV;
	Lock lock(*this);

	// check for the presence of pServant in the activation list
	ServantSet::iterator it = activations.find(pServant);

	// if it's not int he activation list ...
	if (it == activations.end())
	{
		// insert it into the activation list, assuming that activation will work
		activations.insert(pServant);

		bool activated = false;					// set when activated
		PortableServer::ObjectId_var oid;		// object ID of the newly-activated object

		try
		{
			// activate the object
			oid = rootPOAV->activate_object(pServant);

			activated = true;

			// get the object reference to be returned
			resultV = rootPOAV->id_to_reference(oid);
		}
		catch(...)
		{
			// deactivate the object if it was activated
			if (activated)
				try { rootPOAV->deactivate_object(oid); }
				catch(...) { }

			// remove the servant from the activation list if an exception was caught
			it = activations.find(pServant);

			if (it != activations.end())
				activations.erase(it);

			throw;
		}
	}
	else
		// get the object reference to be returned
		resultV = rootPOAV->servant_to_reference(*it);

	return resultV;
}

Object_var ObjectBroker::AdvertizeInstance(PortableServer::Servant pServant, const char* instanceName, const char* instanceKind)
{
	CheckState("AdvertiseInstance", true, true, true);

	Object_var tempV;
	tempV = ActivateInstance(pServant);

	CosNaming::Name objectName(1);
	objectName.length(1);
	objectName[0].id = string_dup(instanceName);
	objectName[0].kind = string_dup(instanceKind ? instanceKind : "");

	try { rootContextV->bind_new_context(objectName); } catch(CosNaming::NamingContext::AlreadyBound&) { }

	const NameItem item(instanceName, instanceKind ? instanceKind : "");
	const string stringizedName = GetStringizedReference(tempV);
	Lock lock(*this);
	NameItemSet::iterator it = names.find(item);

	if (it == names.end())
	{
		names.insert(item);
		stringNameToItem[stringizedName] = item;
	}

	try
	{
		rootContextV->rebind(objectName, tempV);
	}
	catch(...)
	{
		it = names.find(item);

		if (it != names.end())
			names.erase(it);

		StringizedNameToNameItemMap::iterator snit = stringNameToItem.find(stringizedName);

		if (snit != stringNameToItem.end())
			stringNameToItem.erase(snit);
	}

	return tempV;
}

void ObjectBroker::UnadvertiseAll()
{
	CheckState("UnadvertiseAll", true, false);

	Lock lock(*this);

	while(!names.empty())
	{
		NameItem item = *names.begin();

		lock.unlock();

		try 		{ UnadvertiseInstance(item.first.c_str(), item.second.c_str()); }
		catch(...)	{ }
	}

	stringNameToItem.clear();
}

void ObjectBroker::UnadvertiseInstance(const char* instanceName, const char* instanceKind)
{
	CheckState("UnadvertiseInstance", true, false);

	Lock lock(*this);
	NameItem item(instanceName, instanceKind ? instanceKind : "");
	NameItemSet::iterator it = names.find(item);

	if (it != names.end())
	{
		names.erase(it);
		lock.unlock();

		CosNaming::Name objectName(1);
		objectName.length(1);
		objectName[0].id = string_dup(item.first.c_str());
		objectName[0].kind = string_dup(item.second.c_str());

		rootContextV->unbind(objectName);
	}
}

void ObjectBroker::DeactivateAll()
{
	Lock lock(*this);

	while(!activations.empty())
	{
		PortableServer::Servant pServant = *activations.begin();

		lock.unlock();

		try			{ DeactivateInstance(pServant); }
		catch(...) 	{ }

		lock.lock();
	}
}

void ObjectBroker::DeactivateInstance(PortableServer::Servant pServant)
{
	CheckState("DeactivateInstance", true, false);

	Lock lock(*this);

	ServantSet::iterator it = activations.find(pServant);

	if (it != activations.end())
	{
		activations.erase(it);
		lock.unlock();

		PortableServer::ObjectId_var oid;
		oid = rootPOAV->servant_to_id(pServant);

		rootPOAV->deactivate_object(oid.in());
	}
}

Object_var ObjectBroker::GetRawObjectFromStringizedName(const char* instanceStringized)
{
	CheckState("GetRawObjectFromStringizedName");

	return globalORB->string_to_object(instanceStringized);
}


Object_var ObjectBroker::GetRawRootInstance(const char* instanceName)
{
	CheckState("GetRawRootInstance");

	return LocalGetRawRootInstance(instanceName);
}

Object_var ObjectBroker::GetRawNamedInstance(const char* instanceName, const char* instanceKind, bool validateName, StringPairVectorSet* cachePtr)
{
	CheckState("GetRawNamedInstance");

	if (validateName)
		ValidateNamedInstance(instanceName, instanceKind);

	CosNaming::Name objectName(1);
	objectName.length(1);
	objectName[0].id = string_dup(instanceName);
	objectName[0].kind = string_dup(instanceKind ? instanceKind : "");

	Object_var objV;
	objV = rootContextV->resolve(objectName);

	if (is_nil(objV.in()))
	{
		stringstream stm;
		stm << "GetRawNameInstance nil instance for " << instanceName;
		if (instanceKind) stm << "/" << instanceKind;
		throw runtime_error(stm.str());
	}

	return objV;
}

string ObjectBroker::GetStringizedReference(Object_ptr pRef)
{
	CheckState("GetStringizedReference");

	String_var sRef = globalORB->object_to_string(pRef);
	const char* pstr = sRef.in();
	string result(pstr ? pstr : "");

	return result;
}

void ObjectBroker::Initialize(int& argc, char**& argv)
{
	if (shutDown || shutdownQueued) throw ShutDown("Initialize: ObjectBroker is shut down");
	if (initialized) throw NotInitialized("Initialize: ObjectBroker was already initialized");

	try
	{
		initialized = true;

		globalORB = ORB_init(argc, argv);
		rootContextV = LocalGetRootInstance<CosNaming::NamingContext_var,CosNaming::NamingContext>("NameService");
		rootPOAV = LocalGetRootInstance<PortableServer::POA_var,PortableServer::POA>("RootPOA");
		rootPOAMgrV = rootPOAV->the_POAManager();

		if (is_nil(rootPOAMgrV))
			throw runtime_error("Failed to narrow the POA manager");
	}
	catch(...)
	{
		initialized = false;
		throw;
	}
}

bool ObjectBroker::IsShutdownQueued()
{
	return shutdownQueued;
}

bool ObjectBroker::IsShutDown()
{
	return shutDown;
}

void ObjectBroker::ListenForRequests()
{
	CheckState("ListenForRequests", true, true, true);

	rootPOAMgrV->activate();
	listening = true;
	globalORB->run();
}

void ObjectBroker::QueueShutdown()
{
	if (initialized && !shutDown && !shutdownQueued)
	{
		shutdownQueued = true;
		boost::thread thr(boost::bind(&ObjectBroker::QueuedShutdownCallback, this));
	}
}

void ObjectBroker::QueuedShutdownCallback()
{
	if (initialized && !shutDown && shutdownQueued)
		Shutdown();
}

void ObjectBroker::Shutdown(bool waitForCompletion)
{
	if (initialized && !shutDown && !shuttingDown)
	{
		shuttingDown = true;

		ShutdownSignal(*this);

		shutDown = true;
		shuttingDown = false;
		shutdownQueued = false;

		UnadvertiseAll();
		DeactivateAll();

		rootPOAMgrV = PortableServer::POAManager::_nil();
		rootPOAV  = PortableServer::POA::_nil();
		rootContextV = CosNaming::NamingContext::_nil();

		/*if (!listening)*/
			try { globalORB->shutdown(waitForCompletion); } catch(...) { }

		//try { globalORB->destroy(); } catch(...) { }

		//initialized = false;
	}
}

Object_var ObjectBroker::LocalGetRawRootInstance(const char* instanceName)
{
	//CheckState("LocalGetRawRootInstance");

	Object_var objV;
	objV = globalORB->resolve_initial_references(instanceName);

	return objV;
}

Object_var ObjectBroker::GetRawDynamicObjectFromTypeCode(TypeCode_ptr typeCode)
{
	CheckState("GetRawDynamicObjectFromTypeCode");

	// access the factory
	DynamicAny::DynAnyFactory_var dynFactoryV;
	dynFactoryV = GetRootInstance<DynamicAny::DynAnyFactory_var,DynamicAny::DynAnyFactory>("DynAnyFactory");

	// create an instance of the type
	Object_var objV;
	objV = dynFactoryV->create_dyn_any_from_type_code(typeCode);

	return objV;
}

void ObjectBroker::AnyToStringVector(StringVector& to, const Any& from)
{
#if 0
	// (ORB isn't used) CheckState("AnyToStringVector");

	// import the any into a DynArray
	TypeCode_var fromType;
	fromType = from.type();

	DynArray_var dynArrayV;
	dynArrayV = GetDynamicObjectFromTypeCode<DynArray_var,DynArray>(fromType.in());

	// after this point, the dynArrayV must be destroyed
	DynAnySeq* dynSeqPtr = NULL;
	try
	{
		dynArrayV->from_any(from);

		// get the elements of the sequence
		dynSeqPtr = dynArrayV->get_elements_as_dyn_any();

		// clear the size initially, but reserve enough space for the entire set
		const _CORBA_ULong seqLen = dynSeqPtr->length();
		to.resize(seqLen);

		// now copy each string
		for (_CORBA_ULong idx = 0; idx < seqLen; ++idx)
		{
			String_var strV;
			strV = (*dynSeqPtr)[idx]->get_string();
			to[idx] = strV.in();
		}

		// release the sequence ptr
		DynAnySeq* temp = dynSeqPtr;
		dynSeqPtr = NULL;
		delete temp;
	}
	catch(...)
	{
		if (dynSeqPtr) delete dynSeqPtr;
		dynArrayV->destroy();
		throw;
	}

	dynArrayV->destroy();
#else
	AnyToCppVector<StringVector>(to, from);
#endif
}

Any_var ObjectBroker::StringVectorToAny(const StringVector& from)
{
	CheckState("StringVectorToAny(const StringVector&)");
#if 0
	Any_var result;

	// create a type code for the string array
	TypeCode_var tcV = globalORB->create_array_tc(from.size(), _tc_string);

	// create an instance of the dynamic array
	DynArray_var dynArrayV;
	dynArrayV = GetDynamicObjectFromTypeCode<DynArray_var,DynArray>(tcV.in());

	// after this point, the dynArrayV must be destroyed
	DynAnySeq* dynSeqPtr = NULL;
	try
	{
		// get the elements of the sequence
		dynSeqPtr = dynArrayV->get_elements_as_dyn_any();

		// add strings at each element
		_CORBA_ULong i = 0;

		BOOST_FOREACH(const string& s, from)
			(*dynSeqPtr)[i++]->insert_string(s.c_str());

		// compute the result
		result = dynArrayV->to_any();

		// release the sequence ptr
		DynAnySeq* temp = dynSeqPtr;
		dynSeqPtr = NULL;
		delete temp;
	}
	catch(...)
	{
		if (dynSeqPtr) delete dynSeqPtr;
		dynArrayV->destroy();
		throw;
	}

	dynArrayV->destroy();
	return result;
#else
	return CppVectorToAny<StringVector>(from, _tc_string);
#endif
}

Any_var ObjectBroker::StringVectorToAny(const string* pStart, size_t numElems)
{
	CheckState("StringVectorToAny(const string*, size_t)");
#if 0
	Any_var result;

	// create a type code for the string array
	TypeCode_var tcV;
	tcV = globalORB->create_array_tc(numElems, _tc_string);

	// create an instance of the dynamic array
	DynArray_var dynArrayV;
	dynArrayV = GetDynamicObjectFromTypeCode<DynArray_var,DynArray>(tcV.in());

	// after this point, the dynArrayV must be destroyed
	DynAnySeq* dynSeqPtr = NULL;
	try
	{
		// get the elements of the sequence
		dynSeqPtr = dynArrayV->get_elements_as_dyn_any();

		// add strings at each element
		_CORBA_ULong x = dynSeqPtr->length();

		for (_CORBA_ULong i = 0; i < x; ++i)
		{
			const string& str = pStart[i];
			(*dynSeqPtr)[i]->insert_string(str.c_str());
		}

		// compute the result
		result = dynArrayV->to_any();

		// release the sequence ptr
		DynAnySeq* temp = dynSeqPtr;
		dynSeqPtr = NULL;
		delete temp;
	}
	catch(...)
	{
		if (dynSeqPtr) delete dynSeqPtr;
		dynArrayV->destroy();
		throw;
	}

	return result;
#else
	return CppVectorToAny<string>(pStart, numElems, _tc_string);
#endif
}

void ObjectBroker::CheckState(const char* caller, bool initCheck, bool shutdownCheck, bool checkServer) const
{
	if (shutdownCheck && shutDown)
	{
		stringstream msg;
		msg << caller << ": ObjectBroker is shut down";
		throw ShutDown(msg.str());
	}

	if (initCheck && !initialized)
	{
		stringstream msg;
		msg << caller << ": ObjectBroker has not been initialized";
		throw NotInitialized(msg.str());
	}

	if (checkServer && !server)
	{
		stringstream msg;
		msg << caller << ": ObjectBroker has not been initialized as a server";
		throw NotInitializedAsServer(msg.str());
	}
}

ORB_var ObjectBroker::ORB()
{
	CheckState("ORB()");
	return globalORB;
}

void ObjectBroker::GetServers(StringPairVectorSet& servers)
{
	CheckState("GetServers");
	servers.clear();

	const _CORBA_ULong blockSize = 25;
	CosNaming::BindingList_var bl;
	CosNaming::BindingIterator_var bi;

	rootContextV->list(blockSize, bl, bi);

	const _CORBA_Boolean hasIterator = !CORBA::is_nil(bi);

	try
	{
		StringPairVectorSet theSet;
		_CORBA_Boolean done = (bl->length() == 0);

		while(!done)
		{
			const _CORBA_ULong len = bl->length();

			for (_CORBA_ULong idx = 0; idx < len; ++idx)
			{
				CosNaming::Name& nam = bl[idx].binding_name;
				const _CORBA_ULong nameLength = nam.length();
				StringPairVector spv(nameLength);

				for (_CORBA_ULong nameIdx = 0; nameIdx < nameLength; ++nameIdx)
				{
					spv[nameIdx].first = nam[nameIdx].id;
					spv[nameIdx].second = nam[nameIdx].kind;
				}

				servers.insert(spv);
			}

			if (hasIterator)
			{
				bi->next_n(blockSize, bl);
				done = (bl->length() == 0);
			}
			else
				done = true;
		}

		if (hasIterator)	bi->destroy();
	}
	catch(...)
	{
		if (hasIterator)	bi->destroy();
		throw;
	}

}

bool ObjectBroker::ValidateNamedInstance(const char* instanceName, const char* instanceKind, StringPairVectorSet* cachePtr, bool throwOnFailure)
{
	CheckState("ValidateNamedInstance");

	if (!instanceName)
		throw invalid_argument("ValidateNamedInstance: instanceName passed as NULL");

	StringPairVectorSet servers;

	if (!cachePtr)
	{
		GetServers(servers);
		cachePtr = &servers;
	}

	StringPairVector srch(1);
	srch[0].first = instanceName;
	if (instanceKind) srch[0].second = instanceKind;

	const bool result = (servers.find(srch) != servers.end());

	if (!result && throwOnFailure)
	{
		CosNaming::NamingContext::NotFound nfex;
		nfex._raise();
	}

	return result;
}

bool ObjectBroker::GetAdvertizedInstanceNameFromStringizedName(const char* instanceStringized, string& advertizedInstanceName, string& advertizedInstanceKind) const
{
	CheckState("GetAdvertizedInstanceNameFromStringizedName");

	if (!instanceStringized)
		throw invalid_argument("GetAdvertizedInstanceNameFromStringizedName: instanceStringized passed as NULL");

	bool result;
	Lock lock(const_cast<ObjectBroker&>(*this));
	StringizedNameToNameItemMap::const_iterator snit = stringNameToItem.find(instanceStringized);

	if (snit != stringNameToItem.end())
	{
		advertizedInstanceName = snit->second.first;
		advertizedInstanceKind = snit->second.second;
		result = true;
	}
	else
		result = false;

	return result;
}

}
}
