//------------------------------------------------------------------------------
// <copyright file="PPPHostProgram.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>
//   Program entry point for PPPHost program
// </summary>
//
// <owner>Mike Cookson</owner>
//------------------------------------------------------------------------------

#include <iostream>
#include "LqrSMACIdl.h"
#include "EventPropertyValue.h"
#include "EventTransceiver.h"
#include "LqrSMACObjectBroker.h"
#include "LqrSMACIObjectBroker-inline.h"
#include "LqrSMACObjectBroker-inline.h"
#include "LqrSMACGetErrorDescription.h"
#include "LqrSMACProcessTree.h"
#include "LqrSMACStdOutLogger.h"
#include "LqrSMACSemaphore.h"
#include "PayloadManagerClient.h"
#include "PayloadMessage.h"
#include "PPPHostProgram.h"
#include "PPPHost.h"
#include "PPPHostSettings.h"
#include "SCPExecutor.h"
#include "SCPExecutorSettings.h"
#include "nsextend.h"
#include <errno.h>
#include <time.h>
#include "DataSetEvent.h"
#include <boost/filesystem.hpp>

using namespace std;
using namespace CORBA;
using namespace boost;
using namespace boost::filesystem;
using namespace LiquidRobotics::EventLib;
using namespace LiquidRobotics::PayloadInterface;
using namespace LiquidRobotics::PPPHostLib;
using namespace LiquidRobotics::SMACFramework;

PPPHostProgram::PPPHostProgram(IObjectBroker& _broker, const PPPHostProgramSettings& _settings, Logger::Ptr _logger)
: EventTransceiver(_settings.GetEventTransceiverSettings(), _broker, *_logger)
, broker(_broker)
, logger(_logger)
, settings(_settings)
, waitingForGPS(_settings.GetWaitForValidGPS())
{
}

PPPHostProgram::~PPPHostProgram()
{
}

void PPPHostProgram::Initialize(Object_ptr _pServer)
{
	const EventSettings& gpsSettings = settings.GPSEventSettings();

	if (!gpsSettings.Empty() || !waitingForGPS)
	{
		RegisterEventClassFactory(gpsSettings.GetType(), new EventTransceiver::TEventClassFactory<GPSSampleEvent>());
		InformSignalType& gpsSig = SignalForEventName(gpsSettings.GetName());
		connectionToGPSEvent.reset( new ScopedConnection(gpsSig.connect(boost::bind(&PPPHostProgram::GPSSampleHandler, this, _1, _2))) );
	}
	else
		Logger::Locked(logger)->Info << "GPS settings are empty, so the class factory is not being registered and the event is not being trapped" << endl;

	const EventSettings& nfaSettings = settings.NewFileAvailableEventSettings();

	if (!nfaSettings.Empty() && !settings.NewFileStorageRoot().empty())
	{
		RegisterEventClassFactory(nfaSettings.GetType(), new EventTransceiver::TEventClassFactory<DataSetEvent>());
		InformSignalType& nfaSig = SignalForEventName(nfaSettings.GetName());
		connectionToNewFileAvailableEvent.reset( new ScopedConnection(nfaSig.connect(boost::bind(&PPPHostProgram::NewFileAvailableHandler, this, _1, _2))) );
	}
	else
		Logger::Locked(logger)->Info << "New File Available settings are empty or the storage root is empty, so the class factory is not being registered and the event is not being trapped" << endl;

	EventTransceiver::Initialize(_pServer);

	if (!waitingForGPS)
	{
		Logger::Locked(logger)->Info << "Not configured to wait for GPS, so SCP Executor starts immediately" << endl;
		scpX.reset(new SCPExecutor(settings.GetSCPExecutorSettings(), logger));
		scpX->Initialize();
	}
}

int PPPHostProgram::Main(int argc, char* argv[])
{
	int retcode = 0;
	try
	{
		PPPHostProgramSettings settings;
		settings.Load("PPPHost-config.xml");

		const string serverName = settings.GetServerName();

		Logger::Ptr logger;
		logger.reset(new StdOutLogger(serverName.c_str(), 1, Logger::LT_DEBUG));

		ObjectBroker broker(true);
		broker.Initialize(argc, argv);

		PPPHostProgram hostProg(broker, settings, logger);

		PortableServer::ServantBase_var theServer = 0;
		theServer = new ::POA_LiquidRobotics::SMAC::IPingableCommandableWithEvents_tie<PPPHostProgram>(&hostProg);

		PayloadManagerClient pmClient(settings.GetPMClientSettings(), broker, hostProg);
		Object_var serverRef = broker.AdvertizeInstance(theServer.in(), serverName.c_str());

		hostProg.Initialize(serverRef.in());
		pmClient.Register(serverRef.in());

		broker.ListenForRequests();
	}
	catch(CORBA::Exception& ex)
	{
		cerr << "Caught " << DecodeCORBAException(ex) << endl;
		retcode = 1;
	}
	catch(std::exception& ex)
	{
		cerr << "Caught " << typeid(ex).name() << ": " << ex.what() << endl;
		retcode = 1;
	}

	return retcode;

}

::CORBA::Double PPPHostProgram::Ping()
{
	stringstream msg;
	msg << "PPPHostProgram::Ping: ";

	::CORBA::Double result;
	timeval tv;
	int status = gettimeofday(&tv, NULL);
	int errSave = errno;

	if (!status)
	{
		result = tv.tv_usec;
		result /= 1000000.0;
		result += tv.tv_sec;
	}
	else
	{
		msg << "(gettimeofday returned error #" << errSave << " '" << GetErrorDescription(errSave) << "') ";
		result = 0;
	}

	Logger::Locked logger2(logger);
	logger2->Info.precision(15);
	logger2->Info << msg.str() << result << endl;

	return result;
}

void PPPHostProgram::SendCommand(const ::CORBA::Any& command, const char* requester, const char* transactionID, ::CORBA::Any_OUT_arg result)
{
	using namespace LiquidRobotics::SMAC;

	try
	{
		EventPropertyValue commandEPV(command, broker);
		const EventPropertyValue::StorageType stype = commandEPV.GetStorageType();
		string cmdStr;

		switch(stype)
		{
		case EventPropertyValue::ST_STRING:

			cmdStr = commandEPV.GetString();
			break;

		default:
			{
				stringstream msg;
				msg << "Unsupported property type ";

				commandEPV.Describe(msg);

				Logger::Locked(logger)->Error <<msg.str() << endl;
				ThrowCORBAException2<BadParameter>("command", msg.str());
				break;
			}
		}

		if (boost::iequals(cmdStr, "CALLNOW"))
		{
			Logger::Ptr log = logger;
			SCPExecutor::Ptr scpX0;

			if (waitingForGPS)
			{
				Logger::Lock logLock(*log);
				log->Info << "Call postponed until GPS is available" << endl;
			}
			else if (scpX0 = scpX, scpX0)
			{
				scpX0->QueueConnect();

				Logger::Lock logLock(*log);
				log->Info << "Call-in has been scheduled" << endl;
			}
			else
			{
				Logger::Lock logLock(*log);
				log->Error << "Cannot schedule call-in since SCP executor is missing (NotReady)" << endl;

				ThrowCORBAException<NotReady>("SCP executor is missing!");
			}

			result.ptr() = new Any();

			EventPropertyValue resultEVP(broker);
			resultEVP.SetString("OK");
			resultEVP.CopyOut(*result.ptr());
		}
		else
		{
			stringstream msg;
			msg << "Unknown command '" << cmdStr << "'";

			Logger::Locked(logger)->Error << msg.str() << endl;
			ThrowCORBAException2<BadParameter>("command", msg.str());
		}
	}
	catch(CORBA::Exception& corbaEx)
	{
		throw;
	}
	catch(std::exception& ex)
	{
		stringstream msg;
		msg << "Caught " << typeid(ex).name() << " '" << ex.what() << "' while processing SendCommand";

		Logger::Ptr log = logger;

		if (log)
		{
			Logger::Lock logLock(*log);
			Logger::Locked(logger)->Error << msg.str() << endl;
		}

		ThrowCORBAException2<BadParameter>("command", msg.str());
	}
}

void PPPHostProgram::GPSSampleHandler(EventTransceiver& sender, const IEventBase& receivedEvent)
{
	if (waitingForGPS)
	{
		const EventSettings& gpsSettings = settings.GPSEventSettings();
		const GPSSampleEvent& gpsEvent =
			EventTransceiver::UpcastEventRef<GPSSampleEvent>(receivedEvent, gpsSettings,
					"PPPHostProgram::GPSSampleHandler");

		if (gpsEvent.HasTime() && gpsEvent.HasGPS())
		{
			waitingForGPS = false;
			logger->Info << "GPSSampleHandler received valid GPS location and time, starting SCP Executor" << endl;
			scpX.reset(new SCPExecutor(settings.GetSCPExecutorSettings(), logger));
			scpX->Initialize();
		}
	}
}

void PPPHostProgram::NewFileAvailableHandler(EventTransceiver& sender, const IEventBase& receivedEvent)
{
	const EventSettings& nfaSettings = settings.NewFileAvailableEventSettings();
	const DataSetEvent& nfaEvent = EventTransceiver::UpcastEventRef<DataSetEvent>(receivedEvent, nfaSettings, "PPPHostProgram::NewFileAvailableHandler");
	const string& storageRoot = settings.NewFileStorageRoot();

	if (is_directory(storageRoot))
	{
		EventPropertyValue::UInt8 _boardID, _taskID;
		nfaEvent.GetGeneratorAddress(_boardID, _taskID);

		PayloadMessage::BoardAddress boardAddress = MakeBoardAddress(_boardID, _taskID);

		path storagePath = path(storageRoot) / path(utos(boardAddress, 16, 4));
		string storagePathStr = storagePath.string();

		bool storagePathStrOK = is_directory(storagePathStr);

		if (!storagePathStrOK)
		{
			create_directory(storagePathStr);
			storagePathStrOK = true;
		}

		if (storagePathStrOK)
		{
//TODO: add code here to use the original filename...
			string suggestedName;
			if (settings.UseOriginalFilename()) {
				suggestedName = nfaEvent.GetOriginalFileName (storagePathStr);
			} else {
				suggestedName = nfaEvent.SuggestSaveFileName(storagePathStr);
			}

			Logger::Locked(logger)->Info << "Storing incoming event data as " << suggestedName << endl;
			nfaEvent.SaveContentToFile(suggestedName);
		}
		else
			Logger::Locked(logger)->Warning << "Storage path '" << storagePathStr << "' is not available, new file available event not handled" << endl;
	}
	else
		Logger::Locked(logger)->Warning << "Storage root directory '" << storageRoot << "' does not exist, new file available event not handled" << endl;
}
