//-------------------------------------------------------------------------------------
// <copyright file="SCLookup.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 SCLookup, looks up or checks for a storage controller for a given
//  board address.
// </summary>
//
// <owner>Mike Cookson</owner>
//-------------------------------------------------------------------------------------

#include "SCLookup.h"
#include "DataSetEvent.h"
#include "LqrSMACIObjectBroker.h"
#include "LqrSMACLogger.h"
#include "nsextend.h"
#include "PayloadInterfaceController_Config.h"
#include "PayloadManagerClient.h"
#include "PayloadMessage.h"
#include "SendCommandCompleteEvent.h"
#include <stdexcept>

using namespace std;
using namespace LiquidRobotics::SMACFramework;
using namespace LiquidRobotics::EventLib;

namespace LiquidRobotics {
namespace PayloadInterface {

SCLookup::SCLookup(
		IObjectBroker& _broker,
		PayloadManagerClient& _pmclient,
		Logger& _logger,
		const StorageController::Config& _scconfig,
		const PayloadInterfaceController_Config& _config,
		EventTransceiver& _et)
: broker(_broker)
, pmclient(_pmclient)
, logger(_logger)
, scconfig(_scconfig)
, config(_config)
, et(_et)
{
}

StorageController& SCLookup::GetSC(uint16_t boardAddress)
{
	return GetSCPtr(boardAddress)->GetSC();
}

ProxySC::Ptr SCLookup::GetSCPtr(uint16_t boardAddress)
{
	ProxySC::Ptr result;

	// just return a reference if there's already an instance
	if (pscDict.HasSC(boardAddress))
		result = pscDict.GetSC(boardAddress);

	else
	{
		// otherwise, see if there's a commandable target and throw if not
		if (!ProxySC::HaveCommandableTarget(boardAddress, broker, pmclient))
		{
			stringstream stm;
			stm << "No commandable target found for board address 0x" << ultos(boardAddress, 16, 4) << "!";

			Logger::Locked(logger)->Error << stm.str() << endl;
			throw runtime_error(stm.str());
		}

		// otherwise ...
		else
		{
			// create a new proxy instance for this board address
			Logger::Locked(logger)->Info << "Adding new storage controller proxy instance for 0x" << ultos(boardAddress, 16, 4) << endl;
			result.reset( new ProxySC(GetBoardID(boardAddress), GetTaskID(boardAddress), scconfig, broker, pmclient, logger, config.GetMaxCommandOutput()) );

			// subscribe to new file available for file-based execution
			result->Subscribe(ProxySC::NewFileSlotType(boost::bind(&SCLookup::SCNewFileAvailable, this, _1, _2, _3)));

			// subscribe to complete signal for console-based execution
			result->Subscribe(ProxySC::CompleteSignalSlotType(boost::bind(&SCLookup::SCCommandComplete, this, _1, _2, _3, _4, _5, _6, _7)));

			// add the proxy to the map
			pscDict.AddSC(result);
		}
	}

	return result;
}

void SCLookup::Initialize()
{
	// configure EventTransceiver to create the appropriate class for SendCommandComplete event types
	const EventSettings& cmdCmpltEvent = config.GetCommandCompleteEventSettings();
	et.RegisterEventClassFactory(cmdCmpltEvent.GetType(), new EventTransceiver::TEventClassFactory<SendCommandCompleteEvent>);

	// subscribe to command complete events on AsynchCmdCmpltHandler
	EventTransceiver::InformSignalType& sig0 = et.SignalForEventName(cmdCmpltEvent.GetName());
	connectionToCmdCmpltEvent.reset(
			new ScopedConnection(
					sig0.connect(
							boost::bind(&SCLookup::AsynchCmdCmpltHandler, this, _1, _2)
							)));

	// register and subscribe to received new file available event(s)
	typedef PayloadInterfaceController_Config::EventAndDestinationList EventAndDestinationList;
	const EventAndDestinationList& nfaList = config.GetNewFileAvailableEventSettings();

	for (EventAndDestinationList::const_iterator it = nfaList.begin(); it != nfaList.end(); ++it)
	{
		et.RegisterEventClassFactory(it->first.GetType(), new EventTransceiver::TEventClassFactory<DataSetEvent>);

		EventTransceiver::InformSignalType& nfasig = et.SignalForEventName(it->first.GetName());
		ScopedConnectionPtr ptr;

		ptr.reset(new ScopedConnection(nfasig.connect(boost::bind(&SCLookup::DataSetHandler, this, _1, _2))));
		connectionToNewFileAvailableEvent.push_back(ptr);
	}


}

ProxySCDictionary::ProxySCListPtr SCLookup::GetSCList() const
{
	return pscDict.GetSCList();
}

bool SCLookup::HaveSC(uint16_t boardAddress)
{
	bool result = pscDict.HasSC(boardAddress);

	if (!result)
	{
		try
		{
			GetSC(boardAddress);
			result = true;
		}
		catch(exception& ex)
		{
			Logger::Locked(logger)->Error << "Caught " << typeid(ex).name() << " during GetSC(" << boardAddress << "): " << ex.what() << endl;
		}
		catch(...)
		{
			Logger::Locked(logger)->Error << "Caught an unspecified exception during GetSC()" << endl;
		}
	}

	return result;
}

void SCLookup::AsynchCmdCmpltHandler(EventTransceiver& sender, const IEventBase& receivedEvent)
{
	Logger::Locked(logger)->Info << "Asynch Cmd Event" << endl;

	const EventSettings& cmdCmpltEvent = config.GetCommandCompleteEventSettings();
	const SendCommandCompleteEvent& evt =
		EventTransceiver::UpcastEventRef<SendCommandCompleteEvent>(receivedEvent, cmdCmpltEvent,
				"SCLookup::AsynchCmdCmpltHandler");

	ProxySCDictionary::ProxySCListPtr listPtr = GetSCList();

	for (ProxySCDictionary::ProxySCList::const_iterator it = listPtr->begin(); it != listPtr->end(); ++it)
		(*it)->ProcessCommandCompletion(evt);
}

void SCLookup::DataSetHandler(EventTransceiver& sender, const IEventBase& receivedEvent)
{
	typedef PayloadInterfaceController_Config::EventAndDestinationList EventAndDestinationList;
	const DataSetEvent* evtPtr = NULL;

	try			{ evtPtr = dynamic_cast<const DataSetEvent*>(&receivedEvent); }
	catch(...)	{ Logger::Locked(logger)->Error << receivedEvent << " did not upcast to DataSetEvent! (event will be ignored)" << endl; }

	if (evtPtr) {
		stringstream msginfo;
		evtPtr->Describe(msginfo);
		try
		{
			if (!evtPtr->HasGeneratorAddress())
			{
				Logger::Locked(logger)->Info << "Ignoring data set event with no generator address (" << msginfo.str() << ")" << endl;
				return;
			}

			const EventAndDestinationList& nfaList = config.GetNewFileAvailableEventSettings();
			const string receivedEventName = receivedEvent.GetEventName();

			for (EventAndDestinationList::const_iterator it = nfaList.begin(); it != nfaList.end(); ++it)
			{
				if (!receivedEventName.compare(it->first.GetName()))
				{
					EventPropertyValue::UInt8 _boardID, _taskID;
					evtPtr->GetGeneratorAddress(_boardID, _taskID);

					PayloadMessage::BoardAddress boardAddress = MakeBoardAddress(_boardID, _taskID);
					GetSC(boardAddress);

					if (HaveSC(boardAddress)) {
						Logger::Locked(logger)->Debug << "Forwarding [" << msginfo.str() << "] to storage controller 0x" << utos(boardAddress,16,4) << endl;
						GetSCPtr(boardAddress)->ProcessNewFileDataSet(*evtPtr, it->second);
					}
					else
						Logger::Locked(logger)->Warning << "Failed to create storage controller for 0x" << utos(boardAddress,16,4) << " [" << msginfo.str() << "]" << endl;

					break;
				}
			}
		}
		catch(CORBA::Exception& ex)
		{
			Logger::Locked(logger)->Error << "Caught CORBA exception: " << DecodeCORBAException(ex) << " while handling new file available data set" << endl;
		}
		catch(exception& ex)
		{
			Logger::Locked(logger)->Error << "Caught exception " << typeid(ex).name() << " '" << ex.what() << "' while handling new file available data set" << endl;
		}
	}
}


}
}
