//------------------------------------------------------------------------------
// <copyright file="LqrSMACSubseaStation.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>
//   Implementation of SubseaStation class, a serializable entity that holds the
//   attributes of a subsea asset.
// </summary>
//
// <owner>Mike Cookson</owner>
//------------------------------------------------------------------------------

#include "LqrSMACSubseaStation.h"
#include "LqrSMACGeodetic.h"
#include "nsextend.h"
#include <sstream>
#include <iomanip>
#include <stdexcept>
#include <boost/algorithm/string.hpp>
#include <boost/lexical_cast.hpp>
#include <ctype.h>
#include <math.h>
#include <float.h>

using namespace std;
using namespace LiquidRobotics::SMACFramework;

static const string nameTag = "name";
static const string latitudeTag = "latitude";
static const string longitudeTag = "longitude";
static const string depthTag = "depth";
static const string angularLimitTag = "angularLimit";
static const string slantRangeTag = "slantRange";
static const string watchCircleTag = "watchCircle";

static const string standardTags[] = { nameTag, latitudeTag, longitudeTag, depthTag, angularLimitTag, slantRangeTag, watchCircleTag };
static const size_t numStandardTags = sizeof(standardTags)/sizeof(standardTags[0]);

inline double hypot(double leg1, double leg2)
{
	return sqrt(leg1*leg1 + leg2*leg2);
}

static boost::shared_ptr<double> CopyDoublePtr(boost::shared_ptr<double> rhs)
{
	boost::shared_ptr<double> result;

	if (rhs)
		result.reset(new double(*rhs));

	return result;
}

static void Escape(ostream& stm, const string& str)
{
	bool needsQuote = false;

	for (string::const_iterator ci = str.begin(); !needsQuote && (ci != str.end()); ++ci)
		if (iscntrl(*ci) || isspace(*ci) || (*ci & 0x80))
			needsQuote = true;

	if (needsQuote)
		stm << "\"";

	for (string::const_iterator ci = str.begin(); ci != str.end(); ++ci)
	{
		const char c = *ci;

		switch(c)
		{
		case '\f':
			stm << "\\f";
			break;
		case '\n':
			stm << "\\n";
			break;
		case '\r':
			stm << "\\r";
			break;
		case '\t':
			stm << "\\t";
			break;
		case '\v':
			stm << "\\v";
			break;
		case '\\':
			stm << "\\\\";
			break;
		case '\"':
			if (needsQuote)
				stm << "\\\"";
			else
				stm << "\"";
			break;
		default:
			if ((c < 0) || iscntrl(c))
				stm << "\\x" << utos(c,16,2);
			else
				stm.put(c);
			break;
		}
	}

	if (needsQuote)
		stm << "\"";
}

#if 0
static bool Unescape(istream& istm, string& output)
{
	stringstream ostm;
	bool quoted = false;

	// skip initial white space
	char c;
	while(c = istm.peek(), istm.good() && isblank(c))
		istm.get();

	if (istm.good())
	{
		const char quotechar = '\"';
		const char escchar = '\\';

		quoted = (c == quotechar);

		if (quoted)
			istm.get();

		while(c = istm.peek(), istm.good())
		{
			if (c == '\t')
				c = ' ';
			if (quoted && (c == quotechar))
			{
				istm.get();
				break;
			}
			else if (!quoted && isspace(c))
				break;
			else if (quoted && iscntrl(c))
			{
				stringstream msg;
				msg << "Unepxected control character #" << (unsigned) c << " in quoted string";
				throw invalid_argument(msg.str());
			}
			else if (iscntrl(c) || (c & 0x80))
			{
				stringstream msg;
				msg << "Unexpected control character #" << (unsigned) c << " in stream";
				throw invalid_argument(msg.str());
			}
			else if (quoted && (c == escchar))
			{
				istm.get();

				const char c2 = istm.peek();

				if (!istm.good())
				{
					stringstream msg;
					msg << "No second character following escape (" << c << ")";
					throw invalid_argument(msg.str());
				}

				if (c2 == 'r')
					ostm.put('\r');
				else if (c2 == '\n')
					ostm.put('\n');
				else if (c == 't')
					ostm.put('\t');
				else if (c2 == 'f')
					ostm.put('\f');
				else if (c2 == 'x')
				{
					istm.get();
					const char c3 = istm.peek();
					if (istm.good() || !isxdigit(c3))
						throw invalid_argument("Expected hexadecimal digit after \\x");
					istm.get();
					char c4 = istm.peek();
					if (!istm.good() || !isxdigit(c4))
						c4 = '\0';
					else
						istm.get();
					const char buffer[] = { c3, c4, '\0' };
					char* endptr = NULL;
					char cc = (char) strtol(buffer, &endptr, 16);
					ostm.put(cc);
				}
				else if ((c >= '0') && (c <= '7'))
				{
					istm.get();
					char c3 = istm.peek();
					char c4;
					if (istm.good() && (c3 >= '0') && (c3 <= '7'))
					{
						istm.get();
						c4 = istm.peek();
						if (istm.good() && (c4 >= '0') && (c4 <= '7'))
							istm.get();
						else
							c4 = '\0';
					}
					else
						c3 = c4 = '\0';
					const char buffer[] = { c2, c3, c4, '\0' };
					char* endptr = NULL;
					char cc = (char) strtol(buffer, &endptr, 8);
					ostm.put(cc);
				}
				else
				{
					stringstream msg;
					msg << "Unexpected escape sequence with character #" << (unsigned) c2;
					throw runtime_error(msg.str());
				}
			}
			else
			{
				istm.get();
				ostm.put(c);
			}
		}
	}

	output = ostm.str();
	return quoted || output.length();
}
#endif

inline void UpdateDoublePtr(boost::shared_ptr<double>& ptr, double val)
{
	if (!ptr)
		ptr.reset(new double(val));
	else
		*ptr = val;
}

static void ValidateAttributeKeyValue(const string& val)
{
	for (size_t idx = 0; idx < numStandardTags; ++idx)
		if (boost::iequals(val, standardTags[idx]))
		{
			stringstream stm;
			stm << "Cannot used reserved word " << val << " for attribute key";
			throw stm.str();
		}
}

static void ValidateAngularRangeValue(double val)
{
	if ((val <= 0) || (val >= 90))
	{
		stringstream msg;
		msg << "Angular limit of " << val << " is out of the required range of 0..90, exclusive";
		throw invalid_argument(msg.str());
	}
}

static void ValidateDepthValue(double val)
{
	if (val < 0)
	{
		stringstream msg;
		msg << "Cannot configure a negative depth value (" << val << ")";
		throw runtime_error(msg.str());
	}
}

static void ValidateLatitudeValue(double val)
{
	if ((val < -90) || (val > 90))
	{
		stringstream msg;
		msg << "A latitude of " << val << " is out of the expected range, -90..90";
		throw invalid_argument(msg.str());
	}
}

static void ValidateLongitudeValue(double val)
{
	if ((val < -180) || (val > 180))
	{
		stringstream msg;
		msg << "A longitude of " << val << " is out of the expected range, -180..180";
		throw invalid_argument(msg.str());
	}
}

static void ValidateNameValue(const string& val)
{
	if (!val.length())
		throw invalid_argument("Cannot configure a empty name");
}

static void ValidateSlantRangeValue(double val)
{
	if (val < 0)
	{
		stringstream msg;
		msg << "Cannot configure a negative value for slant range (" << val << ")";
		throw runtime_error(msg.str());
	}
}

static void ValidateWatchCircleValue(double val)
{
	if (val < 0)
	{
		stringstream msg;
		msg << "Cannot configure a negative value for watch circle (" << val << ")";
		throw runtime_error(msg.str());
	}
}

namespace LiquidRobotics {
namespace SMACFramework {

/// Private constructor for serialization
SubseaStation::SubseaStation()
{
}

/// Class constructor with required parameters
SubseaStation::SubseaStation(
		const string& _name,			/**< [in] name of the station */
		double _latitude,				/**< [in] location of the station (latitude) */
		double _longitude				/**< [in] location of the station (longitude) */
		)
: name(_name)
, latitude(_latitude)
, longitude(_longitude)
{
}

/// Class constructor with a string list of keys and values like "name=Station1 latitude=37.1234 longitude=-121.5678 depth=100.1"
SubseaStation::SubseaStation(
		const string& settings			/**< [in] string with keys and values as a list of equations, throws if invalid construction */
		)
: name("")
, latitude(0.0)
, longitude(0.0)
{

}

/// Class constructor that copies another station instance
SubseaStation::SubseaStation(
		const SubseaStation& rhs		/**< [in] station instance to be copied */
		)
: name(rhs.name)
, latitude(rhs.latitude)
, longitude(rhs.longitude)
, attributes(rhs.attributes)
{
	depth = CopyDoublePtr(rhs.depth);
	angularLimit = CopyDoublePtr(rhs.angularLimit);
	slantRangeLimit = CopyDoublePtr(rhs.slantRangeLimit);
	watchCircle = CopyDoublePtr(rhs.watchCircle);
}

/// Assignment operator for SubseaStation
SubseaStation& SubseaStation::operator=(const SubseaStation& rhs)
{
	if (this != &rhs)
	{
		name = rhs.name;
		latitude = rhs.latitude;
		longitude = rhs.longitude;
		depth = CopyDoublePtr(rhs.depth);
		angularLimit = CopyDoublePtr(rhs.angularLimit);
		slantRangeLimit = CopyDoublePtr(rhs.slantRangeLimit);
		watchCircle = CopyDoublePtr(rhs.watchCircle);
		attributes = rhs.attributes;
	}

	return *this;
}

/// Checks the station angular limit, slant range, watch circle and depth against given location at the surface
bool SubseaStation::CheckLimits(
		const Geodetic::Location& currentLocation,	/**< [in] latitude and longitude of the current location (at the surface) */
		bool throwOnError				/**< [in] true=throw an exception if limits check failed (default), false=just return true if OK or false if failed */
		) const
{
	bool ok = true;
	bool complete = !depth || (!angularLimit && !slantRangeLimit && !watchCircle);
	stringstream stm;

	if (!complete)
	{
		const Geodetic::Location stationPos(latitude, longitude);
		const Geodetic::BearingAndDistance bd = currentLocation.CalculateBearingAndDistanceFrom(stationPos);
		const double surfaceDistance = 1000.0*bd.distance;
		const double slantDistance = hypot(surfaceDistance,*depth);
		const double angleToStation = ComputeAngleFromSurfaceDistance(surfaceDistance);

		if (watchCircle && (surfaceDistance > *watchCircle))
		{
			stm << "Surface distance from station is " << surfaceDistance << " m, but watch circle is configured to " << *watchCircle << " m";
			ok = false;
		}
		else if (slantRangeLimit && (slantDistance > *slantRangeLimit))
		{
			stm << "Slant distance to station is " << slantDistance << " m, but limit is configured to " << *slantRangeLimit << " m";
			ok = false;
		}
		else if (angularLimit && (angleToStation > *angularLimit))
		{
			stm << "Angle to station is " << angleToStation << " degrees based on " << surfaceDistance << " m surface distance, but limit is set to " << *angularLimit << " degrees";
			ok = false;
		}
	}

	if (!ok && throwOnError)
		throw runtime_error(stm.str());

	return ok;
}

///// Gets the range from a surface position to this station.
//void SubseaStation::GetRangeBearing (
//		const Geodetic::Location& currentLocation,	/**< [in] latitude and longitude of the current location (at the surface) */
//		double& range,
//		double& bearing
//		) const
//{
//	const Geodetic::Location stationPos(latitude, longitude);
//	const Geodetic::BearingAndDistance bd = currentLocation.CalculateBearingAndDistanceFrom (stationPos);
//
//	range   = bd.distance;
//	bearing = bd.bearing;
//}

/// Clears the angular limit setting (if any)
void SubseaStation::ClearAngularLimit()
{
	angularLimit.reset();
}

/// Clears the optional attribute with the given name (if any)
void SubseaStation::ClearAttribute(
		const string& attributeName		/**< [in] the name of the attribute to clear */
		)
{
	StringMap::iterator it = attributes.find(attributeName);

	if (it != attributes.end())
		attributes.erase(it);
}

/// Clears all optional attributes
void SubseaStation::ClearAttributes()
{
	attributes.clear();
}

/// Clears the depth setting (if any)
void SubseaStation::ClearDepth()
{
	depth.reset();
}

/// Clears the slant range limit (if any)
void SubseaStation::ClearSlantRangeLimit()
{
	slantRangeLimit.reset();
}

/// Clears the watch circle limit (if any)
void SubseaStation::ClearWatchCircle()
{
	watchCircle.reset();
}

/// Computes the angle (degrees) between the subsea modem and the current modem given a ping distance and the correct depth configured (throws if no depth configured)
double SubseaStation::ComputeAngleFromSlantDistance(
		double slantDistance	/**< [in] measured diagonal distance to subsea modem (meters) */
		) const
{
	if (slantDistance < 0)
	{
		stringstream msg;
		msg << "Cannot compute angle with slant distance = " << slantDistance;
		throw invalid_argument(msg.str());
	}

	if (!depth)
		throw runtime_error("Cannot compute angle from slant distance when no depth is configured");

	const double quotient = (slantDistance > 0) ? min(1.0, fabs(*depth/slantDistance)) : 0;
	const double ang = 180.0*acos(quotient)/M_PI;
	return ang;
}

/// Computes the angle (degrees) between the subsea modem and the current modem given a measurement of the surface distance; for example, this might be done using GPS (throws if no depth configured)
double SubseaStation::ComputeAngleFromSurfaceDistance(
		double surfaceDistance	/**< [in] measured distance to subsea modem at the surface (meters) */
		) const
{
	if (surfaceDistance < 0)
	{
		stringstream msg;
		msg << "Cannot compute angle with surface distance = " << surfaceDistance;
		throw invalid_argument(msg.str());
	}

	if (!depth)
		throw runtime_error("Cannot compute angle from surface distance when no depth is configured");

	const double ang = 180.0*atan2(surfaceDistance,*depth)/M_PI;
	return ang;
}

/// Computes the watch circle using slant range, angular limit and depth (does not take the configured watch circle into account.)  Returns zero if no depth or no limits configured
double SubseaStation::ComputeWatchCircle() const
{
	bool set = false;
	double result = 0.0;

	if (depth && slantRangeLimit && (*depth < *slantRangeLimit))
	{
		const double slantRadius = sqrt(*slantRangeLimit * *slantRangeLimit - *depth * *depth);

		if (!set || (slantRadius < result))
		{
			result = slantRadius;
			set = true;
		}
	}

	if (depth && angularLimit && (*depth > 0) && (*angularLimit > 0) && (*angularLimit < 90))
	{
		const double hyp = *depth / cos(M_PI * *angularLimit / 180.0);
		const double angularRadius = sqrt(hyp*hyp - *depth * *depth);

		if (!set || (angularRadius < result))
		{
			result = angularRadius;
			set = true;
		}
	}

	return set ? result : 0.0;
}

/// Returns the angular limit: the maximum angle between the station and the current position (degrees); throws if no angular limit set.
double SubseaStation::GetAngularLimit() const
{
	if (!angularLimit)
		throw runtime_error("Angular limit was not set");

	return *angularLimit;
}

/// Retrieves the angular limit and stores it in "output" The angular limit is maximum angle between the station and the current position (degrees)  No value is set in "output" and the function returns false if none was configured; returns true if the value was retrieved.
bool SubseaStation::GetAngularLimit(
		double& output			/**< [out] the angular limit (degrees, only set if there is a configured angular limit) */
		) const
{
	if (angularLimit)
	{
		output = *angularLimit;
		return true;
	}
	else
		return false;
}

/// Retrieves the attribute with the given name; throws if there is no attribute with the given name.
string SubseaStation::GetAttribute(
		const string& attributeName		/**< [in] the name of the attribute to return */
		) const
{
	ValidateAttributeKeyValue(attributeName);
	const string upName = boost::to_lower_copy(attributeName);
	StringMap::const_iterator ci = attributes.find(upName);

	if (ci == attributes.end())
	{
		stringstream msg;
		msg << "Attribute '" << attributeName << "' was not found";
		throw invalid_argument(msg.str());
	}

	return ci->second;
}

/// Retrieves the attribute with the given name and stores it in "output"  Returns true if there is an attribute match or false if there is no match.
bool SubseaStation::GetAttribute(
		const string& attributeName,	/**< [in] the name of the attribute to return */
		string& output					/**< [out] receives the attribute value if there is a match */
		) const
{
	const string upName = boost::to_lower_copy(attributeName);
	StringMap::const_iterator ci = attributes.find(upName);

	if (ci != attributes.end())
	{
		output = ci->second;
		return true;
	}
	else
		return false;
}

/// Returns the map of optional attributes
const SubseaStation::StringMap& SubseaStation::GetAttributesMap() const
{
	return attributes;
}

/// Returns the configured depth or throws an exception if no depth was configured
double SubseaStation::GetDepth() const
{
	if (!depth)
		throw runtime_error("Depth was not set");

	return *depth;
}

/// If there is a configure depth it is returned in "output and returns true; otherwise, returns false.
bool SubseaStation::GetDepth(
		double& output					/**< [out] receives the depth value (meters) if it is configured */
		) const
{
	if (depth)
	{
		output = *depth;
		return true;
	}
	else
		return false;
}

/// Returns the configured latitude
double SubseaStation::GetLatitude() const
{
	return latitude;
}

/// Returns the configured latitude and longitude as a LatLon value
Geodetic::Location SubseaStation::GetLocation() const
{
	return Geodetic::Location(latitude, longitude);
}

/// Returns the configured longitude
double SubseaStation::GetLongitude() const
{
	return longitude;
}

/// Returns the configured name
const string& SubseaStation::GetName() const
{
	return name;
}

/// Returns the configured slant range limit (meters) or throws if none is configured
double SubseaStation::GetSlantRangeLimit() const
{
	if (!slantRangeLimit)
		throw runtime_error("Slant range limit was not set");

	return *slantRangeLimit;
}

/// If there is a configured slant range (meters), it is returned in "output" and the method returns true; otherwise, returns false
bool SubseaStation::GetSlantRangeLimit(
		double& output					/**< [out] receives the slant range limit (meters) if it is configured */
		) const
{
	if (slantRangeLimit)
	{
		output = *slantRangeLimit;
		return true;
	}
	else
		return false;
}

/// Returns the configured watch circle (radius in meters) or throws if none is configured
double SubseaStation::GetWatchCircle() const
{
	if (!watchCircle)
		throw runtime_error("Watch circle was not set");

	return *watchCircle;
}

/// If there is a configured watch circle (radius in meters), it is returned in "output" and the method returns true; otherwise, returns false
bool SubseaStation::GetWatchCircle(
		double& output					//*< [out] receives the watch circle (radius in meters) if it is configured
		) const
{
	if (watchCircle)
	{
		output = *watchCircle;
		return true;
	}
	else
		return false;
}

/// Returns true if an angular limit is configured or false if it is not
bool SubseaStation::HasAngularLimit() const
{
	return !!angularLimit;
}

/// Returns true if there is a configured attribute of the given name or false if there is not
bool SubseaStation::HasAttribute(const string& attributeName) const
{
	ValidateAttributeKeyValue(attributeName);
	const string upName = boost::to_lower_copy(attributeName);
	StringMap::const_iterator ci = attributes.find(upName);

	return ci != attributes.end();
}

/// Returns true if there is a configured depth or false if there is not
bool SubseaStation::HasDepth() const
{
	return !!depth;
}

/// Returns true if there is a configured slant range limit or false if there is not
bool SubseaStation::HasSlantRangeLimit() const
{
	return !!slantRangeLimit;
}

/// Returns true if there is a configured watch circle or false if there is not
bool SubseaStation::HasWatchCircle() const
{
	return !!watchCircle;
}

/// Updates the station parameters using a list of key/value pairs in "settings", optionally prevents the name to be set
void SubseaStation::Set(
		const string& settings,			//*< [in] a set of key/value pairs as equations
		bool allowNameSet				//*< [in] true if it's OK to set the name; false if doing so will throw an exception
		)
{

}

/// Sets a new value for the angular limit (degrees)
void SubseaStation::SetAngularLimit(
		double newValue					//*< [in] the new angular limit value (degrees)
		)
{
	ValidateAngularRangeValue(newValue);
	UpdateDoublePtr(angularLimit, newValue);
}

/// Sets an optional attribute
void SubseaStation::SetAttribute(
		const string& attributeName,	//*< [in] the name of an optional parameter to set
		const string& newValue			//*< [in] the value for the optional parameter
		)
{
	ValidateAttributeKeyValue(attributeName);
	const string upName = boost::to_lower_copy(attributeName);
	attributes[upName] = newValue;
}

/// Sets a depth (meters)
void SubseaStation::SetDepth(
		double newValue					//*< [in] the new depth value (meters)
		)
{
	ValidateDepthValue(newValue);
	UpdateDoublePtr(depth, newValue);
}

/// Sets a new value for the station latitude
void SubseaStation::SetLatitude(
		double newValue					//*< [in] the new latitude value (degrees)
		)
{
	ValidateLatitudeValue(newValue);
	latitude = newValue;
}

/// Sets a latitude/longitude value for the station
void SubseaStation::SetLocation(
		const Geodetic::Location& newValue			//*< [in] the new value for latitude and longitude as a geodetic location
		)
{
	if (!newValue.valid)
		throw invalid_argument("Cannot set an invalid geodetic location");

	ValidateLatitudeValue(newValue.lat);
	ValidateLongitudeValue(newValue.lon);

	latitude = newValue.lat;
	longitude = newValue.lon;
}

/// Sets a new value for the station longitude
void SubseaStation::SetLongitude(
		double newValue					//*< [in] the new longitude value (degrees)
		)
{
	ValidateLongitudeValue(newValue);
	longitude = newValue;
}

/// Sets a new value for the station name
void SubseaStation::SetName(
		const string& newValue			//*< [in] the new station name
		)
{
	ValidateNameValue(newValue);
	name = newValue;
}

/// Sets a new value for the slant range limit (meters)
void SubseaStation::SetSlantRangeLimit(
		double newValue					//*< [in] new slant range limit (meters)
		)
{
	ValidateSlantRangeValue(newValue);
	UpdateDoublePtr(slantRangeLimit, newValue);
}

/// Sets a new value for the watch circle (radius in meters)
void SubseaStation::SetWatchCircle(
		double newValue					//*< [in] new watch circle (meters)
		)
{
	ValidateWatchCircleValue(newValue);
	UpdateDoublePtr(watchCircle, newValue);
}

/// Returns the station configuration as a string using the stream operator
string SubseaStation::ToString() const
{
	stringstream stm;
	stm << *this;
	return stm.str();
}

void SubseaStation::ValidateAll()
{
	ValidateNameValue(name);
	ValidateLatitudeValue(latitude);
	ValidateLongitudeValue(longitude);

	if (angularLimit)		ValidateAngularRangeValue(*angularLimit);
	if (depth)				ValidateDepthValue(*depth);
	if (slantRangeLimit)	ValidateSlantRangeValue(*slantRangeLimit);
	if (watchCircle)		ValidateWatchCircleValue(*watchCircle);

	for (StringMap::const_iterator ci = attributes.begin(); ci != attributes.end(); ++ci)
		ValidateAttributeKeyValue(ci->first);
}

}
}

/// Stream operator for SubseaStation
std::ostream& operator<<(std::ostream& stm, const LiquidRobotics::SMACFramework::SubseaStation& station)
{
	stm << nameTag << "=";
	Escape(stm, station.GetName());

	stm << " " << latitudeTag << "=" << Geodetic::AngleToString(station.GetLatitude())
	    << " " << longitudeTag << "=" << Geodetic::AngleToString(station.GetLongitude());

	if (station.HasDepth())
		stm << " " << depthTag << "=" << station.GetDepth();

	if (station.HasAngularLimit())
		stm << " " << angularLimitTag << "=" << station.GetAngularLimit();

	if (station.HasSlantRangeLimit())
		stm << " " << slantRangeTag << "=" << station.GetSlantRangeLimit();

	if (station.HasWatchCircle())
		stm << " " << watchCircleTag << "=" << station.GetWatchCircle();

	const SubseaStation::StringMap& sm = station.GetAttributesMap();

	for (SubseaStation::StringMap::const_iterator ci = sm.begin(); ci != sm.end(); ++ci)
	{
		stm << " ";
		Escape(stm, ci->first);
		stm << "=";
		Escape(stm, ci->second);
	}

	return stm;
}

string SubseaStation::DoubleToStr(const double d)
{
	stringstream ss;
	ss << scientific << setprecision(DBL_DIG) << d;
	const string result = ss.str();
	return result;
}

double SubseaStation::StrToDouble(const string& str)
{
	return boost::lexical_cast<double>(str);
}


#include <boost/archive/xml_iarchive.hpp>
#include <boost/archive/xml_oarchive.hpp>

void SubseaStation::RunTests()
{
	SubseaStation s1("stn1", 80, 170);

	using namespace std;
	using namespace boost;
	using namespace boost::archive;
	using namespace boost::serialization;

	stringstream stm1;

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

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

	stringstream stm2(stm1.str());

	{
		SubseaStation s2;
		xml_iarchive ia(stm2);
		ia >> make_nvp("rootlevel", s2);
	}
}
