//------------------------------------------------------------------------------
// <copyright file="ConnectionManagerProgram.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 ConnectionManagerProgram class
// </summary>
//
// <owner>Arash Ushani</owner>
//------------------------------------------------------------------------------

#include "ConnectionManagerProgramSettings.h"
#include "ConnectionManagerChannel.h"
#include "ConnectionManagerRUDICS.h"
#include "ChannelStateEvent.h"
#include "ChannelRequestEvent.h"
#include "LqrSMACGetErrorDescription.h"
#include "ConnectionManagerProgram.h"
#include "ConnectionManagerProgramSettings.h"
#include <boost/foreach.hpp>

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

namespace LiquidRobotics {
namespace ConnectionManager {

const string ConnectionManagerProgram::CMD_REQUEST = "REQUEST";
const string ConnectionManagerProgram::CMD_CANCELREQUEST = "CANCEL";
const string ConnectionManagerProgram::CMD_GETSTATE = "STATE";

ConnectionManagerProgram::ConnectionManagerProgram(IObjectBroker& _broker, const ConnectionManagerProgramSettings& _settings, Logger::Ptr _logger)
: EventTransceiver(_settings.GetEventTransceiverSettings(), _broker, *_logger)
, broker(_broker)
, settings(_settings)
, logger(_logger)
{
	ConnectionManagerProgramSettings::ChannelSettingsList channelSettingsList = settings.GetChannelSettings();

	for (ConnectionManagerProgramSettings::ChannelSettingsList::iterator channelSettings = channelSettingsList.begin(); channelSettings!=channelSettingsList.end(); channelSettings++)
	{
		int protoRev = channelSettings->GetProtocolRevision();

		logger->Info << "Making Channel 0x" << std::hex << protoRev << std::dec << endl;

		switch (protoRev) {
			case ConnectionManagerChannelSettings::RUDICS:
			channels[protoRev].reset(new ConnectionManagerRUDICS(*channelSettings, logger));
				break;
			default:
			channels[protoRev].reset(new ConnectionManagerChannel(*channelSettings, logger));
				break;
		}

		channels[protoRev]->ChannelOff();
		currentRequests[protoRev] = 0;

		// Subscribe to Signal Strength signal
		if (channels[protoRev]->ReportsSignalStrength())
		{
			logger->Info << "Subscribing to 0x" << std::hex << protoRev << std::dec << " Signal Strength. " << endl;
			ScopedConnectionPtr connection = channels[protoRev]->SubscribeToSignalStrength(boost::bind(&ConnectionManagerProgram::SignalStrengthHandler, this, _1, _2));
			signalStrengthConnections[protoRev] = connection;
		}
	}

	logger->Info << "Constructed channel map." << endl;
}

ConnectionManagerProgram::~ConnectionManagerProgram()
{
}

void ConnectionManagerProgram::Initialize(Object_ptr _pServer, PayloadManagerClient::Ptr _pmClient)
{
	pmClient = _pmClient;

	logger->Info << "--------------------------------" << endl;
	logger->Info << "Connection Manager " << std::dec << MAJOR << "." << MINOR << "." << BUILD << endl;

	// Subscribe to Request event
	InformSignalType& sig = SignalForEventName(settings.GetChannelRequestEvent().GetName());
	connectionToRequestConnectionEvent.reset(new ScopedConnection(sig.connect(boost::bind(&ConnectionManagerProgram::ChannelRequestEventHandler, this, _1, _2))));

	// initializing event transceiver
	EventTransceiver::Initialize(_pServer);

	// initialize timer collection
	timerCollection.Initialize();
}

::LiquidRobotics::SMAC::EnumerationData* ConnectionManagerProgram::GetEnumerationData()
{
	EnumerationData_var result;
	result = new EnumerationData;
	return result._retn();
}

void ConnectionManagerProgram::PowerOff(::LiquidRobotics::SMAC::PowerOffTimeType offDelay)
{
	logger->Info << "PowerOff offDelay=" << offDelay << endl;
}

void ConnectionManagerProgram::PowerOn()
{
	logger->Info << "PowerOn" << endl;
}

::LiquidRobotics::SMAC::BoardIDType ConnectionManagerProgram::boardID()
{
	return pmClient->GetMyBoardID();
}

::LiquidRobotics::SMAC::PayloadDescription* ConnectionManagerProgram::description()
{
	Desc::CORBAEntity_var result = settings.GetPayloadManagerClientSettings().GetPayloadDescription().GetEntity();
	return result._retn();
}

::LiquidRobotics::SMAC::EPayloadEnableState ConnectionManagerProgram::enableState()
{
	return EPES_ENABLED;
}

::LiquidRobotics::SMAC::PowerOffTimeType ConnectionManagerProgram::requestOffTime()
{
	return (PowerOffTimeType) 0;
}

::LiquidRobotics::SMAC::PowerOnTimeType ConnectionManagerProgram::requestOnTime()
{
	return (PowerOnTimeType) 0;
}

::LiquidRobotics::SMAC::ComponentVersion* ConnectionManagerProgram::softwareVersion()
{
	ComponentVersion_var result;
	result = new ComponentVersion;
	stringstream ss;
	ss << MAJOR << "." << MINOR << "." << BUILD;
	result->versionString = CORBA::string_dup(ss.str().c_str());
	result->majorVersion = 1;
	result->minorVersion = 0;
	result->subminorVersion = 0;
	return result._retn();
}

::LiquidRobotics::SMAC::TaskIDType ConnectionManagerProgram::taskID()
{
	return pmClient->GetMyTaskID();
}

::CORBA::Double ConnectionManagerProgram::Ping()
{
	using namespace LiquidRobotics::SMACFramework;
	stringstream msg;
	msg << "ConnectionManagerProgram::Ping: ";

	::CORBA::Double result;
	timeval tv;
	int status = gettimeofday(&tv, NULL);
	int errSave = errno;

	if (!status)
	{
		result = tv.tv_usec;
		result /= 1000000.0;
		result += tv.tv_sec;
	}
	else
	{
		msg << "(gettimeofday returned error #" << errSave << " '" << GetErrorDescription(errSave) << "') ";
		result = 0;
	}

	Logger::Lock logLock(*logger);
	logger->Info.precision(15);
	logger->Info << msg.str() << result << endl;
	return result;
}

void ConnectionManagerProgram::SendCommand(const ::CORBA::Any& command, const char* requester, const char* transactionID, ::CORBA::Any_OUT_arg result)
{
	using namespace LiquidRobotics::SMACFramework;
	const char* cmd0;

	if (!(command >>= cmd0))
	{
		logger->Error << "Incoming command type is not a string" << endl;
		ThrowCORBAException2<BadParameter>();
	}

	logger->Info << "ConnectionManager Command:" << cmd0 << "' requester='" << requester << "' transactionID='" << transactionID << "'" << endl;

	const string cmd = cmd0;

	const string resultStr = ProcessCommand(cmd);
	logger->Info << "Returning result='" << resultStr << "'" << endl;

	result.ptr() = new Any;
	*result.ptr() <<= resultStr.c_str();
}

string ConnectionManagerProgram::ProcessCommand(const string& command)
{
	string cmd = boost::to_upper_copy(command);
	string result = "ERROR";

	try
	{
		std::string commandStr, protoRevStr;
		unsigned int i;

		cmd.erase( 0, cmd.find_first_not_of(" \t") );
		i = cmd.find_first_of(" \t");
		if (i==string::npos)
			return result;
		commandStr = cmd.substr(0, i);
		cmd.erase(0, i);

		cmd.erase( 0, cmd.find_first_not_of(" \t") );
		i = cmd.find_first_of(" \t");
		if (i==string::npos)
			protoRevStr = cmd;
		else
			protoRevStr = cmd.substr(0, i);
		cmd.erase(0, i);

		Logger::Locked(logger)->Info << "Command: " << commandStr << ", " << protoRevStr << endl;

		int protoRev;

		try
		{
			protoRev = boost::lexical_cast<int>(protoRevStr.c_str());
		}
		catch (boost::bad_lexical_cast ex)
		{
			try
			{
				stringstream ss;
				ss << std::hex << protoRevStr;
				ss >> protoRevStr;
			}
			catch (...)
			{
				result = "Cannot Parse Protocol Revision from: " + protoRevStr;
				return result;
			}
		}

		if (channels.find(protoRev)==channels.end())
		{
			stringstream ss;
			ss << "Invalid Protocol Revision: 0x" << std::hex << protoRev;
			result = ss.str();
		}
		else
		{
			ChannelPtr channel = channels[protoRev];

			//bool success = true;

			if (commandStr.compare(CMD_REQUEST)==0)
			{
				channel->lock();
				ActiveChannelRequest(protoRev);
				channel->unlock();

				stringstream ss;
				ss << "Received Active Request for Channel 0x" << std::hex << protoRev;
				ss << ". Channel now has " << currentRequests[protoRev] << " current request(s)";
				result = ss.str();
			}
			else if (commandStr.compare(CMD_CANCELREQUEST)==0)
			{
				channel->lock();
				CancelChannelRequest(protoRev);
				channel->unlock();

				stringstream ss;
				ss << "Received Cancel Request for Channel 0x" << std::hex << protoRev;
				ss << ". Channel now has " << currentRequests[protoRev] << " current request(s)";
				result = ss.str();
			}
			else if (commandStr.compare(CMD_GETSTATE)==0)
			{
				stringstream ss;
				ss << "Channel 0x" << std::hex << protoRev << std::dec << " has " << currentRequests[protoRev] << " current request(s)";
				result = ss.str();
			}
			else
			{
				//success = false;
				stringstream ss;
				ss << "Unknown command received: " << commandStr;
				result = ss.str();
			}
		}
	}
	catch (std::exception& ex)
	{
		Logger::Locked(logger)->Error << "Exception occurred while processing command: " << typeid(ex).name() << ", " << ex.what() << endl;
	}
	catch (...)
	{
		Logger::Locked(logger)->Error << "Caught unknown exception while processing command: " << endl;
	}

	Logger::Locked(logger)->Info << result << endl;
	return result;
}

void ConnectionManagerProgram::ChannelRequestEventHandler(EventTransceiver& sender, const IEventBase& receivedEvent)
{
	Logger::Locked(logger)->Info << "Received Request Event" << endl;

	const ChannelRequestEvent* evtPtr = NULL;

	try
	{
		evtPtr = static_cast<const ChannelRequestEvent*>(&receivedEvent);
	}
	catch(std::exception& ex)
	{
		Logger::Locked(logger)->Error << "Error making Request Event pointer" << endl;
		Logger::Locked(logger)->Error << "Caught exception " << typeid(ex).name() << " '" << ex.what() << "' while processing Request Event" << endl;
	}

	// Fire off event handling in different thread
	if (evtPtr)
		boost::thread(boost::bind(&ConnectionManagerProgram::ChannelRequestEventHandler_Asynch, this, *evtPtr));
	else
		Logger::Locked(logger)->Error << "Got null pointer when handling Request Event" << endl;
}

void ConnectionManagerProgram::ChannelRequestEventHandler_Asynch(const ChannelRequestEvent& receivedEvent)
{
	byte protoRev = receivedEvent.GetProtocolRevision();

	if (channels.find(protoRev)==channels.end())
	{
		Logger::Locked(logger)->Error << "Unrecognized Protocol Revision : " << protoRev << endl;
		return;
	}

	channels[protoRev]->lock();

	try
	{
		if (receivedEvent.IsActiveRequest())
			ActiveChannelRequest(protoRev);
		else if (receivedEvent.IsCancelRequest())
			CancelChannelRequest(protoRev);
	}
	catch(std::exception& ex)
	{
		Logger::Locked(logger)->Error << "Error handling Request Event" << endl;
		Logger::Locked(logger)->Error << "Caught exception " << typeid(ex).name() << " '" << ex.what() << "' while processing Request Event" << endl;
	}

	channels[protoRev]->unlock();

}

void ConnectionManagerProgram::ActiveChannelRequest(int protocolRevision)
{
	currentRequests[protocolRevision]++;
	Logger::Locked(logger)->Info << "Active Connection Requests For 0x" << std::hex << (int) protocolRevision << " = " << currentRequests[protocolRevision] << endl;
	//logger->Info << "Active Connection Requests For 0x" << std::hex << (int) protocolRevision << " = " << currentRequests[protocolRevision] << endl;

	// If this is the first request for this channel, turn it on, otherwise, we don't need to do anything
	if (currentRequests[protocolRevision]==1)
	{
		channels[protocolRevision]->ChannelOn();

		// If the channel is BGAN do not fire the channel state event.
		// This will be done by the "FireSignalStrengthEvent" method when the signal strength
		// is above the <minimumSignalStrength> (this is currently hardcoded at 59).
		if (protocolRevision != ConnectionManagerChannelSettings::BGAN) {
			FireChannelStateEvent(protocolRevision, true);
		}

//PBW...
//		Logger::Locked(logger)->Info << "Minimum Signal Strength: " <<
//				std::dec << channels[protocolRevision]->GetChannelSettings().GetMinimumSignalStrength() << endl;
	}
}

void ConnectionManagerProgram::CancelChannelRequest(int protocolRevision)
{
	currentRequests[protocolRevision]--;
	Logger::Locked(logger)->Info << "Active Connection Requests For 0x" << std::hex << (int) protocolRevision << " = " << currentRequests[protocolRevision] << endl;

	// If there are no longer any requests outstanding for this channel, turn it off, otherwise, we don't need to do anything
	if (currentRequests[protocolRevision]==0)
	{
		channels[protocolRevision]->ChannelOff();
		FireChannelStateEvent(protocolRevision, false);
	}
}

void ConnectionManagerProgram::SignalStrengthHandler(ConnectionManagerChannel& sender, int signalStrength)
{
	FireSignalStrengthEvent(sender.GetProtocolRevision(), signalStrength);
}

void ConnectionManagerProgram::FireChannelStateEvent(int channel, bool state)
{

	// Only fire the event if not BGAN or if BGAN when turning off the channel.
	// The BGAN channel will only be turned on if the signal strength is above the minimum.
	if (channel == ConnectionManagerChannelSettings::BGAN) {
		if (state == true) {
			return;
		}
	}

	ChannelStateEvent event(settings.GetChannelStateEvent(), broker);
	event.SetProtocolRevision(channel);
	event.SetChannelState(state);

	logger->Info << "Firing Channel State Event!" << endl;
	TransmitEvent(event);
	logger->Info << "Fired Channel State Event!" << endl;
}

void ConnectionManagerProgram::FireSignalStrengthEvent(int channel, int strength)
{

	if (channel == ConnectionManagerChannelSettings::BGAN) {

	//TODO: this needs to be a configuration value (<minimumSignalStrength) in "ConnectionManager-config.xml".
		if (strength < 59) {	// 59 if the minimum BGAN signal strength that will
								// support TCP communications.
			return;
		}
	}

	ChannelStateEvent event(settings.GetChannelStateEvent(), broker);
	event.SetProtocolRevision(channel);
	event.SetChannelState(true);
	event.SetSignalStrength(strength);

	logger->Info << "Firing Signal Strength Event!" << endl;
	TransmitEvent(event);
	logger->Info << "Fired Signal Strength Event!" << endl;
}


}
}
