//----------------------------------------------------------------------------------
// <copyright file="SV3NetInterface_RegistrationLoop.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 RegistrationLoop inner class, which implements
//  the payload registration mechanism, detection of communication loss and re-
//	registration.
// </summary>
//
// <owner>Mike Cookson</owner>
//----------------------------------------------------------------------------------

#include "SV3NetInterface.h"
#include "SV3NetInterfaceSettings.h"
#include "RegulusREST.h"
#include "nsextend.h"

using namespace std;
using namespace LiquidRobotics::SMACFramework;
using namespace LiquidRobotics::SV3RemotingLib;

namespace LiquidRobotics {
namespace PayloadInterface {

SV3NetInterface::RegistrationLoop::RegistrationLoop(SV3NetInterface& _net)
: allRegSignalSub(allRegSignal, firingMutex)
, net(_net)
, mutex(_net.mutex)
, wakeSem(0)
, commLost(true)
, threadExited(true)
{
}

bool SV3NetInterface::RegistrationLoop::IsCommunicating()
{
	ILockableConcept::Lock lock(mutex);
	return !commLost;
}

void SV3NetInterface::RegistrationLoop::NoteCommLoss()
{
	ILockableConcept::Lock lock(mutex);
	if (!commLost)
		Logger::Locked(net.logger)->Info << "RegLoop: Communication LOST to Host" << endl;

	MarkAllUnregistered();
	commLost = true;
	wakeSem.Post();
}

void SV3NetInterface::RegistrationLoop::NoteNewRegistration(ChannelInfo& ci)
{
	ILockableConcept::Lock lock(mutex);
	Logger::Locked(net.logger)->Info << "RegLoop: New payload change (0x" << utos(ci.channel->GetBoardAddress(),4,16) << ")" << endl;
	ci.channel->SetRegistered(false);
	newReg = true;
	wakeSem.Post();
}

void SV3NetInterface::RegistrationLoop::Start()
{
	if (threadExited) {
		ILockableConceptLock lock(mutex);
		Logger::Locked(net.logger)->Info << "RegLoop: Starting thread" << endl;
		wakeSem.SetCount(0);
		commLost = true;
		threadExited = false;
		threadp.reset(new boost::thread(boost::bind(&SV3NetInterface::RegistrationLoop::Loop, this)));
	}
	else
		Logger::Locked(net.logger)->Info << "RegLoop: Thread already started" << endl;
}

void SV3NetInterface::RegistrationLoop::Stop()
{
	if (!threadExited) {
		Logger::Locked(net.logger)->Info << "RegLoop: Stopping thread" << endl;
		threadExited = true;
		wakeSem.Post();
		threadp->join();
		ILockableConceptLock lock(mutex);
		threadp.reset();
		wakeSem.SetCount(0);
		commLost = true;
		threadExited = false;
		threadp.reset();
	}
	else
		Logger::Locked(net.logger)->Info << "RegLoop: Thread already stopped" << endl;
}

void SV3NetInterface::RegistrationLoop::Loop()
{
	Logger::Locked(net.logger)->Info << "RegLoop: Starting background loop" << endl;
	const unsigned timeoutSec = 15;
	bool purge = true;

	while(!threadExited) {

		try {

			newReg = false;

			if (RegisterAll(purge)) {
				purge = false;
				commLost = false;
				AnnounceAllRegistered();
			}

			// here I want to wait for events to be flagged and wait synchronously; then ...
			//
			// * exit immediately if the thread was terminated
			// * if new payloads are added or registrations are changed, wait for at least 15 seconds between additions
			// * wait for at least 15 seconds after comm loss
			bool haveNewReg = AnyUnregistered();
			bool haveCommLost = false;
			bool timedOut = false;
			bool first = true;

			while(!threadExited && ((!haveNewReg && !haveCommLost) || !timedOut)) {

				if (!first) {
					if (haveNewReg || haveCommLost) {
						Logger::Locked(net.logger)->Debug << "(RegLoop) Waiting for an event or " << timeoutSec << " sec" << endl;
						timedOut = !wakeSem.TimedWait(1000*timeoutSec);
					}
					else {
						Logger::Locked(net.logger)->Debug << "(RegLoop) Waiting for any new events" << endl;
						timedOut = false;
						wakeSem.Wait();
					}
				}

				first = false;

				{
					ILockableConceptLock lock(mutex);
					if (newReg)						{ haveNewReg = true; newReg = false; timedOut = false; }
					if (commLost && !haveCommLost)	{ purge = true; haveCommLost = true; timedOut = false; }
				}

			}

		}
		catch(exception& ex) {
			Logger::Locked(net.logger)->Warning << "(RegLoop) Caught " << typeid(ex).name() << ": " << ex.what() << endl;
			MarkAllUnregistered();
			purge = true;
			commLost = true;
			wakeSem.SetCount(0);
			if (!threadExited)
				do {
					Logger::Locked(net.logger)->Debug << "(RegLoop) Waiting for thread exit or " << timeoutSec << " sec" << endl;
				} while(!threadExited && wakeSem.TimedWait(1000*timeoutSec));
		}
	}

	Logger::Locked(net.logger)->Warning << "(RegLoop) Exiting thread" << endl;
}

bool SV3NetInterface::RegistrationLoop::AnyUnregistered()
{
	ILockableConceptLock lock(mutex);
	bool result = false;

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

	return result;
}

bool SV3NetInterface::RegistrationLoop::RegisterAll(bool purge)
{
	unsigned countRegistered = 0;
	typedef vector<SV3NetChannel::Ptr> LocalChannelArray;
	LocalChannelArray channelsToRegister;
	ILockableConceptLock lock(mutex);
	SV3NetChannel::Ptr firstChannel;

	for (ChannelArray::const_iterator ci = net.channels.begin(); ci != net.channels.end(); ++ci)
		if (!(*ci)->channel->GetRegistered())
			channelsToRegister.push_back((*ci)->channel);

	newReg = false;
	lock.unlock();

	for (LocalChannelArray::const_iterator ci = channelsToRegister.begin(); ci != channelsToRegister.end(); ++ci) {
		if (!firstChannel) firstChannel = *ci;
		(*ci)->RegisterPayload((ci+1) == channelsToRegister.end(), purge);
		purge = false;
		++countRegistered;
	}

	return countRegistered && !newReg;
}

void SV3NetInterface::RegistrationLoop::AnnounceAllRegistered()
{
	ILockableConceptLock lock(firingMutex);
	allRegSignal(*this);
}

void SV3NetInterface::RegistrationLoop::MarkAllUnregistered()
{
	ILockableConcept::Lock lock(mutex);

	for (ChannelArray::iterator it = net.channels.begin(); it != net.channels.end(); ++it)
		(*it)->channel->SetRegistered(false);
}

}
}
