#ifndef _SRS_h__
#define _SRS_h__

#include <GSTenums.h>
#include <buildspec.h>
#include <config.h>
#include <exceptions/GSTRuntimeException.h>

#include <boost/shared_ptr.hpp>
#include <map>
#include <string>

#include <Geometry/boost_serialize_includes.hpp>

namespace GST
{
namespace ClientUtils
{
class SRSItemDesc;
} // namespace ClientUtils

namespace Geometry
{

class NoSRS;
class GSTSRSRef;
class WKTSRSDef;
class PROJ4SRSDef;

template<typename Archive>
void registerSRSTypes(Archive &ar)
{
	ar.template register_type<NoSRS>();
	ar.template register_type<GSTSRSRef>();
	ar.template register_type<WKTSRSDef>();
	ar.template register_type<PROJ4SRSDef>();
}

/**
  *		\defgroup refSRS The Spatial Reference Handling
  *		@{
  *
  *		\section Datamodel
  *
  *		In GST the Spatial Reference Systems are stored in a relation called srs
  in the GST schema. The Table consists of the following columns: <table> <tr>
				<td><b>id bigserial NOT NULL primary key</b></td>
				<td>The GST SRS ID</td>
			</tr>
			<tr>
				<td><b>code_type varchar(50)</b></td>
				<td>A code identifier</td>
			</tr>
			<tr>
				<td><b>code varchar(255)</b></td>
				<td>A code value</td>
			</tr>
			<tr>
				<td><b>name varchar(255)</b></td>
				<td>A human readable label</td>
			</tr>
			<tr>
				<td><b>param varchar(2000)</b></td>
				<td>A PROJ4 description of the SRS</td>
			</tr>
		</table>
  *
  *		Of course you can easily create new SRS definitions by adding rows to
  this table. E.g. a set of default SRS is provided with GST Storage as *
  insert script like the following: \code INSERT INTO gst.srs (code_type, code,
  name, param) VALUES ('EPSG', '4326', 'WGS84', '+proj=longlat +ellps=WGS84
  +datum=WGS84 +no_defs'); INSERT INTO gst.srs (code_type, code, name, param)
  VALUES ('EPSG', '32633', 'WGS 84 / UTM zone 33N', '+proj=utm +zone=33
  +ellps=WGS84 +datum=WGS84 +units=m +no_defs '); INSERT INTO gst.srs
  (code_type, code, name, param) VALUES ('EPSG', '32634', 'WGS 84 / UTM zone
  34N', '+proj=utm +zone=34 +ellps=WGS84 +datum=WGS84 +units=m +no_defs');
			INSERT INTO gst.srs (code_type, code, name, param) VALUES ('EPSG',
  '31467', 'DHDN / 3-degree Gauss-Kruger zone 3', '+proj=tmerc +lat_0=0 +lon_0=9
  +k=1 +x_0=3500000 +y_0=0 +ellps=bessel +datum=potsdam +units=m +no_defs');
		\endcode
  *
  *		The key of table SRS is automatic incrementing on insert. Thus you don't
  need to define a value for srs.id in insert commands. *		@note The
  automatic increment starts with 20.000. SRS with a key below are predefined
  SRS of GST Storage Setup. All above are SRS added on runtime.
  *
  *
  *		\section ClientAPI
  *
  *		There is a more easy way of handling SRS than providing insert scripts.
  *		Using the API is more comfortable and allows to define SRS in different
  styles than PROJ4. Here the class SRS comes in.
  *
  *		\subsection construct Constructing & Storing a SRS
  *			To construct a new SRS create a definition with the help of class
  SRS which is the SRS factory and base class. E.g. \code SRSPtr new_wgs84 =
  SRS::FromWKT("GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS
  84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],"
												"AUTHORITY[\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],"
												"UNIT[\"degree\",0.01745329251994328,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4326\"]]");
				\endcode
  *			(You can use any WKT definition that is compatible with GDAL here.)
  *
  *			To insert this SRS into the table SRS, use the
  ClientUtils::NetworkInterface. \code
				//NetworkPtr nif; //is still created and connected
				Geometry::GSTSRSRefPtr registered;
				registered = nif->InsertSRS(new_wgs84, "My favorite and new
  SRS", "EPSG", "4326"); \endcode *			First parameter is your defined SRS.
  Next 3 parameters handles user label and code system and code key. If there is
  a code system for your SRS available (like EPSG), you can use it with the key
  value pair (code_type, code_value) in 3rd and 4th parameter of InsertSRS().
  *
  *			After InsertSRS() your SRS is inserted in the table srs. You'll get
  back a reference to a GSTSRSRef which will have the auto increment key of the
  new created SRS. *			You can get it by \code registered->getID();
			\endcode
  *
  *			Now your SRS is registered and you can use it. E.g. creating classes
  in that reference system or upload features and specify this SRS as features
  encoding.
  *
  *			@see ClientUtils::NetworkInterface::UploadNewFeature(),
  ClientUtils::NetworkInterface::GetFeature(), GeometryBuilder::setSRS()
  *
  *			@note For uploading and downloading features you don't need to store
  your SRS. Just construct your SRS by a SRS factory function, like FromWKT()
  and pass it *			to the ClientUtils::NetworkInterface method. Just for
  creation of a class with a custom SRS, you need to have a registered SRS (or
  NoSRS if no spatial reference is required).
  *
  *		\subsection Inheritance
  *			The class SRS provides factory functions for srs description. On the
  other hand it also is the base class, used by any
  ClientUtils::NetworkInterface method. *			\image html
  srs_inheritance_graph.png "Inheritance of SRS"
  *
  *			@see SRS::EmptySRS()
  *			@see SRS::FromWKT()
  *			@see SRS::FromPROJ4()
  *			@see SRS::FromGSTSRSID()
  *
  *
  *		\subsection list List SRS
  *			Getting a list of all SRS is quite easy. Simply call
  ClientUtils::NetworkInterface::ListAllSRS().
  *
  *
  *		\subsection update Updating / Deletion of SRS
  *			You can update or delete registered SRS. Both methods
  ClientUtils::NetworkInterface::UpdateSRS() and
  ClientUtils::NetworkInterface::DeleteSRS() are using the key for SRS matching.
  *		@}
  */

class SRS;
class NoSRS;
class GSTSRSRef;
class WKTSRSDef;
class PROJ4SRSDef;
typedef boost::shared_ptr<SRS> SRSPtr;

/**
 * This is the base class for Spatial Reference Systems. With it you can
 * construct a SRS description by the Members FromWKT(), FromPROJ4(),
 * FromGSTSRSID().
 *
 * @note For more information about this class please refer to \ref refSRS
 * "Spatial Reference Handling"
 */
class GST_API_EXPORT SRS
{
public:
	enum Type
	{
		NOSRS = 0,
		GSTID,
		WKT,
		PROJ4,
		ESRIWKT
	};

	/**
	 * Generates an encoding string of the SRS.
	 *
	 * @note For developers: Implement me in derived implementations!
	 */
	virtual std::string encode() const = 0;
	virtual std::string ToString() const = 0;
	virtual std::string getDescription() const
	{
		return "";
	}
	virtual Type getType() const;
	virtual bool operator==(const SRS &other) const;

	///@name static c'tors
	//@{
	static SRSPtr EmptySRS();
	/**
	 * Constructs SRS from encoding string.
	 * @see encode
	 */
	static SRSPtr FromEncode(const std::string &encodeStr);
	static SRSPtr FromWKT(const std::string &wktDesc);
	static SRSPtr FromPROJ4(const std::string &prj4Desc);
	static SRSPtr FromGSTSRSID(long gst_srs_ident);
	static SRSPtr FromESRIWKT(const std::string &wktDesc);
	static SRSPtr SafePtr(SRSPtr srs);
	//@}

	/// boost serialization access
	template<typename Archive>
	void serialize(Archive &ar, const unsigned int version);

	virtual ~SRS()
	{
	}

protected:
	Type srs_type;

	SRS(const SRS::Type srs_type);
	SRS()
	{
	}

	friend class boost::serialization::access;
};
BOOST_SERIALIZATION_ASSUME_ABSTRACT(SRS);

template<typename Archive>
void SRS::serialize(Archive &ar, const unsigned int version)
{
	ar &BOOST_SERIALIZATION_NVP(srs_type);

	if(version < 2 && srs_type == ESRIWKT)
	{
		throw exceptions::GSTRuntimeException(
			__METHOD_NAME__,
			"Can not serialize a ESRI WKT with this API version.");
	}
}

/**
 * Defines an non defined SRS. This means there is no SRS specified.
 * For inserts and downloads of features with a NoSRS it is assumed,
 * that they have the same SRS as the class your downloading from / uploading
 * to.
 */
class GST_API_EXPORT NoSRS : public SRS
{
	friend class boost::serialization::access;

public:
	static const char EncodeIdent;

	NoSRS();
	virtual std::string encode() const;
	virtual std::string ToString() const;

	/// boost serialization access
	template<typename Archive>
	void serialize(Archive &ar, const unsigned int version);
};
typedef boost::shared_ptr<NoSRS> NoSRSPtr;

template<typename Archive>
void NoSRS::serialize(Archive &ar, const unsigned int version)
{
	ar &boost::serialization::make_nvp(
		"srs_base", boost::serialization::base_object<SRS>(*this));
}

/**
 * This is a registered SRS. It has a key, representing this SRS in the
 * database.
 */
class GST_API_EXPORT GSTSRSRef : public SRS
{
	friend class boost::serialization::access;

	long id;

	GSTSRSRef() : SRS(SRS::GSTID)
	{
	}

public:
	static const char EncodeIdent;

	GSTSRSRef(const long &GST_SRS_ident);
	virtual std::string encode() const;
	virtual std::string ToString() const;

	long getID() const;
	void setID(const long &id);

	/// boost serialization access
	template<typename Archive>
	void serialize(Archive &ar, const unsigned int version);
};
typedef boost::shared_ptr<GSTSRSRef> GSTSRSRefPtr;

template<typename Archive>
void GSTSRSRef::serialize(Archive &ar, const unsigned int version)
{
	ar &boost::serialization::make_nvp(
		"srs_base", boost::serialization::base_object<SRS>(*this));
	ar &BOOST_SERIALIZATION_NVP(id);
}

/**
 * A Spatial Reference System description by the well known text format (WKT
 * format).
 */
class GST_API_EXPORT WKTSRSDef : public SRS
{
	friend class boost::serialization::access;

	std::string wkt;

	WKTSRSDef() : SRS(SRS::WKT)
	{
	}

public:
	static const char EncodeIdent;

	WKTSRSDef(const std::string &wktDesc);
	virtual std::string encode() const;
	virtual std::string ToString() const;
	virtual std::string getDescription() const;

	/// boost serialization access
	template<typename Archive>
	void serialize(Archive &ar, const unsigned int version);
};
typedef boost::shared_ptr<WKTSRSDef> WKTSRSDefPtr;

template<typename Archive>
void WKTSRSDef::serialize(Archive &ar, const unsigned int version)
{
	ar &boost::serialization::make_nvp(
		"srs_base", boost::serialization::base_object<SRS>(*this));
	ar &BOOST_SERIALIZATION_NVP(wkt);
}

/**
 * A Spatial Reference System description by the PROJ4 format
 *	http://trac.osgeo.org/proj/.
 */
class GST_API_EXPORT PROJ4SRSDef : public SRS
{
	friend class boost::serialization::access;

	std::string prj4DescStr;

	PROJ4SRSDef() : SRS(SRS::PROJ4)
	{
	}

public:
	static const char EncodeIdent;

	PROJ4SRSDef(const std::string &prj4Desc);
	virtual std::string encode() const;
	virtual std::string ToString() const;
	virtual std::string getDescription() const;

	/// boost serialization access
	template<typename Archive>
	void serialize(Archive &ar, const unsigned int version);
};
typedef boost::shared_ptr<PROJ4SRSDef> PROJ4SRSDefPtr;

template<typename Archive>
void PROJ4SRSDef::serialize(Archive &ar, const unsigned int version)
{
	ar &boost::serialization::make_nvp(
		"srs_base", boost::serialization::base_object<SRS>(*this));
	ar &BOOST_SERIALIZATION_NVP(prj4DescStr);
}

/**
 * A Spatial Reference System description by the ESRI WKT format
 */
class GST_API_EXPORT ESRIWKTDef : public SRS
{
	friend class boost::serialization::access;

	std::string wktDescStr;

	ESRIWKTDef() : SRS(SRS::ESRIWKT)
	{
	}

public:
	static const char EncodeIdent;

	ESRIWKTDef(const std::string &wktDescStr);
	virtual std::string encode() const;
	virtual std::string ToString() const;
	virtual std::string getDescription() const;

	/// boost serialization access
	template<typename Archive>
	void serialize(Archive &ar, const unsigned int version);
};
typedef boost::shared_ptr<ESRIWKTDef> ESRIWKTDefPtr;

template<typename Archive>
void ESRIWKTDef::serialize(Archive &ar, const unsigned int version)
{
	ar &boost::serialization::make_nvp(
		"srs_base", boost::serialization::base_object<SRS>(*this));
	ar &BOOST_SERIALIZATION_NVP(wktDescStr);
}
} // namespace Geometry
} // namespace GST

BOOST_CLASS_EXPORT_KEY(GST::Geometry::SRS);
BOOST_CLASS_EXPORT_KEY(GST::Geometry::NoSRS);
BOOST_CLASS_EXPORT_KEY(GST::Geometry::GSTSRSRef);
BOOST_CLASS_EXPORT_KEY(GST::Geometry::WKTSRSDef);
BOOST_CLASS_EXPORT_KEY(GST::Geometry::PROJ4SRSDef);
BOOST_CLASS_EXPORT_KEY(GST::Geometry::ESRIWKTDef);

BOOST_CLASS_VERSION(GST::Geometry::SRS, 2)
BOOST_CLASS_VERSION(GST::Geometry::NoSRS, 1)
BOOST_CLASS_VERSION(GST::Geometry::GSTSRSRef, 1)
BOOST_CLASS_VERSION(GST::Geometry::WKTSRSDef, 1)
BOOST_CLASS_VERSION(GST::Geometry::PROJ4SRSDef, 1)
BOOST_CLASS_VERSION(GST::Geometry::ESRIWKTDef, 1)

#endif // _SRS_h__
