#ifndef _TABLE_CONSTRAINEDATTRIBUTE_DESC_H_
#define _TABLE_CONSTRAINEDATTRIBUTE_DESC_H_

#include "Client/ClientUtils/Network/TableAttributeVal.h"
#include <Client/ClientUtils/Network/ObjectPropertyDesc.h>
#include <Client/ClientUtils/Network/SimplexPropertyDesc.h>
#include <Client/ClientUtils/Network/TableAttributeDesc.h>
#include <GSTenums.h>
#include <Utils/Converter.h>
#include <buildspec.h>
#include <exceptions/GSTRuntimeException.h>
#include <map>
#include <string>

#include <boost/algorithm/string.hpp>
#include <boost/archive/xml_iarchive.hpp>
#include <boost/archive/xml_oarchive.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/shared_ptr.hpp>

#include <Geometry/boost_serialize_includes.hpp>

namespace GST
{
namespace ClientUtils
{

class ForeignKey;

/**
 * A relation constructing a foreign key Item.
 *
 * \li targetColumn: A table column referencing a primary key column of a third
 * table (targetColumn) \li sourceColumn: A primary key column of a third party
 * table (keyColumn) that is referenced by the pair::first value
 */
class GST_API_EXPORT ForeignKeyItem
{
	friend class boost::serialization::access;

public:
	typedef int CPLinkId;

private:
	TableAttributeDescPtr targetColumn;
	TableAttributeKeyDescPtr sourceColumn;
	CPLinkId constrained_property_table_id;

public:
	ForeignKeyItem();

	ForeignKeyItem(TableAttributeDescPtr _targetColumn,
				   TableAttributeKeyDescPtr _sourceColumn);

	/**
	 * constructs a persistent foreign key
	 *
	 *		@parameter _targetColumn                    description for column
	 *in the feature class table, that corresponds to key column in the source
	 *table
	 *		@parameter _sourceColumn                    description of primary
	 *key column in the source table
	 *		@parameter _constrained_property_table_id   value of id in
	 *gst.constrained_properties_link table  from the item describing
	 *										            relationship between
	 *target column and key column
	 */
	ForeignKeyItem(TableAttributeDescPtr _targetColumn,
				   TableAttributeKeyDescPtr _sourceColumn,
				   int _constrained_property_table_id);
	ForeignKeyItem(const ForeignKeyItem &other);
	TableAttributeDescPtr getTargetColumn() const;
	TableAttributeKeyDescPtr getSourceColumn() const;
	std::string getTargetKeyColumnName() const;
	std::string getSourceKeyColumnName() const;
	std::string getSourceKeyColumnTableName() const;
	int getCPLinkId() const;
	bool isPersistent() const;
	void changeDatabaseName(const std::string &dbname);

	bool operator==(const ForeignKeyItem &other) const;

private:
	template<typename Archive>
	void serialize(Archive &ar, const unsigned int version)
	{
		// If you change members here do not forget to increment object version
		// (see bottom of this file)
		ar &boost::serialization::make_nvp("target_column", this->targetColumn);
		ar &boost::serialization::make_nvp("source_column", this->sourceColumn);
	}
};
typedef boost::shared_ptr<ForeignKeyItem> ForeignKeyItemPtr;

/**
 * A list of ForeignkeyItems constructing a foreign key.
 *
 */

class GST_API_EXPORT ForeignKey
{
	friend class boost::serialization::access;

public:
	typedef enum FKTYPE
	{
		NOACTION = 0,
		RESTRICT = 1,
		SETNULL = 2
	} FKTYPE;

private:
	std::vector<ForeignKeyItemPtr> foreignKey;
	FKTYPE fk_type;

public:
	ForeignKey();
	ForeignKey(FKTYPE _fk_type);
	ForeignKey(const ForeignKey &other);
	~ForeignKey();

	bool isPersistent() const;
	bool targetIsNotNULL() const;
	bool operator==(const ForeignKey &other) const;
	void AddForeignKeyItem(ForeignKeyItemPtr key);
	void AddForeignKeyItem(TableAttributeDescPtr targetColumn,
						   TableAttributeKeyDescPtr keyColumn);
	std::string getSourceTableName() const;
	std::string getTargetKeyColumnName(int keyPosition) const;
	std::string getSourceKeyColumnName(int keyPosition) const;
	int getSize() const;
	FKTYPE getFKType() const;
	std::string getFKTypeAsString() const;
	std::vector<ForeignKeyItem::CPLinkId> getPersistentTableIds() const;
	TableAttributeDescPtr getTargetColumn(int keyPosition) const;
	ForeignKeyItemPtr getTargetColumn(
		const TableAttributeDesc &pkComponent) const;
	TableAttributeKeyDescPtr getSourceColumn(int keyPosition) const;
	std::string GlueTargetColumnNames(const std::string &glue
									  = std::string(",")) const;
	std::string GlueSourceColumnNames(const std::string &glue
									  = std::string(",")) const;
	void setFKType(FKTYPE _fktype);
	/**
	 *	Replaces the database name in the foreign key component
	 */
	void changeDatabaseName(const std::string &dbname);
	void reorderForeignKey(TableAttributeKeyDescListPtr rightorder);

private:
	template<typename Archive>
	void serialize(Archive &ar, const unsigned int version)
	{
		// If you change members here do not forget to increment object version
		// (see bottom of this file)
		ar &boost::serialization::make_nvp("fk_type", this->fk_type);
		ar &boost::serialization::make_nvp("foreignKey", this->foreignKey);
	}
};

typedef boost::shared_ptr<ForeignKey> ForeignKeyPtr;

// compare operator at the bottom

/**
  * This is the basis class for the constrained property pattern in GST.
  *
  * The source_map_column presents the referenced column of a third party table.
  This column is mapped
  * to the feature class by the foreign keys of the referenced table. The
  derived classes ConstrainedSimplexPropertyDesc and
  ConstrainedObjectPropertyDesc
  * presents the column in the feature class (="constrained column" in GST
  Desktop).
  *
  * The source_map_column can be also a column of a view in order
  * to hide the complexity of third party models by using views like prepared
  table joins, prepared  filters (where clause) etc.
  * To reference a view (instead of a table) set the source_view_name to the
  referenced view (see setSourceViewName()).
  * If source_view_name is empty (default) the source_map_column refers to a
  table. If source_view_name refers to an existing view,
  * source_map_column refers to a column in the view.

  * The limitation to the view is:
  *		\li The view must have <b>the complete primary key</b> of the referenced
  table <b>without using aliases for the primary keys</b> (so all primary key *
  columns of the referenced table have to occur with the same name in the view)
  *
  * The source_key_columns is the foreign key used to reference the third party
  table (so it also represents the primary keys of the third party table).
  * Use AddKeyColumn() to build up the foreign key.
  * The source_key_columns should be built up by columns being unique for each
  record in the referenced table or the referenced view. (Being the primary key
  * or having a unique constraint by the database is not required but is
  suggested.)
  *
  * fk_type represent the constrains handled by the database on delete of the
  records in the referenced table (default: NOACTION). *	Please refer to
  http://en.wikipedia.org/wiki/Foreign_key#RESTRICT ff.
  *
  * @see ConstrainedObjectPropertyDesc
  * @see ConstrainedSimplexPropertyDesc
  * @see ConstrainedTableAttributeVal
  */
class GST_API_EXPORT TableConstrainedAttributeDesc
{
	friend class boost::serialization::access;

public:
	TableConstrainedAttributeDesc(void);
	TableConstrainedAttributeDesc(const TableConstrainedAttributeDesc &other);
	TableConstrainedAttributeDesc(TableAttributeDescListPtr map_columns,
								  const ForeignKey::FKTYPE fk_type);
	TableConstrainedAttributeDesc(TableAttributeDescListPtr map_columns,
								  const ForeignKey::FKTYPE fk_type,
								  const std::string &viewname);
	virtual ~TableConstrainedAttributeDesc(void);

	std::string GetSourceColumnTableName() const;
	std::vector<std::string> GetSourceColumnNames() const;
	// TODO: the one below is a bit tricky, I am not sure if we can change the
	// key of TableAttributeDescListPtr easily
	void setSourceColumnName(const std::string &name);
	TableAttributeDescListPtr GetSourceColumns() const;

	ForeignKeyPtr GetForeignKey() const;
	void SetForeignKey(ForeignKeyPtr fk);
	/// add foreign key item
	void AddForeignKeyColumn(TableAttributeDescPtr targetColumn,
							 TableAttributeKeyDescPtr keyColumn);
	void AddForeignKeyColumn(ForeignKeyItemPtr fk);
	void setSourceViewName(std::string viewname);
	std::string GetSourceViewName() const;
	std::string GetSourceTableName() const;

	ForeignKey::FKTYPE GetFKType() const;
	void SetFKType(ForeignKey::FKTYPE fktype);
	virtual bool operator==(const TableConstrainedAttributeDesc &other) const;

	virtual void changeDatabaseName(const std::string &dbname);

private:
	/// name serialized members
	//@{
	TableAttributeDescListPtr source_map_columns;
	ForeignKeyPtr foreign_key;
	/**	if TableConstrainedAttributeDesc is persistent this vector is sized like
	 * source_key_columns associating a ForeignKey part to a record in table
	 * gst.constrained_properties_link  */
	//	std::vector<CPLinkId> constrained_property_table_ids;
	std::string source_view_name;
	//@}

	template<typename Archive>
	void serialize(Archive &ar, const unsigned int version)
	{
		// If you change members here do not forget to increment object version
		// (see bottom of this file)
		ar &boost::serialization::make_nvp("source_view_name",
										   this->source_view_name);
		ar &boost::serialization::make_nvp("source_map_columns",
										   this->source_map_columns);
		if(version < 3)
		{
			throw exceptions::GSTRuntimeException(
				__METHOD_NAME__, "Can not read template below version 3.");
		}
		ar &boost::serialization::make_nvp("foreign_key", this->foreign_key);
	}
};
typedef boost::shared_ptr<TableConstrainedAttributeDesc>
	TableConstrainedAttributeDescPtr;

//---------------------------------------------------------------------------------------
/**
 * This is a TableConstrainedAttributeDesc presenting the mapping of a third
 * party column to the feature simplex property. This column is also a non real
 * (virtual) column represented by SimplexPropertyDesc, where getName() holds
 * the displayed name in feature class and getTableName() the feature classes
 * name. The left members (dtype, dimension, notNull) are equal to the
 * source_map_column this property is representing.
 */
class GST_API_EXPORT ConstrainedSimplexPropertyDesc
	: public TableConstrainedAttributeDesc
	, public SimplexPropertyDesc
{
	friend class boost::serialization::access;

public:
	ConstrainedSimplexPropertyDesc(void)
		: SimplexPropertyDesc()
		, TableConstrainedAttributeDesc()
	{
	}
	ConstrainedSimplexPropertyDesc(const SimplexPropertyDesc &spxProp,
								   const TableConstrainedAttributeDesc &cp)
		: SimplexPropertyDesc(spxProp)
		, TableConstrainedAttributeDesc(cp)
	{
	}
	ConstrainedSimplexPropertyDesc(const std::string &name,
								   const std::string &tablename,
								   DType type,
								   int dimension,
								   bool notNull,
								   Geometry::PropertyAlignments alignment,
								   TableAttributeDescListPtr map_columns,
								   ForeignKey::FKTYPE fk_type
								   = ForeignKey::NOACTION,
								   const std::string &viewname = "",
								   const std::string &alternativeType = "")
		: SimplexPropertyDesc(name,
							  tablename,
							  type,
							  dimension,
							  notNull,
							  alignment,
							  alternativeType)
		, TableConstrainedAttributeDesc(map_columns, fk_type, viewname)
	{
	}
	~ConstrainedSimplexPropertyDesc(void)
	{
	}
	virtual bool operator==(const ConstrainedSimplexPropertyDesc &other) const
	{
		return (SimplexPropertyDesc::operator==(other)
				&& TableConstrainedAttributeDesc::operator==(other));
	}
	virtual bool isConstrained() const;

private:
	template<typename Archive>
	void serialize(Archive &ar, const unsigned int version)
	{
		// If you change members here do not forget to increment object version
		// (see bottom of this file)
		ar &boost::serialization::make_nvp(
			"SimplexPropertyDesc",
			boost::serialization::base_object<SimplexPropertyDesc>(*this));
		ar &boost::serialization::make_nvp(
			"ConstrainedSimplexProperty",
			boost::serialization::base_object<TableConstrainedAttributeDesc>(
				*this));
	}
};
typedef boost::shared_ptr<ConstrainedSimplexPropertyDesc>
	ConstrainedSimplexPropertyDescPtr;

//---------------------------------------------------------------------------------------
/**
 * This is a TableConstrainedAttributeDesc presenting the mapping of a third
 * party column to the feature object property. This column is also a non real
 * (virtual) column represented by ObjectPropertyDesc, where getName() holds the
 * displayed name in feature class and getTableName() the feature classes name.
 * The left members (dtype, dimension, notNull) are equal to the
 * source_map_column this property is representing.
 */
class GST_API_EXPORT ConstrainedObjectPropertyDesc
	: public TableConstrainedAttributeDesc
	, public ObjectPropertyDesc
{
	friend class boost::serialization::access;

public:
	ConstrainedObjectPropertyDesc(void)
		: TableConstrainedAttributeDesc()
		, ObjectPropertyDesc()
	{
	}
	ConstrainedObjectPropertyDesc(const ObjectPropertyDesc &objProp,
								  const TableConstrainedAttributeDesc &cp)
		: ObjectPropertyDesc(objProp)
		, TableConstrainedAttributeDesc(cp)
	{
	}
	ConstrainedObjectPropertyDesc(const ConstrainedObjectPropertyDesc &other)
		: ObjectPropertyDesc(static_cast<const ObjectPropertyDesc &>(other))
		, TableConstrainedAttributeDesc(other)
	{
	}
	ConstrainedObjectPropertyDesc(const std::string &name,
								  const std::string &tablename,
								  DType type,
								  int dimension,
								  bool notNull,
								  TableAttributeDescListPtr map_columns,
								  ForeignKey::FKTYPE fk_type
								  = ForeignKey::NOACTION,
								  const std::string &viewname = "",
								  const std::string &alternativeType = "",
								  const int &colIndex = -1,
								  const long &id = -1)
		: ObjectPropertyDesc(name,
							 tablename,
							 type,
							 dimension,
							 notNull,
							 alternativeType,
							 colIndex,
							 id)
		, TableConstrainedAttributeDesc(map_columns, fk_type, viewname)
	{
	}
	~ConstrainedObjectPropertyDesc(void)
	{
	}
	virtual bool operator==(const ConstrainedObjectPropertyDesc &other) const
	{
		return (ObjectPropertyDesc::operator==(other)
				&& TableConstrainedAttributeDesc::operator==(other));
	}
	virtual bool isConstrained() const;

private:
	template<typename Archive>
	void serialize(Archive &ar, const unsigned int version)
	{
		// If you change members here do not forget to increment object version
		// (see bottom of this file)
		ar &boost::serialization::make_nvp(
			"ObjectPropertyDesc",
			boost::serialization::base_object<ObjectPropertyDesc>(*this));
		ar &boost::serialization::make_nvp(
			"ConstrainedObjectProperty",
			boost::serialization::base_object<TableConstrainedAttributeDesc>(
				*this));
	}
};
typedef boost::shared_ptr<ConstrainedObjectPropertyDesc>
	ConstrainedObjectPropertyDescPtr;

} // namespace ClientUtils
} // namespace GST

BOOST_CLASS_EXPORT_KEY(GST::ClientUtils::ForeignKeyItem);
BOOST_CLASS_EXPORT_KEY(GST::ClientUtils::ForeignKey);
BOOST_CLASS_EXPORT_KEY(GST::ClientUtils::TableConstrainedAttributeDesc);
BOOST_CLASS_EXPORT_KEY(GST::ClientUtils::ConstrainedSimplexPropertyDesc);
BOOST_CLASS_EXPORT_KEY(GST::ClientUtils::ConstrainedObjectPropertyDesc);

BOOST_CLASS_VERSION(GST::ClientUtils::ForeignKeyItem, 1)
BOOST_CLASS_VERSION(GST::ClientUtils::ForeignKey, 1)
BOOST_CLASS_VERSION(GST::ClientUtils::TableConstrainedAttributeDesc, 3)
BOOST_CLASS_VERSION(GST::ClientUtils::ConstrainedSimplexPropertyDesc, 3)
BOOST_CLASS_VERSION(GST::ClientUtils::ConstrainedObjectPropertyDesc, 3)

#endif
