//-------------------------------------------------------------------------------------------------
// <copyright file="main.cpp" company="LiquidRobotics">
//  Copyright (c) Liquid Robotics Corporation, confidential and proprietary.  All rights reserved. 
// </copyright>
//
// <summary>
// 	Implementation of GPSPlayback program entry point
// </summary>
//
// <owner>Mike Cookson</owner>
//-------------------------------------------------------------------------------------------------
//
// Synopsis
//
//		GPSPlayback <playback-file> <port-link> {interval}
//
// Description
//
//		Plays back the list of sentences in the playback-file to a dynamically-
//		created pseudo port that is soft linked to port-link and produces sample
//		either at the interval specified (seconds) or at a rate that matches the
//		interval in the file.
//
//----------------------------------------------------------------------------------

#include <cerrno>
#include <ctime>
#include <iostream>
#include <fstream>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>
#include <pty.h>
#include <termios.h>
#include <sys/time.h>
#include <unistd.h>
#include <boost/filesystem.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/shared_ptr.hpp>
#include "LqrSMACGetErrorDescription.h"
#include "LqrSMACTZTools.h"
#include "nsextend.h"
#include "NMEAParser.h"

using namespace std;
using namespace boost;
using namespace boost::filesystem;
using namespace LiquidRobotics::SMACFramework;
using namespace LiquidRobotics::SMACFramework::TZTools;
using namespace LiquidRobotics::GPSListenerLib;

typedef vector<string> StringList;

inline void CheckStatus(int status, const string& operation);
string GetLine(istream& stm);
int Playback(const string& fileName, const string& portLink, bool overrideInterval, int interval = 0);
void ReadSentences(fstream& stm, StringList& sentences, time_t lastTime, time_t& newTime, bool overideInterval, int interval);

int main(int argc, char* argv[])
{
	int exitCode;

	tzset();

	try
	{
		if (argc == 3)
			exitCode = Playback(argv[1], argv[2], false);
		else if (argc == 4)
			exitCode = Playback(argv[1], argv[2], true, lexical_cast<int>(argv[3]));
		else
		{
			cerr << "Unexpected argument count:  " << argv[0] << " <playback-file> <port-link> {interval}" << endl;
			exitCode = 1;
		}
	}
	catch(std::exception& ex)
	{
		cerr << "Caught exception " << typeid(ex).name() << " '" << ex.what() << "'" << endl;
		exitCode = 1;
	}

	return exitCode;
}

void CheckStatus(int status, const string& operation)
{
	if (status == -1)
	{
		const int errno_save = errno;

		stringstream msg;
		msg << operation << " failed with error#" << errno_save << " '" << GetErrorDescription(errno_save) << "'";

		throw runtime_error(msg.str());
	}
}

class PlaybackContext
{
public:

	PlaybackContext(int _outputFD, fstream& _stm, bool _overrideInterval, int _interval)
	: outputFD(_outputFD)
	, stm(_stm)
	, numSentences(0)
	, overrideInterval(_overrideInterval)
	, interval(_interval)
	, lastTime(0)
	{
	}

	void RunFile()
	{
		stm.seekg(0, ios_base::end);
		streampos fileSize = stm.tellg();
		stm.seekg(0);

		ByteArray buffer;
		buffer.resize(fileSize);

		if (!fileSize)
			throw runtime_error("Input file is empty");

		stm.read((char*) &buffer.front(), buffer.size());

		typedef boost::signals2::scoped_connection ScopedConnection;
		typedef boost::shared_ptr<ScopedConnection> ScopedConnectionPtr;

		ScopedConnectionPtr conn = parser.SubscribeSentenceEvents(boost::bind(&PlaybackContext::ParsedSentenceHandler, this, _1, _2, _3));
		//conn.reset(new ScopedConnection(parser.hereIsParsedSentence_Signal.connect(boost::bind(&PlaybackContext::ParsedSentenceHandler, this, _1, _2, _3))));

		while(true)
		{
			const int sentences0 = numSentences;

			parser.Feed(buffer);

			if (sentences0 == numSentences)
				throw runtime_error("Input file contains no parsable sentence sets");

			parser.Reset();
		}
	}

	string Checksum(const string& input)
	{
		uint8_t xsum = 0;

		for (string::const_iterator it = input.begin(); it != input.end(); ++it)
			xsum ^= *it;

		return string("*")+utos(xsum,16,2);
	}

	void ComposeTimeSentence(time_t theTime, string& rawResult)
	{
		tm utcComp;
		gmtime_r(&theTime, &utcComp);

		stringstream rawstm;
		rawstm << "GPZDA," << utos(utcComp.tm_hour,10,2) << utos(utcComp.tm_min,10,2) << utos(utcComp.tm_sec,10,2) << ".00,"
			   << utos(utcComp.tm_mday,10,2) << "," << utos(utcComp.tm_mon+1,10,2) << "," << utos(utcComp.tm_year+1900,10,4)
			   << ",,";

		string rawstmstr = rawstm.str();
		rawResult = rawstmstr+Checksum(rawstmstr);
	}

	void DecomposeTimeSentence(const NMEAParser::ParsedSentence& parsedSentence, time_t& theTime)
	{
		memset(&theTime, '\0', sizeof(theTime));

		const int compactTOD = lexical_cast<int>(parsedSentence[1].substr(0,6));

		theTime = UTCToLocal(lexical_cast<int>(parsedSentence[4]), lexical_cast<int>(parsedSentence[3]), lexical_cast<int>(parsedSentence[2]),
							compactTOD/10000, (compactTOD/100)%100, compactTOD%100);
	}

	void UpdatePositionTimeField(time_t theTime, const NMEAParser::ParsedSentence& parsedSentence, string& rawResult)
	{
		tm utcComp;
		gmtime_r(&theTime, &utcComp);

		stringstream substm;
		substm << utos(utcComp.tm_hour,10,2) << utos(utcComp.tm_min,10,2) << utos(utcComp.tm_sec,10,2) << ".00";

		stringstream rawstm;

		for (size_t idx = 0; idx < parsedSentence.size(); ++idx)
		{
			if (idx)
				rawstm << ",";

			if (idx == 1)
				rawstm << substm.str();
			else
				rawstm << parsedSentence[idx];
		}

		string rawstmstr = rawstm.str();
		rawResult = rawstmstr+Checksum(rawstmstr);
	}

	void ParsedSentenceHandler(NMEAParser& sender, const string& rawSentence, const NMEAParser::ParsedSentence& parsedSentence)
	{
		ParsedSentencePtr ps;
		ps.reset(new NMEAParser::ParsedSentence);
		for (NMEAParser::ParsedSentence::const_iterator it = parsedSentence.begin(); it != parsedSentence.end(); ++it) ps->push_back(*it);

		string report;

		if (ps->size() && ((*ps)[0].length() == 5))
		{
			const string sentType = (*ps)[0].substr(2);

			if (!lastTime)
			{
				if (!sentType.compare("ZDA"))
				{
					DecomposeTimeSentence(parsedSentence, lastTime);
					time(&lastPresentedTime);
					ComposeTimeSentence(lastPresentedTime, report);
				}
			}
			else
			{
				if (!sentType.compare("ZDA"))
				{
					time_t newLastTime;
					int i;

					DecomposeTimeSentence(parsedSentence, newLastTime);

					if (newLastTime > lastTime)
						i = (overrideInterval && interval) ? interval : (newLastTime-lastTime);
					else
						i = 1;

					lastTime = newLastTime;
					sleep(i);
					time(&lastPresentedTime);
					ComposeTimeSentence(lastPresentedTime, report);
				}
				else if (!sentType.compare("GGA"))
					UpdatePositionTimeField(lastPresentedTime, parsedSentence, report);
				else if (!sentType.compare("VTG"))
					report = rawSentence;
			}

			if (report.length())
			{
				report.insert(0, "$");
				report += "\r\n";
				++numSentences;
				CheckStatus(write(outputFD, report.data(), report.length()), "Reporting resulting");
			}
		}
	}

private:

	typedef boost::shared_ptr<NMEAParser::ParsedSentence> ParsedSentencePtr;

	int outputFD;
	fstream& stm;
	int numSentences;
	NMEAParser parser;
	bool overrideInterval;
	int interval;
	time_t lastTime;
	time_t lastPresentedTime;
};

int Playback(const string& fileName, const string& portLink, bool overrideInterval, int interval)
{
	int masterFD = -1;
	int slaveFD = -1;
	char childPtyName[100] = { "" };

	CheckStatus(openpty(&masterFD, &slaveFD, childPtyName, NULL, NULL), "Opening the psuedo terminal for reporting serial data (openpty)");
	CheckStatus(close(slaveFD), "Closing the child side of pseudo terminal");

	path childPath = childPtyName;

	if (!is_other(childPath))
	{
		stringstream msg;
		msg << "Child pseudo-terminal '" << childPtyName << "' returned from openpty() does not exist";

		throw runtime_error(msg.str());
	}

	path portLinkPath = portLink;

	if (is_regular_file(portLinkPath) || is_symlink(portLinkPath))
		remove(portLinkPath);

	create_symlink(childPath, portLinkPath);

	fstream stm(fileName.c_str(), ios_base::in);
	PlaybackContext context(masterFD, stm, overrideInterval, interval);

	context.RunFile();
	return 0;
}
