//-------------------------------------------------------------------------------------
// <copyright file="SV3NetInterface.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 SV3NetInterface, which implements a web services interface client
//  for each sensor server.
// </summary>
//
// <owner>Mike Cookson</owner>
//-------------------------------------------------------------------------------------

#include "SV3NetInterface.h"
#include "nsextend.h"
#include <fstream>
#include <sstream>

using namespace std;
using namespace LiquidRobotics::SMACFramework;

namespace LiquidRobotics {
namespace PayloadInterface {

SV3NetInterface::SV3NetInterface(const SV3NetInterfaceSettings& _settings, Logger& _logger)
: remoteCommandSubscriber(remoteCommandSignal, firingMutex)
, shutdownSubscriber(shutdownSignal, firingMutex)
, commstatusSubscriber(commstatusSignal, firingMutex)
, gpsSubscriber(gpsSignal, firingMutex)
, settings(_settings)
, logger(_logger)
, nextConformingID(0xA0000000)
, goodComm(false)
, gpsRequired(false)
{
}

SV3NetInterface::~SV3NetInterface()
{
	Shutdown();
}

void SV3NetInterface::Initialize()
{
	regLoop.reset(new RegistrationLoop(*this));
	rlAllRegSubPtr = regLoop->allRegSignalSub.Subscribe(boost::bind(&SV3NetInterface::OnRLAllRegistered, this, _1));
	pollingLoop.reset(new PollingLoop(*this));
	plStatusSubPtr = pollingLoop->pollingStatusSignalSub.Subscribe(boost::bind(&SV3NetInterface::OnPLStatus, this, _1, _2));
	regLoop->Start();
	pollingLoop->Start();
	pollingLoop->NoteGPSRequired(gpsRequired);
}

void SV3NetInterface::Shutdown()
{
	// release subscriptions
	if (regLoop && rlAllRegSubPtr)
		regLoop->allRegSignalSub.Release(rlAllRegSubPtr);
	if (pollingLoop && plStatusSubPtr)
		pollingLoop->pollingStatusSignalSub.Release(plStatusSubPtr);

	// stop polling loop and registration loop
	if (pollingLoop) {
		pollingLoop->Stop();
		pollingLoop.reset();
	}

	if (regLoop) {
		regLoop->Stop();
		regLoop.reset();
	}

	// making a copy of the channels array
	ILockableConceptLock lock(mutex);
	ChannelArray copy;
	copy.reserve(channels.size());
	for (ChannelArray::iterator it = channels.begin(); it != channels.end(); ++it)
		copy.push_back(*it);
	channels.resize(0);
	lock.unlock();

	// release all subscriptions
	for (ChannelArray::iterator it = copy.begin(); it != copy.end(); ++it)
		if ((*it)->channel)
		{
			SV3NetChannel& channel = *(*it)->channel;
			channel.remoteCommandSubscriber.Release((*it)->commandSub);
			channel.gpsSignalSubscriber.Release((*it)->gpsSub);
			channel.shutdownSignalSubscriber.Release((*it)->shutdownSub);
		}

	// shut down each instance
	for (ChannelArray::iterator it = copy.begin(); it != copy.end(); ++it)
		(*it)->channel->Shutdown();
}

void SV3NetInterface::NoteClient(uint16_t boardAddress, const string& serverName, const string& description, const string& descriptionJSON)
{
	ChannelInfoPtr ciptr = FindChannelInfo(boardAddress, false);

	if (!ciptr)
	{
		ciptr.reset(new ChannelInfo);
		ciptr->channel.reset(new SV3NetChannel(boardAddress, serverName, description, descriptionJSON, settings, logger));
		ciptr->commandSub = ciptr->channel->remoteCommandSubscriber.Subscribe(boost::bind(&SV3NetInterface::OnRemoteCommand, this, _1, _2, _3, _4, _5));
		ciptr->gpsSub = ciptr->channel->gpsSignalSubscriber.Subscribe(boost::bind(&SV3NetInterface::OnHostGPS, this, _1, _2, _3, _4));
		ciptr->shutdownSub = ciptr->channel->shutdownSignalSubscriber.Subscribe(boost::bind(&SV3NetInterface::OnShutdown, this, _1));

		try
		{
			ciptr->channel->Initialize();
		}
		catch(...)
		{
			ciptr.reset();
			throw;
		}

		ILockableConceptLock lock(mutex);
		channels.push_back(ciptr);
	}
}

void SV3NetInterface::OnRemoteCommand(SV3NetChannel& sender, uint16_t boardAddress, const string& transactionReference, bool returnFile, const string& cmdLine)
{
	ILockableConceptLock lock(firingMutex);
	const uint32_t conformingReference = TransactionReferenceToConformingReference(transactionReference);
	remoteCommandSignal(*this, boardAddress, transactionReference, conformingReference, returnFile, cmdLine);
}

void SV3NetInterface::OnShutdown(SV3NetChannel& sender)
{
	shutdownSignal(*this, sender);
}

void SV3NetInterface::OnPLStatus(PollingLoop& sender, bool goodPoll)
{
	if (goodPoll)
		Logger::Locked(logger)->Debug << "Polling loop reports good poll" << endl;
	else {
		Logger::Locked(logger)->Info << "Polling loop reports bad poll" << endl;
		if (regLoop)
			regLoop->NoteCommLoss();
		if (goodComm) {
			goodComm = false;
			commstatusSignal(*this, false);
		}
	}
}
void SV3NetInterface::OnRLAllRegistered(RegistrationLoop& sender)
{
	Logger::Locked(logger)->Info << "Registration loop reports all registered" << endl;
	if (pollingLoop)
		pollingLoop->NoteAllRegistered();
	if (!goodComm) {
		goodComm = true;
		commstatusSignal(*this, true);
	}
}

SV3NetInterface::ChannelInfoPtr SV3NetInterface::FindChannelInfo(uint16_t boardAddress, bool throwOnError)
{
	ILockableConceptLock lock(mutex);
	SV3NetInterface::ChannelInfoPtr result;

	for (ChannelArray::const_iterator ci = channels.begin(); !result && (ci != channels.end()); ++ci)
		if ((*ci)->channel->GetBoardAddress() == boardAddress)
			result = *ci;

	if (!result && throwOnError)
	{
		stringstream msg;
		msg << "No registered channel for board address " << boardAddress;
		throw invalid_argument(msg.str());
	}
	else
		return result;
}

SV3NetChannel::Ptr SV3NetInterface::FindChannel(uint16_t boardAddress)
{
	ILockableConceptLock lock(mutex);
	ChannelInfoPtr ciptr = FindChannelInfo(boardAddress);
	return ciptr->channel;
}

void SV3NetInterface::GetChannels(SV3NetChannel::PtrArray& result)
{
	ILockableConcept::Lock lock(mutex);
	result.clear();
	result.reserve(channels.size());

	for (ChannelArray::const_iterator ci = channels.begin(); ci != channels.end(); ++ci)
		result.push_back((*ci)->channel);
}

SV3NetInterface::ChannelInfo::~ChannelInfo()
{
	if (channel)
	{
		channel->remoteCommandSubscriber.Release(commandSub);
		channel->shutdownSignalSubscriber.Release(shutdownSub);
	}
}

uint32_t SV3NetInterface::TransactionReferenceToConformingReference(const string& transactionReference, bool addIfNotFound)
{
	ILockableConceptLock lock(localMutex);
	StringToUInt32Map::iterator it = mapTransactionIDToConformingID.find(transactionReference);
	uint32_t conforming = 0;

	if (it == mapTransactionIDToConformingID.end()) {
		if (addIfNotFound) {
			if ((conforming = ++nextConformingID) == 0)
				conforming = ++nextConformingID;
			mapConformingIDToTransactionID[conforming] = transactionReference;
			mapTransactionIDToConformingID[transactionReference] = conforming;
		}
		else {
			stringstream ss;
			ss << "(TransactionReferenceToConformingReference) Transaction reference (" << transactionReference << ") not found";
			throw invalid_argument(ss.str());
		}
	}
	else
		conforming = it->second;

	return conforming;
}

string SV3NetInterface::ConformingReferenceToTransactionReference(uint32_t conformingReference)
{
	ILockableConceptLock lock(localMutex);
	UInt32ToStringMap::iterator it = mapConformingIDToTransactionID.find(conformingReference);

	if (it == mapConformingIDToTransactionID.end()) {
		stringstream ss;
		ss << "(ConformingReferenceToTransactionReference) Conforming reference (0x" << ultos(conformingReference,16,8) << ") not found";
		throw invalid_argument(ss.str());
	}

	return it->second;
}

bool SV3NetInterface::RemoveTransactionReference(const string& transactionReference, bool throwOnError)
{
	ILockableConceptLock lock(localMutex);
	StringToUInt32Map::iterator it = mapTransactionIDToConformingID.find(transactionReference);

	if (it == mapTransactionIDToConformingID.end()) {
		if (throwOnError) {
			stringstream ss;
			ss << "(RemoveTransactionReference) Transaction reference (" << transactionReference << ") not found";
			throw invalid_argument(ss.str());
		}
		else
			return false;
	}

	UInt32ToStringMap::iterator it2 = mapConformingIDToTransactionID.find(it->second);
	if (it2 != mapConformingIDToTransactionID.end())
		mapConformingIDToTransactionID.erase(it2);
	mapTransactionIDToConformingID.erase(it);
	return true;
}

void SV3NetInterface::NoteGPSRequired(bool required)
{
	ILockableConceptLock lock(mutex);
	gpsRequired = required;
	PollingLoopPtr ptr = pollingLoop;
	if (ptr)
		ptr->NoteGPSRequired(required);
}

void SV3NetInterface::OnHostGPS(SV3NetChannel& sender, time_t timestamp, double latitude, double longitude)
{
	gpsSignal(*this, sender.GetBoardAddress(), timestamp, latitude, longitude);
}

}
}
