//-----------------------------------------------------------------------------------------
// <copyright file="GPSListenerProgram_SensorSimulationCmd.h" 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>
//   Declaration and implementation of SensorSimulationCmd class, which parses and executes
//   the SIMULATE command for GPSListenerProg
// </summary>
//
// <owner>Mike Cookson</owner>
//-----------------------------------------------------------------------------------------

#ifndef __GPSLISTENERLIB_GPSLISTENERPROGRAM_SENSORSIMULATIONCMD_H__
#define __GPSLISTENERLIB_GPSLISTENERPROGRAM_SENSORSIMULATIONCMD_H__

#include "GPSListenerProgram.h"
#include <iomanip>

namespace LiquidRobotics {
namespace GPSListenerLib {

class SensorSimulationCmd : public GPSListenerProg::ICommandBase
{
public:

	SensorSimulationCmd(GPSListenerProg& _prog)
	: prog(_prog)
	, isSimulating(false)
	, latCenter(0.0)
	, lonCenter(0.0)
	, speedKt(0.0)
	, radiusM(0.0)
	, movingCW(false)
	, show(false)
	{
	}

	inline double ConvertDouble(const string& paramName, const string& str)
	{
		try
		{
			return boost::lexical_cast<double>(str);
		}
		catch(exception& ex)
		{
			stringstream stm;
			stm << "For " << paramName << ": " << ex.what();
			throw invalid_argument(stm.str());
		}
	}

	bool Parse(const GPSListenerProg::StringArray& command, const char* _requester, const char* _transactionID)
	{
		if ((command.size() > 1)
		 && boost::iequals(command[0], GPSListenerProg::SensorCategory)
		 && (boost::iequals(command[1], GPSListenerProg::SimulationCmd) || boost::iequals(command[1], GPSListenerProg::ShowSimulationCmd)))
		{
			show = boost::iequals(command[1], GPSListenerProg::ShowSimulationCmd);

			if ((command.size() > 2) && boost::iequals(command[2], "off"))
			{
				if (command.size() != 3)
					ThrowCORBAException2<BadParameter>("command", "Incorrect argument count for simulate command");

				isSimulating = false;
			}
			else if ((command.size() > 2)
				  && boost::iequals(command[2], "on"))
			{
				if (command.size() != 8)
					ThrowCORBAException2<BadParameter>("command", "Incorrect argument count for simulate command");

				latCenter = ConvertDouble("center latitude", command[3]);
				lonCenter = ConvertDouble("center longitude", command[4]);
				speedKt = ConvertDouble("speed (kt)", command[5]);
				radiusM = ConvertDouble("radius (m)", command[6]);
				movingCW = boost::iequals(command[7], "CW");

				stringstream err;

				if ((latCenter < -90) || (latCenter > 90))
				{
					err << "Latitude (" << latCenter << ") is out of range";
					ThrowCORBAException2<BadParameter>("command", err.str());
				}

				if ((lonCenter < -180) || (lonCenter > 180))
				{
					err << "Longitude (" << lonCenter << ") is out of range";
					ThrowCORBAException2<BadParameter>("command", err.str());
				}

				if (speedKt <= 0)
				{
					err << "Speed must be a positive value";
					ThrowCORBAException2<BadParameter>("command", err.str());
				}

				if (radiusM <= 0)
				{
					err << "Radius must be a positive value";
					ThrowCORBAException2<BadParameter>("command", err.str());
				}

				if (!boost::iequals(command[7], "CW") && !boost::iequals(command[7], "CCW"))
				{
					err << "Direction (" << command[7] << ") must be specified as either CW or CCW";
					ThrowCORBAException2<BadParameter>("command", err.str());
				}

				isSimulating = true;
			}
			else
				ThrowCORBAException2<BadParameter>("command", "Invalid invocation format for simulate command");

			return true;
		}
		else
			return false;
	}

	bool RunImmediate(::CORBA::Any_OUT_arg& result)
	{
		string resultStr;

		if (show)
		{
			GPSEventSource::SimulationMode smod(Geodetic::Location(latCenter,lonCenter), speedKt, radiusM, movingCW);
			stringstream ss;
			ss << "name,latitude,longitude" << endl;
			for (unsigned ndx = 0; ndx <= 3600; ++ndx)
			{
				const double index = 1.0*ndx;
				Geodetic::Location newLoc = smod.GetPosition(index);
				ss << ndx << "," << setprecision(10) << newLoc.lat << "," << setprecision(10) << newLoc.lon << endl;
			}
			resultStr = ss.str();
		}
		else
		{
			resultStr = "OK";

			if (isSimulating)
				prog.StartSimulation(latCenter, lonCenter, speedKt, radiusM, movingCW);
			else
				prog.StopSimulation();
		}

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

	void Run()
	{
		throw logic_error("SimulationCmd::Run() called unexpectedly");
	}

private:

	GPSListenerProg& prog;
	bool isSimulating;
	double latCenter;
	double lonCenter;
	double speedKt;
	double radiusM;
	bool movingCW;
	bool show;
};

}
}

#endif
