//------------------------------------------------------------------------------
// <copyright file="DataRecorder.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>
// 	Data Recorder from serial port to an output file
// </summary>
//
// <owner>Raj Jha</owner>
//------------------------------------------------------------------------------

#include "LqrSMACIdl.h"
#include "omniORB4/CORBA.h"
#include "LqrSMACCmdLineParser.h"
#include "EventPropertyValue.h"
#include "LqrSMACByteArray.h"
#include "LqrSMACGetErrorDescription.h"
#include "DataSetEvent.h"
#include "GPSSampleEvent.h"
#include "SendCommandCompleteEvent.h"
#include "TelemetryEventBase.h"
#include "TelemetrySettingsChangedEvent.h"
#include <cerrno>
#include <ctime>
#include <iostream>
#include <sstream>
#include <fstream>
#include <stdexcept>
#include <string>
#include <typeinfo>
#include <stdint.h>
#include <boost/algorithm/string.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/function.hpp>
#include <boost/thread.hpp>
#include "DataRecorderHandler.h"
#include "DataRecorder.h"

using namespace std;
using namespace boost::posix_time;
using namespace CORBA;
using namespace LiquidRobotics::EventLib;
using namespace LiquidRobotics::SMAC;

namespace LiquidRobotics {
namespace DataRecorder {

DataRecorder::DataRecorder(IObjectBroker& _broker, const DataRecorderSettings& _settings, Logger& _logger)
: EventTransceiver(_settings.GetEventTransceiverSettings(), _broker, _logger)
, broker(_broker)
, logger(_logger)
, settings(_settings)
, gpsEventName(_settings.GetGPSEventSettings().GetName())
, gpsEventType(_settings.GetGPSEventSettings().GetType())
, lat(0)
, lon(0)
, GPSvalid(false)
{
}

DataRecorder::~DataRecorder()
{
}

void DataRecorder::Initialize(Object_ptr _pServer, PayloadManagerClient::Ptr _pmClient)
{
	pmClient = _pmClient;
	RegisterEventClassFactory(gpsEventType, new EventTransceiver::TEventClassFactory<GPSSampleEvent>());

	InformSignalType& sig0 = SignalForEventName(gpsEventName);
	connectionToGPSEvent.reset( new ScopedConnection(sig0.connect(boost::bind(&DataRecorder::GPSSampleHandler, this, _1, _2))) );

	EventTransceiver::Initialize(_pServer);

	// Setup the serial data receiver and handler
	dataHandler.reset ( new DataRecorderHandler( settings, broker, logger ));
	dataHandler->Initialize();
}

::CORBA::Double DataRecorder::Ping()
{
	using namespace LiquidRobotics::SMACFramework;
	stringstream msg;
	msg << "DataRecorder::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::Lock logLock(logger);
	logger.Info.precision(15);
	logger.Info << msg.str() << result << endl;
	return result;
}

EnumerationData* DataRecorder::GetEnumerationData()
{
	EnumerationData_var result;
	result = new EnumerationData;
	return result._retn();
}

void DataRecorder::PowerOff(::LiquidRobotics::SMAC::PowerOffTimeType offDelay)
{
	logger.Info << "PowerOff offDelay=" << offDelay << endl;
}

void DataRecorder::PowerOn()
{
	logger.Info << "PowerOn" << endl;
}

BoardIDType DataRecorder::boardID()
{
	return pmClient->GetMyBoardID();
}

PayloadDescription* DataRecorder::description()
{
	Desc::CORBAEntity_var result = settings.GetPayloadManagerClientSettings().GetPayloadDescription().GetEntity();
	return result._retn();
}

EPayloadEnableState DataRecorder::enableState()
{
	return EPES_ENABLED;
}

PowerOffTimeType DataRecorder::requestOffTime()
{
	return (PowerOffTimeType) 0;
}

PowerOnTimeType DataRecorder::requestOnTime()
{
	return (PowerOnTimeType) 0;
}

ComponentVersion* DataRecorder::softwareVersion()
{
	ComponentVersion_var result;
	result = new ComponentVersion;
	result->versionString = CORBA::string_dup("1.0.1");
	result->majorVersion = 1;
	result->minorVersion = 0;
	result->subminorVersion = 1;

	return result._retn();
}

TaskIDType DataRecorder::taskID()
{
	return pmClient->GetMyTaskID();
}

SensorSample* DataRecorder::SampleNow(const Any& sampleParameter)
{
	using namespace LiquidRobotics::SMACFramework;

	ThrowCORBAException2<BadParameter>();
	return NULL;
}

struct SendCommandLocalData
{
	typedef LiquidRobotics::SMACFramework::ByteArray ByteArray;
	typedef LiquidRobotics::SMACFramework::CmdLineParser CmdLineParser;
	typedef LiquidRobotics::SMACFramework::CmdLineParser::Ptr CmdLineParserPtr;
	typedef boost::shared_ptr<EventSettings> EventSettingsPtr;
	typedef boost::shared_ptr<DataSetEvent> DataSetEventPtr;
	typedef boost::shared_ptr<TelemetryEventBase> TelemetryEventBasePtr;

	string cmd;
	CmdLineParserPtr parser;
	string requester;
	string transactionID;
	EventSettingsPtr evtID;
	DataSetEventPtr evt;
	TelemetryEventBasePtr tevt;
	ByteArray content;
	string fieldName;
	string fieldType;
	string fieldValue;
};

typedef boost::shared_ptr<SendCommandLocalData> SendCommandLocalDataPtr;

void DataRecorder::SendCommand(const ::CORBA::Any& command, const char* requester, const char* transactionID, ::CORBA::Any_OUT_arg result)
{
	using namespace LiquidRobotics::SMACFramework;
	typedef SendCommandLocalData SCLD;

	SendCommandLocalDataPtr s;
	s.reset(new SendCommandLocalData);

	const char* cmd0;

	if (!(command >>= cmd0))
	{
		logger.Error << "Incoming command type is not a string" << endl;
		s.reset();
		ThrowCORBAException2<BadParameter>();
	}

	s->cmd = cmd0;

	logger.Info << "DataRecorder::SendCommand command='" << s->cmd << "' requester='" << requester << "' transactionID='" << transactionID << "'";

	try
	{
		s->parser.reset(new CmdLineParser(s->cmd));
	}
	catch(std::exception& ex)
	{
		logger.Info << endl;
		logger.Error << "DataRecorder::SendCommand caught " << typeid(ex).name() << ": " << ex.what() << endl;
		s.reset();
		ThrowCORBAException2<BadParameter>();
	}

	const CmdLineParser::StringArray& tokens = s->parser->GetTokens();

	if (tokens.size() == 0)
	{
		logger.Info << " -- nothing to execute!" << endl;
		s.reset();
		ThrowCORBAException2<BadParameter>();
	}

	logger.Info << " " << tokens.size() << " tokens:";

	for (CmdLineParser::StringArray::size_type idx = 0; idx < tokens.size(); ++idx)
		logger.Info << " " << (idx+1) << ") '" << tokens[idx] << "'";
	logger.Info << endl;

	string token0 = tokens[0];
	boost::trim(token0);

	string myResponse;

	if (boost::iequals(token0, "ON"))
	{
		int status = dataHandler->PowerOn();
		if (status)
			myResponse = "Power ON error";
		else
			myResponse = "Power ON done";
	}
	else if (boost::iequals(token0, "OFF"))
	{
		int status = dataHandler->PowerOff();
		if (status)
			myResponse = "Power OFF error";
		else
			myResponse = "Power OFF done";
	}
	else if (boost::iequals(token0, "PAUSE"))
	{
		dataHandler->Pause();
		myResponse = "Recording Paused";
	}
	else if (boost::iequals(token0, "RESUME"))
	{
		dataHandler->Resume();
		myResponse = "Recording Resumed";
	}
	else if (boost::iequals(token0, "OUTPUT"))
	{
		if (tokens.size() < 2)
		{
			logger.Info << " missing filename parameter" << endl;
			s.reset();
			ThrowCORBAException2<BadParameter>();
		}

		string token1 = tokens[1];
		boost::trim(token1);
		if ( dataHandler->SetRecordingFilename(token1) )
			myResponse = "Recording file set to " + token1;
		else
			myResponse = "Unable to set output file " + token1;
	}
	else if (boost::iequals(token0, "START"))
	{
		int status = dataHandler->Start();
		if (status)
			myResponse = "Start: Power ON error";
		else
			myResponse = "Recording Started";
	}
	else if (boost::iequals(token0, "STOP"))
	{
		int status = dataHandler->Stop();
		if (status)
			myResponse = "Stop: Power OFF error";
		else
			myResponse = "Recorder Stopped and Off";
	}
	else if (boost::iequals(token0, "SETPORT"))
	{
		if (tokens.size() < 2)
		{
			logger.Info << " missing setport parameter" << endl;
			s.reset();
			ThrowCORBAException2<BadParameter>();
		}
		myResponse = dataHandler->ChangePortSettings(tokens);
	}

	else if (boost::iequals(token0, "SHUTDOWN"))
	{
		logger.Info << " (queuing shutdown)";
		broker.QueueShutdown();
	}

	time_t t = time(NULL);
	struct tm tmBuf;
	gmtime_r(&t, &tmBuf);
	char charBuf[200];
	asctime_r(&tmBuf, charBuf);
	charBuf[strcspn(charBuf, "\r\n")] = '\0';

	stringstream stm;
	stm << "Time=" << charBuf << "  " << myResponse << endl;

	const string resultStr = stm.str();
	logger.Info << " returning result='" << resultStr << "'" << endl;

	result.ptr() = new Any;
	*result.ptr() <<= resultStr.c_str();
	return;
}

void DataRecorder::SendCommand_Asynch(const string requester, const string transID, float seconds, int times)
{
	logger.Debug << "Executing SendCommand_Asynch requester='" << requester << "' transID='" << transID << "' timeout=" << seconds << " times=" << times;
	if (seconds > 0) logger.Debug << endl, usleep((unsigned long) floor(1000000.0*seconds));

	for (int idx = 0; idx < times; ++idx)
	{
		SendCommandCompleteEvent evt0(settings.GetCommandCompleteEventSettings(), broker);
		evt0.Complete(false);
		evt0.Requester(requester);
		evt0.TransactionID(transID);

		stringstream msg;
		msg << "extra result line #" << (idx+1) << endl;

		EventPropertyValue result0(broker);
		result0.SetString(msg.str());

		Any anyResult0;
		result0.CopyOut(anyResult0);
		evt0.Result(anyResult0);

		TransmitEvent(evt0);
	}

	SendCommandCompleteEvent evt(settings.GetCommandCompleteEventSettings(), broker);
	evt.Complete(true);
	evt.Requester(requester);
	evt.TransactionID(transID);

	EventPropertyValue result(broker);
	result.SetString("Here is asynchronous result");
	Any anyResult;
	result.CopyOut(anyResult);
	evt.Result(anyResult);

	logger.Debug << ": producing event ";
	evt.Describe(logger.Info);
	logger.Debug << endl;

	TransmitEvent(evt);
}

void DataRecorder::SetSamplePending(const Any& sampleParameter)
{
	using namespace LiquidRobotics::SMACFramework;

	ThrowCORBAException2<BadParameter>();
}

ComponentVersion* DataRecorder::DataRecorder::hardwareVersion()
{
	ComponentVersion_var result;
	result = new ComponentVersion;
	result->versionString = CORBA::string_dup("no version");
	result->majorVersion = 0;
	result->minorVersion = 0;
	result->subminorVersion = 0;

	return result._retn();
}

SensorSampleIntervalType DataRecorder::sensorSampleInterval()
{
	return (SensorSampleIntervalType) 0;
}

void DataRecorder::sensorSampleInterval(SensorSampleIntervalType _v)
{
}

Boolean DataRecorder::samplePending()
{
	return (::CORBA::Boolean) 0;
}

void DataRecorder::NoteGPSSample(Long dateTimeUTC, Boolean validFix, Double latitude, Double longitude)
{
	struct tm tmBuf;
	gmtime_r(&dateTimeUTC, &tmBuf);
	char charBuf[200];
	asctime_r(&tmBuf, charBuf);
	charBuf[strcspn(charBuf, "\r\n")] = '\0';
	logger.Info << "DataRecorder::NoteGPSSample dateTimeUTC=" << dateTimeUTC << " (" << charBuf << ") validFix=" << validFix;

	if (validFix)
	{
		logger.Info << " latitude=";
		logger.Info.setf((ios_base::fmtflags) 0, ios::floatfield);
		logger.Info.precision(15);
		logger.Info << latitude;
		logger.Info << " longitude=";
		logger.Info.precision(15);
		logger.Info << longitude;
		logger.Info.setf(ios::fixed, ios::floatfield);
	}

	logger.Info << endl;
}

void DataRecorder::GPSSampleHandler(EventTransceiver& sender, const IEventBase& receivedEvent)
{
	const GPSSampleEvent& sample = EventTransceiver::UpcastEventRef<GPSSampleEvent>(receivedEvent, gpsEventType, "DataRecorder::GPSSampleHandler");

	if (sample.HasTime() && sample.HasGPS())
	{
		lat = sample.GetLatitude();
		lon = sample.GetLongitude();
		dataHandler->ProcessGPS( sample.GetTime().tv_sec, lat, lon );
		if (!GPSvalid)
		{
//				logger.Info << "(GPSSampleHandler) Received : Valid" << endl;
			GPSvalid = true;
			logger.Info << "(GPSSampleHandler) Received ";
			receivedEvent.Describe(logger.Info);
			logger.Info << endl;
		}
	}
	else
	{
		if (GPSvalid)
		{
			logger.Info << "(GPSSampleHandler) Received Invalid data" << endl;
			GPSvalid = false;
		}
	}
}

} // namespace LiquidRobotics
} // namespace DataRecorder
