//-------------------------------------------------------------------------------------------------
// <copyright file="OneTimeCommandController.cpp" company="LiquidRobotics">
//  Copyright (c) Liquid Robotics Corporation, confidential and proprietary.  All rights reserved. 
// </copyright>
//
// <summary>
// </summary>
//
// <owner></owner>
//-------------------------------------------------------------------------------------------------

#include "OneTimeCommandController.h"
#include "LqrSMACCmdLineParser.h"
#include "LqrSMACGetErrorDescription.h"
#include "LqrSMACPipes.h"
#include "LqrSMACPrintfBuffer.h"
#include "nsextend.h"
#include <fstream>
#include <string>
#include <vector>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <boost/algorithm/string.hpp>

using namespace std;
using namespace LiquidRobotics::SMACFramework;
using namespace LiquidRobotics::PayloadInterface;
using namespace LiquidRobotics::PayloadInterface::FCCMessage;

typedef PayloadPortSlave Base;

namespace LiquidRobotics {
namespace OneTimeCommand {

OneTimeCommandController::OneTimeCommandController(const OneTimeCommandSettings& _settings, Logger& _logger, Semaphore& _runSem)
: Base(_settings.GetPortSettings(), _logger)
, config(_settings)
, runSem(_runSem)
, halt(false)
, shutdownCompleted(false)
, running(true)
{
}

OneTimeCommandController::~OneTimeCommandController()
{
}

// Overriding this handler to catch the exit from the program
void OneTimeCommandController::Run(HDLCPort::Ptr pHDLC)
{
	const char* userNamePtr = getenv("LOGNAME");

	if (userNamePtr)
	{
		Logger::Locked(GetLogger())->Info << "Running kill command" << endl;

		stringstream cmdstm;
		cmdstm << "ps -o pid,fname -U " << userNamePtr << " | "
		   << "( while read a b; do if [ \"$b\" = 'PayloadI' -o \"$b\" = 'ReRun' ] ; then echo \"$a\"; fi ; done ) | "
		   <<  "xargs sudo kill -9";

		string cmd = cmdstm.str();
		Logger::Locked(GetLogger())->Info << "Running kill command: " << cmd << endl;
		int systemResult = system(cmd.c_str());
		Logger::Locked(GetLogger())->Info << "Kill command returned " << systemResult << endl;
		if (!systemResult) sleep(2);
	}
	else
		Logger::Locked(GetLogger())->Error << "LOGNAME not defined" << endl;

	PayloadPortSlave::Run(pHDLC);

	if (!shutdownCompleted)
		try 		{ HandleShutdown(); }
		catch(...) 	{ }
}

// Overriding this handler to halt the system when requested
PayloadMessage::Ptr OneTimeCommandController::WaitForMessage(HDLCPort::Ptr pHDLC)
{
	if (halt)
		HandleShutdown();

	return Base::WaitForMessage(pHDLC);
}

// This method is called whenever a message is received.  The return value contains the message to be sent
// in response (or send back a null pointer if no response should be made)
//
// Normally, duplicateHeader will be false; if duplicateHeader is true, the receiver should typically return
// a null pointer and the handler will retransmit the message that was previously sent.
PayloadMessage::Ptr OneTimeCommandController::OnMessageReceived(PayloadMessage::Ptr messageReceived, bool duplicateHeader)
{
	return Base::OnMessageReceived(messageReceived, duplicateHeader);
}

// This method is called whenever an forwarded or copied ICD message is received; the receiver should fill out
// either the ack or the nak response
void OneTimeCommandController::OnICDMessageReceived(PayloadMessage::Ptr messageReceived,
		boost::shared_ptr<AckContainerBase>& ack,
		boost::shared_ptr<NakContainer>& nak)
{
	Base::OnICDMessageReceived(messageReceived, ack, nak);
}

// This methnod is called whenever a power notification message is received; the user should fill out either
// the ack responses or the nak container
void OneTimeCommandController::OnPowerNotification(const PayloadProtocol::PYLD_POWER_NOTIFICATION& notification,
		boost::shared_ptr< CountedAckContainer<PayloadProtocol::ACK_PYLD_POWER_NOTIFICATION_RECORD> >& ack,
		boost::shared_ptr<NakContainer>& nak)
{
	ack.reset(new CountedAckContainer<PayloadProtocol::ACK_PYLD_POWER_NOTIFICATION_RECORD>(PayloadMessage::POWER_NOTIFICATION, 1, 1));
	(*ack)[0].address = MainTaskAddress();
	(*ack)[0].boardID = GetBoardID(MainTaskAddress());
	(*ack)[0].state = 1;
	(*ack)[0].delayOff = config.GetPowerOffDelay();
	(*ack)[0].delayOn = config.GetPowerOnDelay();

	if (config.GetShutdownVerb().length())
	{
		Logger::Locked(GetLogger())->Info << "Setting halt flag" << endl;
		halt = true;
	}
	else
		Logger::Locked(GetLogger())->Info << "Not actually halting since shutdown verb is empty" << endl;
}

// This method is called by the default OnMessageReceived implementation when an ID/enumeration request is
// received; the user should fill out the responses array for processing on return.
void OneTimeCommandController::OnRequestIDReceived(const PayloadProtocol::LRI_REQUEST_ID& idRequest,
		std::vector<PayloadProtocol::LRI_ID_PACKET>& responses)
{
	responses.resize(1);

	memset(&responses.at(0), '\0', sizeof(PayloadProtocol::LRI_ID_PACKET));

	responses[0].address = config.GetBoardAddress();

	const string::size_type descriptionSize = sizeof(responses[0].description);
	char* description = responses[0].description;
	strncpy(description, config.GetDescription().c_str(), descriptionSize);
	description[descriptionSize-1] = '\0';

	responses[0].deviceType = config.GetDeviceType();
	responses[0].firmwareMajor = config.GetFirmwareMajor();
	responses[0].firmwareMinor = config.GetFirmwareMinor();
	responses[0].firmwareRev = config.GetFirmwareRev();
	responses[0].pollingRate = config.GetPollingRate();
	responses[0].port = 0;

	const string::size_type serNumBufSize = sizeof(responses[0].serialNum);
	char* serialNum = responses[0].serialNum;
	memset(serialNum, ' ', serNumBufSize);
	const string& serNum = config.GetSerialNum();
	const string::size_type serNumLen = serNum.length();
	memcpy(serialNum, serNum.c_str(), min(serNumLen, serNumBufSize));

	responses[0].version = 1;

	if (config.GetTelemetryRequested())				responses[0].extraInfo |= PayloadProtocol::EXTRA_INFO_TELEMETRY;
	if (config.GetPowerNotificationRequested())		responses[0].extraInfo |= PayloadProtocol::EXTRA_INFO_POWER;
	if (config.GetEventsRequested())				responses[0].extraInfo |= PayloadProtocol::EXTRA_INFO_EVENTS;
	if (config.GetEchoedAcksRequested())			responses[0].extraInfo |= PayloadProtocol::EXTRA_INFO_ECHOED_ACKS;
}

// This method is called by the default OnMessageReceived implementation when a status request message is
// received; the user should fill out either the Nak structure or the Ack strcture
void OneTimeCommandController::OnStatusRequest(const PayloadProtocol::LRI_REQUEST_PIB_STATUS& statusRequest,
		boost::shared_ptr< CountedAckContainer<PayloadProtocol::LRI_PIB_STATUS> >& ack,
		boost::shared_ptr<NakContainer>& nak)
{
	LatLon fix;
	const bool validFix = (statusRequest.fixValid == 'Y');

	if (validFix)
	{
		fix = LatLon(statusRequest.latitude, statusRequest.longitude);
		lastFix = fix;

		Logger::Locked(GetLogger())->Info
			 << "Valid fix at "
			 << PrintfBuffer("%.4u/%.2u/%.2u %.2u:%.2u:%.2u UTC",
					 2000 + (int) statusRequest.timestamp.year,
					 statusRequest.timestamp.month,
					 statusRequest.timestamp.day,
					 statusRequest.timestamp.hour,
					 statusRequest.timestamp.minute,
					 statusRequest.timestamp.second)
			 << " at " << fix << endl;

		// set the time and date
		string cmd = config.GetDateVerbGMT(
				2000 + (int) statusRequest.timestamp.year,
				statusRequest.timestamp.month,
				statusRequest.timestamp.day,
				statusRequest.timestamp.hour,
				statusRequest.timestamp.minute,
				statusRequest.timestamp.second);

		if (cmd.length())
		{
			Logger::Locked(GetLogger())->Info << "cmd: " << cmd << endl;

			const int systemReturnCode = system(cmd.c_str());
			if (systemReturnCode);
		}
		else
			Logger::Locked(GetLogger())->Debug << "There is no configured verb to set the date/time" << endl;
	}
	else
		Logger::Locked(GetLogger())->Info << "Incoming fix is not valid" << endl;

	// -- enqueue response here --
	if (msgQByBoardID.Empty(config.GetBoardID()))
	{
		PayloadMessage::Ptr payloadMessage;
		payloadMessage.reset(new PayloadMessage(PayloadMessage::LRI_ACK, config.GetBoardAddress(),
				config.GetDestination(), 0, config.GetMessage()));

		Logger::Locked(GetLogger())->Info << "Enqueuing message " << *payloadMessage << endl;

		EnqueueMessage(payloadMessage);
	}

	// set up the response
	ack.reset(new CountedAckContainer<PayloadProtocol::LRI_PIB_STATUS>
					(PayloadMessage::REQUEST_PIB_STATUS, 1, 1));

	PayloadProtocol::LRI_PIB_STATUS& rec = (*ack)[0];
	memset(&rec, '\0', sizeof(rec));
	rec.version = 1;
}

// Returns true if there are messages queued to be read out by the Host, false if there are none; this method
// is used to set the "have messages" flag in all responses
bool OneTimeCommandController::HaveMessagesQueued(BoardAddress sourceAddress) const
{
	return Base::HaveMessagesQueued(sourceAddress);
}

// This method should return true unless intending to exit
bool OneTimeCommandController::Running() const
{
	return running && !halt && !shutdownCompleted;
}

// Returns true if the address is one that should be Nak'd, false if the address is unknown
bool OneTimeCommandController::SendNaksForAddress(BoardAddress destinationAddress) const
{
	return destinationAddress != 0xFFFF;
}

// Returns the board address of the main task
PayloadMessage::BoardAddress OneTimeCommandController::MainTaskAddress() const
{
	return config.GetBoardAddress();
}

void OneTimeCommandController::HandleShutdown()
{
	//ShutdownSystem();
	shutdownCompleted = true;
	sleep(3);
	runSem.Post();
}

void OneTimeCommandController::ShutdownSystem()
{
	Logger::Locked(GetLogger())->Info << "Halt action pending" << endl;

	string shutdownVerb = config.GetShutdownVerb();

	if (shutdownVerb.length())
	{
		Logger::Locked(GetLogger())->Info << "cmd: " << shutdownVerb << endl;
		const int systemReturnCode = system(shutdownVerb.c_str());
		if (systemReturnCode);
	}
	else
		Logger::Locked(GetLogger())->Info << "No defined shutdown verb" << endl;
}

OneTimeCommandController::Logger& OneTimeCommandController::GetLogger() const
{
	return Base::logger;
}

PayloadMessage::Ptr OneTimeCommandController::DequeueMessage(BoardAddress boardAddress)
{
	PayloadMessage::Ptr result = PayloadPortSlave::DequeueMessage(boardAddress);

	if (result)
		running = false;

	return result;
}

}
}
