//------------------------------------------------------------------------------
// <copyright file="main.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 entrypoint implementation for GPS Listener.
// </summary>
//
// <owner>Mike Cookson</owner>
//------------------------------------------------------------------------------

#include "AwaitPPS.h"
#include "GPSListenerProgram.h"
#include "GPSListenerSettings.h"
#include "LqrSMACGeodetic.h"
#include "nsextend.h"
#include <sstream>
#include <stdexcept>
#include <string>
#include <boost/algorithm/string.hpp>

using namespace std;
using namespace LiquidRobotics::SMACFramework;

inline double ConvertAngle(string paramName, string paramValue, Geodetic::HemisphereOrientation orientation)
{
	double result;

	try
	{
		result = Geodetic::StringToAngle(paramValue, orientation);
	}
	catch(exception& ex)
	{
		cerr << "For " << paramName << ", " << ex.what() << endl;
		throw;
	}

	return result;
}

inline double ConvertDouble(string paramName, string paramValue)
{
	double result;

	try
	{
		result = boost::lexical_cast<double>(paramValue);
	}
	catch(exception& ex)
	{
		cerr << "For " << paramName << ", " << ex.what() << endl;
		throw;
	}

	return result;
}

// GPSListener CalcDistance <lat1> <lon1> <lat2> <lon2>
int CalcDistance(int argc, char* argv[])
{
	if (argc != 6)
	{
		cerr << "6 arguments were expected, but " << argc << " were provided" << endl
			 << endl
			 << "Usage:" << endl
			 << "\t" << argv[0] << " CalcDistance <lat1> <lon1> <lat2> <lon2>" << endl;
		return 1;
	}

	try
	{
		double lat1 = ConvertAngle("lat1", argv[2], Geodetic::LATITUDE);
		double lon1 = ConvertAngle("lon1", argv[3], Geodetic::LONGITUDE);
		double lat2 = ConvertAngle("lat2", argv[4], Geodetic::LATITUDE);
		double lon2 = ConvertAngle("lon2", argv[5], Geodetic::LONGITUDE);

		Geodetic::Location loc1(lat1, lon1);
		Geodetic::Location loc2(lat2, lon2);
		Geodetic::BearingAndDistance bd = Geodetic::CalculateBearingAndDistance(loc1, loc2);

		cout << loc1.ToString() << "/" << loc1.ToString(true) << " to " << loc2.ToString() << "/" << loc2.ToString(true) << " -> " << bd.ToString() << endl;
	}
	catch(...)
	{
		return 1;
	}

	return 0;
}

int CalcPoint(int argc, char* argv[])
{
	if (argc != 6)
	{
		cerr << "6 arguments were expected, but " << argc << " were provided" << endl
			 << endl
			 << "Usage:" << endl
			 << "\t" << argv[0] << " CalcPoint <lat1> <lon1> <bearing> <distance>" << endl;
		return 1;
	}

	try
	{
		double lat1 = ConvertAngle("lat1", argv[2], Geodetic::LATITUDE);
		double lon1 = ConvertAngle("lon1", argv[3], Geodetic::LONGITUDE);
		double bearing = ConvertDouble("bearing", argv[4]);
		double distance = ConvertDouble("distance", argv[5]);

		Geodetic::Location loc1(lat1, lon1);
		Geodetic::BearingAndDistance bd(bearing, distance);
		Geodetic::Location loc2 = Geodetic::OffsetLocation(loc1, bd);

		cout << loc1.ToString() << "/" << loc1.ToString(true) << " offset by " << bd.ToString() << " -> " << loc2.ToString() << "/" << loc2.ToString(true) << endl;
	}
	catch(...)
	{
		return 1;
	}

	return 0;
}

int AwaitPPS(int argc, char* argv[])
{
	using namespace LiquidRobotics::GPSListenerLib;

	tzset();

	try
	{
		GPSListenerSettings settings;
		const string xmlFileName = "GPSListener-config.xml";
		settings.Load(xmlFileName);

		bool running = true;

		while(running)
		{
			switch(LiquidRobotics::GPSListenerLib::AwaitPPS(settings.GetPPSGPIO(), 1000, false))
			{
			case AwaitPPSStatus_OK:
			{
				timeval tv;

				if (gettimeofday(&tv, NULL) == -1)
					tv.tv_sec = tv.tv_usec = 0;

				tm comp;
				localtime_r(&tv.tv_sec, &comp);

				stringstream msg_s;

				msg_s  << "PPS "
					   << ultos(comp.tm_year+1900,10,4) << "/" << ultos(comp.tm_mon+1,10,2) << "/" << ultos(comp.tm_mday,10,2) << " "
				       << ultos(comp.tm_hour,10,2) << ":" << ultos(comp.tm_min,10,2) << ":" << ultos(comp.tm_sec,10,2)
				       << "." << ultos(tv.tv_usec,10,6);

				cout << msg_s.str() << endl;
				break;
			}

			case AwaitPPSStatus_Timeout:
			{
				cout << "Timeout waiting for PPS" << endl;
				break;
			}

			case AwaitPPSStatus_BadArguments:
			{
				cout << "Invalid configuration for GPS" << endl;
				running = false;
				break;
			}

			case AwaitPPSStatus_Error:
			default:
			{
				cout << "An error occurred" << endl;
				running = false;
				break;
			}
			}
		}
	}
	catch(exception& ex)
	{
		cout << "Caught exception " << typeid(ex).name() << ": " << ex.what() << endl;
	}

	return 0;
}

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

	if ((argc >= 2) && boost::iequals(argv[1], "CalcDistance"))
		result = CalcDistance(argc, argv);
	else if ((argc >= 2) && boost::iequals(argv[1], "CalcPoint"))
		result = CalcPoint(argc, argv);
	else if ((argc >= 2) && boost::iequals(argv[1], "AwaitPPS"))
		result = AwaitPPS(argc, argv);
	else
		result = GPSListenerProgram(argc, argv);

	return result;
}
