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

#include "SV3NetChannel.h"
#include "SV3NetInterfaceSettings.h"
#include "RegulusREST.h"
#include "LqrSMACTempFile.h"
#include "LqrSMACUptime.h"
#include "nsextend.h"
#include <set>
#include <fstream>
#include <boost/algorithm/string.hpp>

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

const bool VERBOSE = true;

namespace LiquidRobotics {
namespace PayloadInterface {

SV3NetChannel::SV3NetChannel(uint16_t _boardAddress, const string& _serverName, const string& _description, const string& _descriptionJSON, const SV3NetInterfaceSettings& _settings, Logger& _logger)
: gpsSignalSubscriber(gpsSignal, firingMutex)
, remoteCommandSubscriber(remoteCommandSignal, firingMutex)
, shutdownSignalSubscriber(shutdownSignal, firingMutex)
, boardAddress(_boardAddress)
, serverName(_serverName)
, description(_description)
, descriptionJSON(_descriptionJSON)
, settings(_settings)
, logger(_logger)
, isRegistered(false)
, cacheTime(0.0)
{
}

SV3NetChannel::~SV3NetChannel()
{
}

void SV3NetChannel::Initialize()
{
	shutdownSem.SetCount(0);
	//queueHandlerThread.reset(new Thread(boost::bind(&SV3NetChannel::QueueHandler, this)));
}

void SV3NetChannel::Shutdown()
{
	shutdownSem.SetCount(1);
	//queueHandlerThread->join();
	//queueHandlerThread.reset();
}

uint16_t SV3NetChannel::GetBoardAddress() const
{
	return boardAddress;
}

const string& SV3NetChannel::GetServerName() const
{
	return serverName;
}

const string& SV3NetChannel::GetDescription() const
{
	return description;
}

const string& SV3NetChannel::GetDescriptionJSON() const
{
	return descriptionJSON;
}

bool SV3NetChannel::GetRegistered() const
{
	ILockableConceptLock lock(const_cast<LockableConceptMutex&>(mutex));
	return isRegistered;
}

void SV3NetChannel::SetRegistered(bool newValue)
{
	ILockableConceptLock lock(mutex);
	isRegistered = newValue;
}

void SV3NetChannel::DelayShutdown(long seconds)
{
	RegulusREST rest(GetHostAddress(), logger);
	rest.DelayShutdown(IDForBoardID(GetBoardAddress()), seconds);
}

void SV3NetChannel::InstallJAR(const string& fileName)
{
	RegulusREST rest(GetHostAddress(), logger);
	rest.InstallJar(IDForBoardID(GetBoardAddress()), fileName);
}

string SV3NetChannel::InvokeOnServer(const string& command)
{
	RegulusREST rest(GetHostAddress(), logger);
	return rest.RunScript(IDForBoardID(GetBoardAddress()), command);
}

void SV3NetChannel::NoteCommandOutput(const string& transactionReference, const string& outputData, bool commandComplete, bool succeeded)
{
	RegulusREST rest(GetHostAddress(), logger);
	rest.RespondToCommand(IDForBoardID(GetBoardAddress()), transactionReference, outputData, commandComplete, succeeded);
}

void SV3NetChannel::RegisterPayload(bool last, bool purge)
{
	ILockableConceptLock lock(mutex);

	RegulusREST::PayloadRegistration reg;
	reg.boardAddress = 0;
	reg.corbaName = serverName;
	reg.description = description;
	reg.reportingAddress = boardAddress;
	reg.tag = serverName;
	reg.hostID = GetHostID();

	TempFile tempFile;
	const char* startp = descriptionJSON.data();
	ByteArray content((const ByteArray::byte*) startp, (const ByteArray::byte*) (startp+descriptionJSON.length()));
	tempFile.Write(content);
	tempFile.Close();

	Logger::Locked(logger)->Info
			<< "SV3NetChannel 0x" << utos(boardAddress,16,4) << " " << serverName
			<< " Registering payload" << endl;
	lock.unlock();
	{
		RegulusREST rest(GetHostAddress(), logger);
		const string regContent = rest.GetPayloadRegistrationContent(tempFile.GetName(), reg);
		Logger::Locked(logger)->Debug
				<< "SV3NetChannel 0x" << utos(boardAddress,16,4) << " " << serverName
				<< " content: " << regContent << endl;
		rest.RegisterPayloadWithContent(IDForBoardID(GetBoardAddress()), regContent, purge ? SV3NetChannel::GetHostID() : "");
	}
	if (last)
	{
		RegulusREST rest(GetHostAddress(), logger);
		rest.RegistrationComplete(IDForBoardID(GetBoardAddress()));
	}

	SetRegistered(true);
}


void SV3NetChannel::SendFile(const string& fileName, const string& remoteName, bool legacy)
{
	RegulusREST rest(GetHostAddress(), logger);
	if (legacy)
		rest.SendFileToShoreLegacy(IDForBoardID(GetBoardAddress()), fileName, remoteName);
	else
		rest.SendFileToShore(IDForBoardID(GetBoardAddress()), fileName, remoteName);
}

void SV3NetChannel::SendOutputMessage(const ByteArray& messageContent)
{
	RegulusREST rest(GetHostAddress(), logger);
	rest.SendRawPacketToShore(IDForBoardID(GetBoardAddress()), messageContent);
}

void SV3NetChannel::SetPayloadAMPSSwitch(bool on)
{
	RegulusREST rest(GetHostAddress(), logger);
	rest.SetPayloadAMPSSwitch(IDForBoardID(GetBoardAddress()), on);
}

void SV3NetChannel::SetPayloadAMPSSwitch(const string& switchName, bool on)
{
	RegulusREST rest(GetHostAddress(), logger);
	rest.SetPayloadAMPSSwitch(IDForBoardID(GetBoardAddress()), switchName, on);
}

void SV3NetChannel::PollStatusFireEvents(PtrArray& pollList, bool requestGPS, bool requestShutdownNotification, long maxCommands, long timeoutMS)
{
	if (!pollList.size())
		throw invalid_argument("At least one polling ID must be specified (PollStatusFireEvents)");

	SV3NetChannel& thisChannel = *pollList[0];
	Logger& logger = thisChannel.logger;
	RegulusREST rest(thisChannel.GetHostAddress(), logger);
	RegulusREST::IDVector ids;
	set<uint16_t> boardIDSet;

	for (PtrArray::const_iterator ci = pollList.begin(); ci != pollList.end(); ++ci) {
		const uint16_t boardID = (*ci)->GetBoardAddress();
		if (boardIDSet.find(boardID) == boardIDSet.end()) {
			boardIDSet.insert(boardID);
			ids.push_back(SV3NetChannel::IDForBoardID(boardID));
		}
	}

	RegulusREST::GPSInfo gpsInfo;
	RegulusREST::ShutdownInfo shutdownInfo;
	RegulusREST::RemoteCommandVector rcv;
	bool serverTimeout = false;

	try {
		rest.Poll(ids, maxCommands ? &rcv : NULL, requestGPS ? &gpsInfo : NULL, requestShutdownNotification ? &shutdownInfo : NULL, timeoutMS, maxCommands);
	}
	catch(RegulusREST::ServerTimeoutOnRequest& ex) {
		Logger::Locked(logger)->Debug << "[PollStatusFireEvents] " << ex.what() << endl;
		serverTimeout = true;
	}

	if (!serverTimeout) {
		if (shutdownInfo.shutdown) {
			Logger::Locked(logger)->Info << "[PollStatusFireEvents] Got shutdown signal from Host" << endl;
			ILockableConceptLock lock(thisChannel.firingMutex);
			thisChannel.shutdownSignal(thisChannel);
		}

		if (gpsInfo.valid) {
			if (gpsInfo.error)
				Logger::Locked(logger)->Warning << "[PollStatusFireEvents] GPS error: " << gpsInfo.errorMessage << endl;
			else {
				Logger::Locked(logger)->Debug << "[PollStatusFireEvents] latitude=" << setprecision(9) << gpsInfo.latitude << " longitude=" << setprecision(10) << gpsInfo.longitude << " timestamp=" << setprecision(12) << gpsInfo.timestamp << endl;
				ILockableConceptLock lock(thisChannel.firingMutex);
				thisChannel.gpsSignal(thisChannel, gpsInfo.timestamp, gpsInfo.latitude, gpsInfo.longitude);
			}
		}

		for (RegulusREST::RemoteCommandVector::const_iterator ci = rcv.begin(); ci != rcv.end(); ++ci) {

			bool found = false;

			for (PtrArray::const_iterator cii = pollList.begin(); !found && (cii != pollList.end()); ++cii) {

				SV3NetChannel::Ptr channel = *cii;

				if (boost::equals(channel->GetServerName(), ci->serverTag)) {
					found = true;
					ILockableConceptLock firingLock(channel->firingMutex);
					channel->remoteCommandSignal(*channel, channel->GetBoardAddress(), ci->responseTag, ci->returnFile, ci->commandContent);
				}
			}
		}
	}
}

static string FileAsString(const string& fileName)
{
	string result;

	try
	{
		fstream input(fileName.c_str(), ios::binary | ios::in);
		char c;
		stringstream buffer;

		if (input.is_open())
			while(input.get(c).good())
				if ((c != '\r') && (c != '\n'))
					buffer.put(c);

		result = buffer.str();
	} catch(...) {
		result.clear();
	}

	return result;
}

static string InvocationAsString(const string& x)
{
	string result;
	FILE* fp = popen(x.c_str(), "r");

	if (fp) {

		char c;
		stringstream ss;

		while(c = fgetc(fp), !feof(fp) && !ferror(fp))
			if ((c != '\r') && (c != '\n'))
				ss.put(c);

		pclose(fp);
		result = ss.str();
	}

	return result;
}

string SV3NetChannel::GetHostID()
{
	// this should work on the target and pull out information about the particular CPU
	string result = FileAsString("/sys/class/product_id/part_number")+" "+FileAsString("/sys/class/product_id/model_name")+" "+FileAsString("/sys/class/product_id/serial_number");
	boost::trim(result);

	// if nothing was received, use the mac address of eth0
	if (result.length() == 0) {
		result = InvocationAsString("ip addr show eth0 | grep -Po 'link/ether \\K[0-f:]{17}'");
		boost::trim(result);
	}

	return result;
}

string SV3NetChannel::IDForBoardID(uint16_t boardAddress)
{
	string result = utos(boardAddress,16,4);
	return result;
}

void SV3NetChannel::TurnPayloadOff()
{
	RegulusREST rest(GetHostAddress(), logger);
	rest.TurnPayloadOff(IDForBoardID(boardAddress));
}

string SV3NetChannel::GetHostAddress()
{
	string result;
	const string verb = boost::trim_copy(settings.GetHostAddressVerb());

	if (verb.length()) {
		ILockableConceptLock lock(mutex);
		const double now = GetSystemUpTimeSecondsDouble();
		const double elapsed = now-cacheTime;
		if ((cacheTime >= 0) && (elapsed < 60) && cachedHostAddress.length())
			result = cachedHostAddress;
		else {
			FILE* fp = popen(verb.c_str(), "r");
			stringstream ss;
			char c;
			while(c = fgetc(fp), !feof(fp) && !ferror(fp)) ss.put(c);
			pclose(fp);
			cachedHostAddress = result = boost::trim_copy(ss.str());
			cacheTime = now;
		}
	}
	else
		result = boost::trim_copy(settings.GetHostAddress());

	return result;
}

}
}
