//------------------------------------------------------------------------------
// <copyright file="VirtualRelayMessageManager.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 VirtualRelayMessageManager class, which manages outgoing
//  and incoming messages and encryption/decryption
// </summary>
//
// <owner>Arash Ushani</owner>
//------------------------------------------------------------------------------

#include "VirtualRelayMessageManager.h"
#include "VirtualRelayCryptoProcessor.h"
#include "LqrSMACLogger.h"
#include "FCCMessage.h"
#include "nsextend.h"
#include <boost/serialization/vector.hpp>
#include <iostream>
#include <fstream>

using namespace std;
using namespace boost;
using namespace LiquidRobotics::CryptoLib;
using namespace LiquidRobotics::PayloadInterface;
using namespace LiquidRobotics::SMACFramework;

namespace LiquidRobotics {
namespace VirtualRelay {

VirtualRelayMessageManager::VirtualRelayMessageManager(VirtualRelayMessageManagerSettings& _settings, Logger&  _logger)
: logger(_logger)
, settings(_settings)
, cryptoProcessor(_logger)
{
	// Load any old messages now
	LoadMessages();

	vehicleUniqueID = 0xFFFFFFFF;
	signalStrength = 0xFF;
	telemetryCount = 0;

	incomingMessageSignalPtr.reset(new IncomingMessageSignalType());
}

VirtualRelayMessageManager::~VirtualRelayMessageManager()
{
	Logger::Locked(logger)->Debug << Describe() << ": Message Manager Destructor" << endl;
}

void VirtualRelayMessageManager::LoadMessages()
{
	using namespace boost::archive;
	using namespace boost::serialization;

	try
	{
		string filename = settings.GetBackupFilename();

		if (filename.length()>0)
		{
			Logger::Locked(logger)->Info << Describe() << ": Loading saved messages..." << endl;

			ifstream backupFile(settings.GetBackupFilename().c_str(), ifstream::in);

			if (backupFile)
			{
				std::vector<UInt8Vector> messages;
				xml_iarchive ia(backupFile);
				ia >> make_nvp("rootlevel", messages);

				for (std::vector<std::vector<unsigned char> >::iterator it = messages.begin(); it != messages.end(); it++)
					QueueOutgoingLevel2Message(*it);
			}
		}
	}
	catch (std::exception& ex)
	{
		Logger::Locked(logger)->Error << Describe() << ": Exception loading old messages from hard disk." << endl;
		Logger::Locked(logger)->Error << Describe() << ": Caught exception " << typeid(ex).name() << "\n" << ex.what() << endl;
	}
}

void VirtualRelayMessageManager::DumpMessages()
{
	using namespace boost::archive;
	using namespace boost::serialization;

	try
	{
		Logger::Locked(logger)->Info << Describe() << ": Dumping messages to hard disk." << endl;
		string filename = settings.GetBackupFilename();

		if (filename.length()>0)
		{
			ofstream backupFile(settings.GetBackupFilename().c_str(), ifstream::out);

			if (backupFile)
			{
				std::vector<UInt8Vector> messages;

				while(!outgoingQueue.empty())
				{
					messages.push_back(outgoingQueue.top());
					outgoingQueue.pop();
				}

				xml_oarchive oa(backupFile);
				oa << make_nvp("rootlevel", messages);
			}
		}
	}
	catch (std::exception& ex)
	{
		Logger::Locked(logger)->Error << Describe() << ": Exception dumping messages." << endl;
		Logger::Locked(logger)->Error << Describe() << ": Caught exception " << typeid(ex).name() << "\n" << ex.what() << endl;
	}
}

void VirtualRelayMessageManager::SetSessionID(boost::uint32_t _sessionID)
{
	sessionID = _sessionID;
}

void VirtualRelayMessageManager::SetSignalStrength(boost::uint8_t strength)
{
	signalStrength = strength;
}

void VirtualRelayMessageManager::SetUniqueID(boost::uint32_t uniqueID)
{
	vehicleUniqueID = uniqueID;
}

bool VirtualRelayMessageManager::RequestKeepConnectionActive()
{
	return !lastSessionEmpty || !outgoingQueue.empty();
}

bool VirtualRelayMessageManager::TelemetryTimeout()
{
	return telemetryCount > settings.GetTelemetryTimeoutCount();
}

void VirtualRelayMessageManager::ResetTelemetryCount()
{
	telemetryCount = 0;
}

void VirtualRelayMessageManager::GetOutgoingPacket(boost::uint16_t socketBufferSize, UInt8Vector& outgoingPacket)
{
	// Always send a relay status, and then send queued messages as space allows

	VR_RELAYSTATUS relayStatus;
	VirtualRelayMessage::GetRelayStatusMessage(&relayStatus, true, false, false, false, cryptoProcessor.IsEncrypted(), signalStrength);

	UInt8Vector relayStatusMessage((unsigned char*)&relayStatus, ( (unsigned char*) &relayStatus )+ sizeof(VR_RELAYSTATUS) );

	UInt8Vector level2RelayStatus = cryptoProcessor.OutgoingLevel2Message(relayStatusMessage);
	UInt8Vector level3RelayStatus = VirtualRelayMessage::GetOutgoingRelayMessage(level2RelayStatus, settings.GetProtocolRevision(), vehicleUniqueID, sessionID, socketBufferSize);

	outgoingPacket.insert(outgoingPacket.end(), level3RelayStatus.begin(), level3RelayStatus.end());
	lastSessionEmpty = true;

	while (!outgoingQueue.empty())
	{
		UInt8Vector msgToSend = outgoingQueue.top();
		UInt8Vector cryptoOutput = cryptoProcessor.OutgoingLevel2Message(msgToSend);
		UInt8Vector msg = VirtualRelayMessage::GetOutgoingRelayMessage(cryptoOutput, settings.GetProtocolRevision(), vehicleUniqueID, sessionID, socketBufferSize);

		if (outgoingPacket.size()+msg.size()>socketBufferSize-sizeof(VR_RELAYSTATUS))
		{
			Logger::Locked(logger)->Info << Describe() << ": Send buffer is full. Will send remaining messages in next session." << endl;
			break;
		}

		outgoingPacket.insert(outgoingPacket.end(), msg.begin(), msg.end());
		outgoingQueue.pop();

		lastSessionEmpty = false;
	}

	// Keep track of the last relay status CRC
	lastRelayStatusCRC = relayStatus.crc16;

	// Be ready to accept a 5 byte channel status from WGMS
	pendingChannelStatus = true;
}

VirtualRelayMessageManager::ScopedConnectionPtr VirtualRelayMessageManager::SubscribeToIncomingMessages(IncomingMessageSignalType::slot_type slot)
{
	ScopedConnectionPtr incomingMessageConnectionPtr;
	incomingMessageConnectionPtr.reset(new ScopedConnection(incomingMessageSignalPtr->connect(slot)));
	return incomingMessageConnectionPtr;
}

void VirtualRelayMessageManager::ProcessIncomingPacket(UInt8Vector& incomingMsg)
{
	try
	{
		unsigned int start = 0;

		// Always interpret the first five bytes as a channel status
		if (pendingChannelStatus)
		{
			PVR_CHANNELSTATUS channelStatus = (PVR_CHANNELSTATUS) &incomingMsg[start];
			Logger::Locked(logger)->Info << Describe() << ": Received Channel Status. Relay Status Size = " << channelStatus->relayStatusSize;
			Logger::Locked(logger)->Info << ", CRC = 0x" << std::hex << channelStatus->relayStatusCRC << std::dec;
			Logger::Locked(logger)->Info << ", Disposition = " << (channelStatus->disposition==1 ? "Secure":"Not Secure") << endl;

			if (channelStatus->relayStatusSize==sizeof(VR_RELAYSTATUS) && lastRelayStatusCRC==channelStatus->relayStatusCRC)
			{
				Logger::Locked(logger)->Info << Describe() << ": Relay Status Length and CRC Match!" << endl;

				// We can reset the telemetry count here
				ResetTelemetryCount();
			}
			else
			{
				if (channelStatus->relayStatusSize!=sizeof(VR_RELAYSTATUS))
					Logger::Locked(logger)->Alert << Describe() << ": Returned Relay Status size does not match" << endl;

				if (lastRelayStatusCRC!=channelStatus->relayStatusCRC)
					Logger::Locked(logger)->Alert << Describe() << ": Channel Status CRC does not match Relay Status CRC (0x" << std::hex << lastRelayStatusCRC << ")" << std::dec << endl;
			}

			pendingChannelStatus = false;
			start = sizeof(VR_CHANNELSTATUS);
		}

		// Deal with the rest of the message (ie, all the commands)
		while (incomingMsg.size()>start+sizeof(VR_SHORETOSHIPHEADER)+1)
		{
			PVR_SHORETOSHIPHEADER messageHeader = (PVR_SHORETOSHIPHEADER) &incomingMsg[start];
			int msgSize = bswap16(messageHeader->messageLength) + 3;

			if (msgSize+start > incomingMsg.size())
			{
				Logger::Locked(logger)->Error << Describe() << ": Error: The message size is too large: " << msgSize << " bytes" << endl;
				break;
			}

			UInt8Vector message(incomingMsg.begin()+start, incomingMsg.begin() + start + msgSize);
			start += msgSize;

			lastSessionEmpty = false;

			try
			{
				ProcessMessage(message);
			}
			catch (std::exception& ex)
			{
				Logger::Locked(logger)->Error << Describe() << ": Exception when handling message." << endl;
				Logger::Locked(logger)->Error << Describe() << ": Caught exception " << typeid(ex).name() << "\n" << ex.what() << endl;
			}
		}
	}
	catch (std::exception& ex)
	{
		Logger::Locked(logger)->Error << Describe() << ": Exception when handling incoming relay messages." << endl;
		Logger::Locked(logger)->Error << Describe() << ": Caught exception " << typeid(ex).name() << "\n" << ex.what() << endl;
	}
}

void VirtualRelayMessageManager::ProcessMessage(UInt8Vector& level3Message)
{
	UInt8Vector level2Message(level3Message.begin()+sizeof(VR_SHORETOSHIPHEADER), level3Message.end());
	UInt8Vector message = cryptoProcessor.IncomingLevel2Message(level2Message);

	boost::uint16_t command = message[8] + (message[9]<<8);

	switch (command)
	{
		case FCCMessage::CMD_SECURE_ON:
		{
			Logger::Locked(logger)->Info << Describe() << ": Received a WGMS Secure On Command." << endl;

			// Change to Secure On 2 (type 0xA51E)

			message[8] = 0x1E;
			// message[9] = 0xA5; // Don't need this because it already is 0xA5

			message.pop_back();
			message.pop_back();
			VirtualRelayMessage::AppendCRC(message);

			// Insert 0x0001 for ICD protocol
			message.insert(message.begin(), 0);
			message.insert(message.begin(), 1);

			(*incomingMessageSignalPtr)(*this, message);
			break;
		}

		case FCCMessage::CMD_SECURE_AUTHENTICATE:
		{
			Logger::Locked(logger)->Info << Describe() << ": Received a WGMS Secure Authenticate Command." << endl;

			UInt8Vector result = cryptoProcessor.VerifySecureAuthenticate(message);
			QueueOutgoingLevel2Message(result);

			// No FCC Message to send

			break;
		}

		case FCCMessage::CMD_SECURE_OFF:
		{
			Logger::Locked(logger)->Info << Describe() << ": Received Secure Off Command" << endl;

			VirtualRelayMessage::VR_ACK_BASIC secureOffAck;
			secureOffAck.type = FCCMessage::ACK_BASIC;
			secureOffAck.commandID = (message[4]<<24) + (message[3]<<16) + (message[2]<<8) + message[1];
			secureOffAck.responseSize = 0;

			UInt8Vector outgoingMessage;
			outgoingMessage.insert(outgoingMessage.end(), (char*)&secureOffAck, ((char*)&secureOffAck) + sizeof(VirtualRelayMessage::VR_ACK_BASIC));

			VirtualRelayMessage::AppendCRC(outgoingMessage);
			QueueOutgoingLevel2Message(outgoingMessage);

			// No FCC Message to send

			break;
		}

		default:	// A normal (clear-text or encrypted) command
		{
			// Insert 0x0001 for ICD protocol
			message.insert(message.begin(), 0);
			message.insert(message.begin(), 1);

			(*incomingMessageSignalPtr)(*this, message);

			break;
		}
	}
}

// Requests an immediate connection by returning a flag
bool VirtualRelayMessageManager::HandleOutgoingMessage(UInt8Vector& level2Message)
{
	bool immediateConnectionRequested = false;

	UInt8Vector msg = level2Message;

	switch(level2Message[0])
	{
		case FCCMessage::SND_TELEMETRY:
		Logger::Locked(logger)->Info << Describe() << ": Received Telemetry to be sent to shore" << endl;
		telemetryCount++;
		immediateConnectionRequested = settings.GetTelemetryInterrupt();
		break;

		case FCCMessage::ACK_SECURE_ON_2:
		Logger::Locked(logger)->Info << Describe() << ": Received Secure On Ack 2" << endl;
		cryptoProcessor.ProcessSecureOnAck2(msg);
		immediateConnectionRequested = settings.GetAckInterrupt();
		break;

		default:
		Logger::Locked(logger)->Info << Describe() << ": Received Ack to be sent to shore" << endl;
		immediateConnectionRequested = settings.GetAckInterrupt();
		break;
	}

	QueueOutgoingLevel2Message(msg);

	return immediateConnectionRequested;

}

VirtualRelayPacketComparision::Priority VirtualRelayMessageManager::HighestPriority()
{
	UInt8Vector msgToSend = outgoingQueue.top();

	VirtualRelayPacketComparision comp;

	return comp.GetPriority(msgToSend);
}

VirtualRelayMessageManager::UInt8Vector VirtualRelayMessageManager::PopMessage()
{
	UInt8Vector msgToSend = outgoingQueue.top();

	Logger::Locked(logger)->Info << Describe() << ": Popping next message (" << msgToSend.size() << " bytes)" << endl;

	outgoingQueue.pop();
	// No need to send through crypto processor
	return msgToSend;
}


void VirtualRelayMessageManager::QueueOutgoingLevel2Message(char* msg, int len)
{
	UInt8Vector toSend(msg, msg+len);
	QueueOutgoingLevel2Message(toSend);
}

void VirtualRelayMessageManager::QueueOutgoingLevel2Message(UInt8Vector& msg)
{
	outgoingQueue.push(msg);
}

std::string VirtualRelayMessageManager::Describe()
{
	std::stringstream ss;
	ss << "VR-MsgManager ";
	ss << std::dec << settings.GetProtocolRevision();
	ss << " Address 0x" << (sessionID>>16);

	return ss.str();
}

}
}
