//------------------------------------------------------------------------------------------------------
// <copyright file="DataRecorderHandler.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. This file has the serial data receive and process.
// </summary>
//
// <owner>Raj Jha</owner>
//------------------------------------------------------------------------------------------------------

#include <unistd.h>  /* UNIX standard function definitions */
#include <fcntl.h>   /* File control definitions */
#include <errno.h>   /* Error number definitions */
#include <termios.h> /* terminal control definitions */
#include <stdlib.h>
#include <iostream>
#include <sstream>
#include <fstream>
#include "DataRecorderSettings.h"
#include "DataRecorder.h"
#include "LqrSMACLogger.h"
#include "LqrSMACSerialListener.h"
#include "LqrSMACTimerCollection.h"
#include "LqrSMACUptime.h"
#include <boost/algorithm/string.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include "DataRecorderHandler.h"

using namespace std;
using namespace boost::posix_time;
using namespace LiquidRobotics::SMACFramework;
using namespace CORBA;

namespace LiquidRobotics {
namespace DataRecorder {

DataRecorderHandler::DataRecorderHandler(const DataRecorderSettings& _settings, IObjectBroker& _broker, Logger& _logger)
: settings(_settings)
, broker(_broker)
, logger(_logger)
, storageFileName(_settings.GetOutFileName())
, initialized(false)
, recordingEnabled(false)
, GPSdateTime(0)
, lat (100.0)
, lon (200.0)
{
}

DataRecorderHandler::~DataRecorderHandler()
{
}

void DataRecorderHandler::Initialize()
{
	PowerOff(); // power = OFF at startup
	Resume(); // ready to start recording when ON

	if (!initialized)
	{
		try
		{
			if (SerialStartup())
				initialized = true;
		}
		catch(...)
		{
			Logger::Locked(logger)->Error << "Initialize(): caught exception, unable to setup serial " << endl;
			initialized = false;
			throw;
		}
/*		try
		{
			// open serial port
			serialPort = settings.GetSerialPortSettings().CreatePort(logger);
			Logger::Locked(logger)->Info << " starting serial listener" << endl;

			serialListener.reset(new SerialListener(*serialPort, settings.GetSerialListenerSettings()));
			serialListenerConnection =
				serialListener->Subscribe(boost::bind(&DataRecorderHandler::ReceivedSerialData, this, _1, _2));
			serialListener->Initialize();
			slInit = true;

			recordingEnabled = true;

			initialized = true;
		}
		catch(exception& ex)
		{
			Logger::Locked(logger)->Error << "DataRecorderHandler::Initialize(): caught exception "
										  << typeid(ex).name() << " '" << ex.what() << "'" << endl;

			if (slInit)
				serialListener->Shutdown();

			serialListenerConnection.reset();
			serialListener.reset();
			serialPort.reset();

			throw;
		} */
	}
	else
		Logger::Locked(logger)->Warning << "Initialize(): already initialized" << endl;
}

void DataRecorderHandler::ReceivedSerialData(SerialListener& sender, const ByteArray& receivedData)
{
	Logger::Locked(logger)->Info << "Received " << receivedData.size() << " bytes from serial port" << endl;

	// save data to output file
	if (!IsPaused())
		switch(settings.GetOutputFormat())
		{
		case 2:		WriteToFile_Format2(receivedData);			break;
		default:	WriteToFile(receivedData);					break;
		}
}

void DataRecorderHandler::WriteToFile(const ByteArray& receivedData)
{
	ofstream outFile ( storageFileName.c_str(), ios::out | ios::app | ios::binary );

	if ( !outFile.is_open() || !outFile.good() )
	{
		outFile.close();
		Logger::Locked(logger)->Error << "Unable to open outFile: " << storageFileName << endl;
		return;
	}

	if (receivedData.size() > 0)
	{
		outFile.write( (char *) &receivedData.at(0), (int) receivedData.size() ); // append the received data to the existing records in the file
		outFile.close(); // data recorded, close the file
	}
}

#pragma pack(push,1)

struct Format2Header
{
	Format2Header(uint16_t _numBytes)
	: numBytes(_numBytes)
	{
		gettimeofday(&timeStamp, NULL);
	}

	timeval timeStamp;
	uint16_t numBytes;
};

#pragma pack(pop)

void DataRecorderHandler::WriteToFile_Format2(const ByteArray& receivedData)
{
	ofstream outFile ( storageFileName.c_str(), ios::out | ios::app | ios::binary );

	if ( !outFile.is_open() || !outFile.good() )
	{
		outFile.close();
		Logger::Locked(logger)->Error << "Unable to open outFile: " << storageFileName << endl;
		return;
	}

	if (receivedData.size() > 0)
	{
		Format2Header header(receivedData.size());
		outFile.write((char*) &header, (int) sizeof(header));
		outFile.write( (char *) &receivedData.at(0), (int) receivedData.size() ); // append the received data to the existing records in the file
		outFile.close(); // data recorded, close the file
	}
}

int DataRecorderHandler::PowerOn()
{
	if (settings.GetPowerOnVerb().empty()) return 0;

	int status = system( settings.GetPowerOnVerb().c_str() );
	Logger::Locked(logger)->Info << "Power ON returned status " << status << endl;
	return status;
}

int DataRecorderHandler::PowerOff()
{
	if (settings.GetPowerOffVerb().empty()) return 0;

	int status = system( settings.GetPowerOffVerb().c_str() );
	Logger::Locked(logger)->Info << "Power OFF returned status " << status << endl;
	return status;
}

void DataRecorderHandler::Pause()
{
	Logger::Locked(logger)->Info << "Recording Paused" << endl;
	RLock lock(mtx_paused);
	recordingEnabled = false;
}

void DataRecorderHandler::Resume()
{
	Logger::Locked(logger)->Info << "Recording Resumed" << endl;
	RLock lock(mtx_paused);
	recordingEnabled = true;
}

bool DataRecorderHandler::IsPaused()
{
	RLock lock(mtx_paused);
	return (!recordingEnabled);
}

bool DataRecorderHandler::SetRecordingFilename(const string& fname)
{
	if (fname.empty())
	{
		return false;
	}
	else if (storageFileName.compare( fname ) == 0) // is existing file name
	{
		return true;
	}
	else
	{
		ofstream outFile ( fname.c_str(), ios::out | ios::app | ios::binary ); // check if this can be a valid out file
		if ( !outFile.is_open() || !outFile.good() )
		{
			outFile.close();
			return false;
		}
		else
		{
			Logger::Locked(logger)->Info << "DataRecorderHandler: setting output file to " << fname << endl;
			outFile.close();
			storageFileName = fname;
/*
			ofstream saveFname( "OUTFILENAME.TXT" ); // file for saving outfilename for retrieval at next power on
			if ( saveFname.is_open() AND saveFname.good() )
			{
				saveFname << StorageFileName;
			}
			saveFname.close();
*/
			return true;
		}
	}
}

int DataRecorderHandler::Start()
{
	bool wasPaused = IsPaused();
	Resume();

	int status = PowerOn();
	if (status && wasPaused) // error while Power ON, set original pause/resume state
		Pause();

	return status;
}

int DataRecorderHandler::Stop()
{
	Pause();
	int status = PowerOff();
	return status;
}

string DataRecorderHandler::ChangePortSettings(const CmdLineParser::StringArray& tokens)
{
	bool status = true;

	SerialPortSettings sp = settings.GetSerialPortSettings();

	if (tokens.size() > 1)
		try			{ sp.SetBaudRate( sp.BaudRate_FromString(tokens[1]) ); }
		catch(...)	{ logger.Info << " -- error baudrate parameter!" << endl; status = false; }
	if (!status) return ("Error: BaudRate parameter");

	if (tokens.size() > 2)
		try			{ sp.SetParity( sp.Parity_FromString(tokens[2]) ); }
		catch(...)	{ logger.Info << " -- error parity parameter!" << endl; status = false; }
	if (!status) return ("Error: Parity parameter");

	if (tokens.size() > 3)
		try			{ sp.SetCharacterSize( sp.CharacterSize_FromString(tokens[3]) ); }
		catch(...)	{ logger.Info << " -- error charactersize parameter!" << endl; status = false; }
	if (!status) return ("Error: CharacterSize parameter");

	if (tokens.size() > 4)
		try			{ sp.SetStopBits( sp.StopBits_FromString(tokens[4]) ); }
		catch(...)	{ logger.Info << " -- error stopbits parameter!" << endl; status = false; }
	if (!status) return ("Error: StopBits parameter");

	if (initialized)
	{
		SerialShutdown();
		initialized = false;
	}

	SerialPortSettings orig = settings.GetSerialPortSettings();
	settings.SetSerialPortSettings(sp);

	try
	{
		if (SerialStartup())
			initialized = true;
		else
			status = false;
	}
	catch(exception& ex)
	{
		settings.SetSerialPortSettings(orig);
		initialized = false;
		status = false;
	}
	if (!status)
		return ("Error: setting up serial port and listener");
	else
		return ("serial port settings changed");
}

void DataRecorderHandler::ProcessGPS(::CORBA::Long dateTimeUTC, ::CORBA::Double latitude, ::CORBA::Double longitude)
{
	GPSdateTime = dateTimeUTC;
	lat = latitude;
	lon = longitude;
}

bool DataRecorderHandler::SerialStartup()
{
	bool status = true;
	bool slInit = false;

	try
	{
		Logger::Locked(logger)->Info << "creating serial port" << endl;
		serialPort = settings.GetSerialPortSettings().CreatePort(logger);

		Logger::Locked(logger)->Info << "starting serial listener" << endl;
		serialListener.reset(new SerialListener(*serialPort, settings.GetSerialListenerSettings()));
		serialListenerConnection =
			serialListener->Subscribe(boost::bind(&DataRecorderHandler::ReceivedSerialData, this, _1, _2));
		serialListener->Initialize();
		slInit = true;
	}
	catch(exception& ex)
	{
		Logger::Locked(logger)->Error << "SerialStartup(): caught exception "
									  << typeid(ex).name() << " '" << ex.what() << "'" << endl;
		if (slInit)
			serialListener->Shutdown();
		serialListenerConnection.reset();
		serialListener.reset();
		serialPort.reset();
		status = false;

		throw;
	}

	return status;
}

void DataRecorderHandler::SerialShutdown()
{
	Logger::Locked(logger)->Info << "shutting down serial listener and port" << endl;
	serialListener->Shutdown();
	serialListenerConnection.reset();
	serialListener.reset();
	serialPort.reset();
}

void DataRecorderHandler::Shutdown()
{
	Logger::Locked(logger)->Info << "DataRecorderHandler: shutting down" << endl;

	if (initialized)
	{
		SerialShutdown();
		initialized = false;
	}

	PowerOff();
}


} // namespace LiquidRobotics
} // namespace DataRecorder
