//-------------------------------------------------------------------------------
// <copyright file="LqrSMACSubseaStationCollection.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>
// </summary>
//
// <owner></owner>
//-------------------------------------------------------------------------------

#include "LqrSMACSubseaStationCollection.h"
#include <sstream>
#include <stdexcept>
#include <boost/algorithm/string.hpp>
#include <boost/archive/xml_iarchive.hpp>
#include <boost/archive/xml_oarchive.hpp>
#include <ctype.h>

using namespace std;

inline int icompare(const string& s1, const string& s2)
{
	string::const_iterator ci1 = s1.begin();
	string::const_iterator ci2 = s2.begin();
	bool done1, done2;

	while(done1 = (ci1 == s1.end()),
		   done2 = (ci2 == s2.end()),
	       (done1 == done2) && !done1 && (toupper(*ci1) == toupper(*ci2)))
   {
	   ++ci1;
	   ++ci2;
   }

   int result;

   if (done1 && done2)
	   result = 0;
   else if (done1 && !done2)
	   result = -1;
   else if (!done1 && done2)
	   result = 1;
   else if (toupper(*ci1) < toupper(*ci2))
	   result = -1;
   else
	   result = 1;

   return result;
}

namespace LiquidRobotics {
namespace SMACFramework {

/// Functor to compare to entries in SubseaStationSet (performs a caseless "less" function on the name and handles nil pointers)
bool SubseaStationCollection::SubseaStationSetComparitor::operator ()(const SubseaStation::Ptr& val1, const SubseaStation::Ptr& val2) const
{
	bool result;

	if (val1 && val2)
		result = (icompare(val1->GetName(),val2->GetName()) < 0);
	else
		result = val1 && val2;

	return result;
}

/// Class constructor with required parameters
SubseaStationCollection::SubseaStationCollection()
{
}

/// Copy constructor
SubseaStationCollection::SubseaStationCollection(
		const SubseaStationCollection& rhs			/**< [in] instance to be copied */
		)
{
	DeepCopyStations(rhs.stations);
}

/// Assignment operator
SubseaStationCollection& SubseaStationCollection::operator=(
		const SubseaStationCollection& rhs			/**< [in] instance to be copied */
		)
{
	if (this != &rhs)
		DeepCopyStations(rhs.stations);

	return *this;
}

/// Adds a new station to the list controlled by the collection, throws if the name of the station is duplicated in the existing list
void SubseaStationCollection::AddStation(
		const SubseaStation& newStation,				/**< [in] the new station to be added to the collection */
		bool overwrite									/**< [in] true if it's OK to overwrite the station, false if a duplicate name throws */
		)
{
	SubseaStation::Ptr temp;
	temp.reset(new SubseaStation(newStation));

	SubseaStationSet::iterator it = stations.find(temp);

	if (it != stations.end())
	{
		if (overwrite)
			stations.erase(it);
		else
		{
			stringstream msg;
			msg << "Adding SubseaStation with name=\"" << newStation.GetName() << "\" would overwrite an existing member of the collection";
			throw invalid_argument(msg.str());
		}
	}

	stations.insert(temp);
}

/// Removes all stations in the set
void SubseaStationCollection::Clear()
{
	stations.clear();
}

/// Deletes the named station, throws if the named station does not exist; station names are not case sensitive
void SubseaStationCollection::DeleteStation(
		const string& stationName					/**< [in] the name of the station to be deleted */
		)
{
	SubseaStation::Ptr temp;
	temp.reset(new SubseaStation(stationName, 0.0, 0.0));

	SubseaStationSet::iterator it = stations.find(temp);

	if (it == stations.end())
	{
		stringstream msg;
		msg << "The collection does not contain a SubseaStation with name=\"" << stationName << "\" (DeleteStation)";
		throw invalid_argument(msg.str());
	}
	else
		stations.erase(it);
}

/// Finds the station nearest "currentPosition" and returns the reference of the station; throws if the station list is empty
const SubseaStation& SubseaStationCollection::FindNearestStation(
		const Geodetic::Location& currentPosition	/**< [in] the current latitude and longitude, station distances are measured from this point */
		) const
{
	if (stations.size() == 0)
		throw runtime_error("There are no stations in the collection (FindNearestStation)");

//TODO: "found" is never set here...
	SubseaStation::Ptr found;
	double minRange = 0.0;

	for (SubseaStationSet::const_iterator ci = stations.begin(); ci != stations.end(); ++ci)
	{
		if (!*ci)
			throw runtime_error("Nil pointer found in SubseaStationCollection (FindNearestStation)");

		bool inRange;
		try
		{
			(*ci)->CheckLimits(currentPosition);
			inRange = true;
		}
		catch(...)
		{
			inRange = false;
		}

		if (inRange)
		{
			const Geodetic::Location stationLoc = (*ci)->GetLocation();
			const double rangeToStation = currentPosition.CalculateBearingAndDistanceTo(stationLoc).distance;

			if (!found || (rangeToStation < minRange))
				minRange = rangeToStation;
		}
	}

	if (!found)
		throw runtime_error("No station minimum range found even though the collection is not empty?");

	return *found;
}

/// Finds the station nearest "currentPosition" and returns the reference of the station; throws if the station list is empty.
/// This method "only" checks the distance to the nearest station; it does not use angular_limit, watch_circle or slant_range_limit.
const SubseaStation& SubseaStationCollection::FindNearestStation2(
		const Geodetic::Location& currentPosition	/**< [in] the current latitude and longitude, station distances are measured from this point */
		) const
{
	if (stations.size() == 0) {
		throw runtime_error("There are no stations in the collection (FindNearestStation)");
	}

	SubseaStation::Ptr found;
	double minRange = 999999999.99;

	for (SubseaStationSet::const_iterator ci = stations.begin(); ci != stations.end(); ++ci)
	{
		//double range=0.0;
		//double bearing=0.0;

		if (!*ci) {
			throw runtime_error("Nil pointer found in SubseaStationCollection (FindNearestStation2)");
		}

//		(*ci)->GetRangeBearing (currentPosition, range, bearing);
		const Geodetic::Location stationLoc = (*ci)->GetLocation();
		const double rangeToStation = currentPosition.CalculateBearingAndDistanceTo(stationLoc).distance;

		if (rangeToStation < minRange) {
			minRange = rangeToStation;
			found = (*ci);
		}
	}

	return *found;
}

/// Finds the group of stations that are in range of "currentPosition" and returns a list of names of the station; the list will be empty if nothing is in range
SubseaStationCollection::StringList SubseaStationCollection::FindStationsInRange(
		const Geodetic::Location& currentPosition	/**< [in] the current latitude and longitude, station distances are measured from this point */
		) const
{
	StringList stationList;

	for (SubseaStationSet::const_iterator ci = stations.begin(); ci != stations.end(); ++ci)
	{
		if (!*ci)
			throw runtime_error("Nil pointer found in SubseaStationCollection (FindStationsInRange)");

		bool inRange=false;
		try
		{
//			bool state = (*ci)->CheckLimits(currentPosition);
			inRange = (*ci)->CheckLimits(currentPosition);
//			inRange = true;
		}
		catch(...)
		{
			inRange = false;
		}

		if (inRange) {
			stationList.push_back((*ci)->GetName());
		}
	}

	return stationList;
}

/// Updates an existing station in the list with a set of new values; throws if the named station does not exist or if the new station name overwrites another station in the list
void SubseaStationCollection::UpdateStation(
		const string& stationName,					/**< [in] the name of the existing station to be updated */
		const SubseaStation& newStation				/**< [in] the new definition of the station */
		)
{
	SubseaStation::Ptr temp;
	temp.reset(new SubseaStation(stationName, 0.0, 0.0));

	SubseaStationSet::iterator it = stations.find(temp);

	if (it == stations.end())
	{
		stringstream msg;
		msg << "The collection does not contain a SubseaStation with name=\"" << stationName << "\" (UpdateStation)";
		throw invalid_argument(msg.str());
	}
	else
	{
		if (stationName.compare((*it)->GetName()) == 0)
		{
			stations.erase(it);

			SubseaStation::Ptr temp2;
			temp2.reset(new SubseaStation(newStation));

			stations.insert(temp2);
		} else {
//			**it = newStation;
			SubseaStation::Ptr temp2;
			temp2.reset(new SubseaStation(newStation));
			stations.insert(temp2);
		}
	}
}

/// Copies "rhs" to "stations" as a deep copy (doesn't just save a reference)
void SubseaStationCollection::DeepCopyStations(const SubseaStationSet& rhs)
{
	stations.clear();

	for (SubseaStationSet::const_iterator ci = rhs.begin(); ci != rhs.end(); ++ci)
	{
		if (*ci)
		{
			SubseaStation::Ptr newItem;
			newItem.reset(new SubseaStation(**ci));

			stations.insert(newItem);
		}
		else
			throw invalid_argument("SubseaStationSet element contains a nil pointer member (DeepCopyStations)");
	}
}

/// Validates the entire list of stations (verifies the set is valid)
void SubseaStationCollection::ValidateAll() const
{
	for (SubseaStationSet::const_iterator ci = stations.begin(); ci != stations.end(); ++ci)
		if (!*ci)
			throw invalid_argument("SubseaStationSet element contains a nil pointer member (ValidateAll)");
}

/// Validates the entire list of stations (verifies the set is valid)
void SubseaStationCollection::List (Logger& logger)
{
	for (SubseaStationSet::const_iterator ci = stations.begin(); ci != stations.end(); ++ci) {
		Logger::Locked(logger)->Info << "SubseaStations: " << (*ci)->ToString() << endl;
	}

}

/// Returns the named station, throws if the named station does not exist; station names are not case sensitive
const SubseaStation& SubseaStationCollection::GetStation(
		const string& stationName					/**< [in] the name of the station to be deleted */
		)
{
	SubseaStation::Ptr temp;
	temp.reset(new SubseaStation(stationName, 0.0, 0.0));

	SubseaStationSet::iterator it = stations.find(temp);

	if (it == stations.end())
	{
		stringstream msg;
		msg << "The collection does not contain a SubseaStation with name=\"" << stationName << "\" (GetStation)";
		throw invalid_argument(msg.str());
	}
	else {
		SubseaStation::Ptr found = (*it);
		return *found;
	}
}

void SubseaStationCollection::RunTests()
{
	using namespace boost;
	using namespace boost::archive;
	using namespace boost::serialization;

	SubseaStation s1("stn1", 80, 170);
	SubseaStation s2("stn2", 81, 171);
	SubseaStation s3("stn3", 82, 172);

	SubseaStationCollection col1;
	col1.AddStation(s1);
	col1.AddStation(s2);
	col1.AddStation(s3);

	stringstream stm1;

	{xml_oarchive oa(stm1);
	oa << make_nvp("rootlevel", col1);}

	cout << "stream out:" << stm1.str() << endl;

	stringstream stm2(stm1.str());

	{
		SubseaStationCollection col2;
		xml_iarchive ia(stm2);
		ia >> make_nvp("rootlevel", col2);
	}
}

}
}

