////////////////////////////////////////////////////////////////
// Implementation of the OGC SimpleFeature in SAGA-GIS
//
// Author:
// Stefan Liersch: stliersch@freenet.de, stefan.liersch@ufz.de
// 2006-11-01
////////////////////////////////////////////////////////////////

//--------------------------------------------------------------
//				ToDo
//--------------------------------------------------------------
// - read spatial_ref_sys: if SRID does not exist in spatial_ref_sys table
//   the program will shut down!
//
// - correcting read MultiPolygon !!!
//   if a MultiPolygon contains a polygon with a ring it will not be read correctly
//   possible solution:
//   - implement new bool feature (part) attribute true if ordinary polygon
//     and false if ring polygon
//     implementing inner ring elements as sub-features
//   - new read procedure is necessary for that, too.
//
// - Initialize() Assign SQL table eventuell überflüssig
// - GEOMETRYCOLLECTION not yet implemented
// - implement storage_type
// - implementation of GID, ESEQ, SEQ not really necessary for this
//   SimpleFeature implementation
//--------------------------------------------------------------

#include "simple_feature.h"

/*
#include <sstream>			// used by function StringToDouble()
using std::istringstream;	// used by function StringToDouble()

#include <iostream>
#include <fstream>
using std::ofstream;
using std::endl;
*/
CSimpleFeature::CSimpleFeature(PGconn *conn, const char *tablename)
								: Csqltable(tablename) 
{
	this->conn	= conn;
	FeatureName = tablename;
	// read to simple feature (SF) and SQLTable corresponding tables
	// and assign SF and SQLTable parameters
	if (Initialize(tablename))
	{
		SelectAssignMethod(GeoType);
	}
}

CSimpleFeature::~CSimpleFeature(void)
{
}

//--------------------------------------------------------------

bool CSimpleFeature::Initialize(const char *tablename)
{
	const int FIELDTYPE_GEOMETRY = 16632;

	//-----------------------------------------------------
	// read table GEOMETRY_COLUMNS
	//-----------------------------------------------------
	char query[4096] = "SELECT * FROM GEOMETRY_COLUMNS "
			  "WHERE f_table_name = '";
	strcat(query,tablename);
	strcat(query, "'");

	// send query
	res = PQexec(conn, query);

	// check whether SQL query was successfull or not
	if (!res || PQresultStatus(res) != PGRES_TUPLES_OK)
	{
		PQclear(res);
		return(false);
	} else
	{
		NN					= PQgetvalue(res,0,0);
		TableCatalog		= PQgetvalue(res,0,1);
		TableSchema			= PQgetvalue(res,0,2);
		GeometryColumnName	= PQgetvalue(res,0,3);
		CoordDimension		= atoi(PQgetvalue(res,0,4));
		SRID				= atoi(PQgetvalue(res,0,5));
		SRID_asString		= PQgetvalue(res,0,5);
		GeoType_asString	= PQgetvalue(res,0,6);
		GeoType				= AssignGeoType(GeoType_asString);
		ETYPE				= AssignETYPE(GeoType);

		PQclear(res);
	}

	//-----------------------------------------------------
	// read table SPATIAL_REF_SYS
	//-----------------------------------------------------
	if (SRID > 0)
	{
		char query1[4096] = "SELECT * FROM SPATIAL_REF_SYS "
				"WHERE srid = ";
		strcat(query1,SRID_asString.c_str());

		// send query
		res = PQexec (conn, query1);

		// check whether SQL query was successfull or not
		if (!res || PQresultStatus(res) != PGRES_TUPLES_OK)
		{
			PQclear(res);
			return(false);
		} else
		{
			SRID_PK				= atoi(PQgetvalue(res,0,0));
			Auth_Name			= PQgetvalue(res,0,1);
			Auth_SRID			= atoi(PQgetvalue(res,0,2));
			srText				= PQgetvalue(res,0,3);
			proj4text			= PQgetvalue(res,0,4);
			
			PQclear(res);
		}
	}

	//-----------------------------------------------------
	// Assign SQLTable values
	//-----------------------------------------------------
	// Assign all Attributes to feature table (SQLTable)
	// except Geometry Column!!!
	// Geometry Column will be stored in 'spatialstr'

	char query2[255] = { "SELECT * FROM "};
	strcat(query2, TableName.c_str());

	res = PQexec(conn, query2);
	// check whether SQL query was successfull or not
	if (!res || PQresultStatus (res) != PGRES_TUPLES_OK)
	{
		// error
		PQclear(res);
		return(false);
	} else
	{
		ncols	= PQnfields(res);
		nrows	= PQntuples(res);

		resize_fields(ncols, nrows);

		for (int x = 0; x < ncols; x++)
		{
			fieldname[x]	= PQfname(res,x);
			fieldtype[x]	= pgdb->GetFieldTypeString(res,x);
			fieldtypeInt[x] = PQftype(res,x);
			if (fieldtypeInt[x] == FIELDTYPE_GEOMETRY)
				GeometryColumnNumber = x;
		}

		// assign field values to feature table matrix
		for (int col = 0; col < ncols; col++)
		{
			if (fieldtypeInt[col] != FIELDTYPE_GEOMETRY)
			{
				for (int row = 0; row < nrows; row++)
					matrix[col][row] = PQgetvalue(res,row,col);
			}
		}
	}

	//-----------------------------------------------------
	// read Geometry / Spatial Column as WKT
	//-----------------------------------------------------
	// reading the geometry column as WellKnownText (WKT)
	// storing as WKT in string: 'spatialstr'
	char spat_query[4096] = { "SELECT " };
	
	// build SQL Query
	for (int i = 0; i < ncols; i++)
	{
		// searching for field type "geometry" OID = 16632)
		if (fieldtypeInt[i] != FIELDTYPE_GEOMETRY)
		{
			fieldname[i] = PQfname(res,i);
			strcat (spat_query, fieldname[i].c_str());
			strcat (spat_query, ", ");
		} else
		{
			fieldname[i] = PQfname(res,i);
			strcat(spat_query, "AsText(");
			strcat(spat_query, fieldname[i].c_str());
			strcat(spat_query, ") AS ");
			strcat(spat_query, fieldname[i].c_str());
			strcat(spat_query, " FROM ");
			strcat(spat_query, FeatureName.c_str());
		}
	}
	// send spat_query
	res = PQexec (conn, spat_query);
	// check whether SQL query was successfull or not
	if (!res || PQresultStatus (res) != PGRES_TUPLES_OK)
	{
		PQclear (res);
		return(false);
	} else {
		spatialstr = PQgetvalue(res,0,GeometryColumnNumber);
		PQclear(res);
	}

	return(true);
}




///////////////////////////////////////////////////////////////////////
//						GET FUNCTIONS
///////////////////////////////////////////////////////////////////////

string CSimpleFeature::GetFeatureName()
{
	return(FeatureName);
}

//---------------------------------------------------------------------

string CSimpleFeature::GetNN()
{
	// returns the first field (res,0,0) from GEOMETRY_COLUMNS
	return(NN);
}
//---------------------------------------------------------------------

string CSimpleFeature::GetTableCatalog()
{
	return(TableCatalog);
}

//---------------------------------------------------------------------

string CSimpleFeature::GetTableSchema()
{
	return(TableSchema);
}

//---------------------------------------------------------------------

string CSimpleFeature::GetGeometryColumnName()
{
	return(GeometryColumnName);
}
//---------------------------------------------------------------------

int CSimpleFeature::GetGeometryColumnNumber()
{
	return(GeometryColumnNumber);
}

//---------------------------------------------------------------------

int CSimpleFeature::GetCoordDimension()
{
	return(CoordDimension);
}

//---------------------------------------------------------------------

int CSimpleFeature::GetSRID()
{
	return(SRID);
}
//---------------------------------------------------------------------

string CSimpleFeature::GetSRID_asString()
{
	return(SRID_asString);
}
//---------------------------------------------------------------------

string CSimpleFeature::GetGeoType_asString()
{
	return(GeoType_asString);
}
//---------------------------------------------------------------------

int CSimpleFeature::GetGeoType()
{
	return(GeoType);
}
//---------------------------------------------------------------------



//---------------------------------------------------------------------

int CSimpleFeature::GetSRID_PK()
{
	return(SRID_PK);
}

//---------------------------------------------------------------------

string CSimpleFeature::GetAuth_Name()
{
	return(Auth_Name);
}

//---------------------------------------------------------------------

int CSimpleFeature::GetAuth_SRID()
{
	return(Auth_SRID);
}

//---------------------------------------------------------------------

string CSimpleFeature::GetsrText()
{
	return(srText);
}

//---------------------------------------------------------------------

string CSimpleFeature::Getproj4text()
{
	return(proj4text);
}
//---------------------------------------------------------------------

//---------------------------------------------------------------------

unsigned int CSimpleFeature::GetNumFeatures()
{
	return(nFeatures);
}
//---------------------------------------------------------------------

unsigned int CSimpleFeature::GetTotalNumPoints()
{
	return(nPoints);
}

//---------------------------------------------------------------------

unsigned int CSimpleFeature::GetNumPointsFeature(unsigned int nFeature)
{
	return(nPoints_Feature[nFeature]);
}
//---------------------------------------------------------------------

double CSimpleFeature::Get_X_Value(unsigned int nFeature, unsigned int PointID)
{
	return(SF_Point[nFeature][PointID].x);
}

//---------------------------------------------------------------------

double CSimpleFeature::Get_Y_Value(unsigned int nFeature, unsigned int PointID)
{
	return(SF_Point[nFeature][PointID].y);
}
//---------------------------------------------------------------------

string CSimpleFeature::GetWKT()
{
	return(spatialstr);
}
//---------------------------------------------------------------------

//---------------------------------------------------------------------
int CSimpleFeature::GetETYPE()
{
	return(ETYPE);
}

//---------------------------------------------------------------------


///////////////////////////////////////////////////////////////////////
//
//				Assign GeoType and Coordinates of WKT
//              to vector< vector<SF_TPoint> >
//
///////////////////////////////////////////////////////////////////////
//---------------------------------------------------------------------

//---------------------------------------------------------------------
// Assigns SF_TGeometry
SF_TGeometry CSimpleFeature::AssignGeoType(const string &s)
{
	string s1 = "POINT";
	string s2 = "LINESTRING";
	string s3 = "POLYGON";
	string s4 = "MULTIPOINT";
	string s5 = "MULTILINESTRING";
	string s6 = "MULTIPOLYGON";
	string s7 = "GEOMETRYCOLLECTION";

	if (s == s1)
		GeoType = SF_point;
	if (s == s2)
		GeoType = SF_linestring;
	if (s == s3)
		GeoType = SF_polygon;
	if (s == s4)
		GeoType = SF_MultiPoint;
	if (s == s5)
		GeoType = SF_MultiLinestring;
	if (s == s6)
		GeoType = SF_MultiPolygon;
	if (s == s7)
		GeoType = SF_GeometryCollection;

	return(GeoType);
}

//---------------------------------------------------------------------
int CSimpleFeature::AssignETYPE(SF_TGeometry gt)
{
	switch(gt)
	{
	case SF_point			: return(0); break;
	case SF_MultiPoint		: return(0); break;
	case SF_linestring		: return(1); break;
	case SF_MultiLinestring	: return(1); break;
	case SF_polygon			: return(2); break;
	case SF_MultiPolygon	: return(2); break;
	default					: return(-1);
	}
}
//---------------------------------------------------------------------

void CSimpleFeature::SelectAssignMethod(SF_TGeometry Geometry)
{
	if (Geometry == SF_point)
		AssignCoordinatesPoint(spatialstr);
	if (Geometry == SF_linestring)
		AssignCoordinatesLinestring(spatialstr);
	if (Geometry == SF_MultiPoint)
		AssignCoordinatesMultiPoint(spatialstr);
	if (Geometry == SF_polygon)
		AssignCoordinatesPolygon(spatialstr);
	if (Geometry == SF_MultiLinestring)
		AssignCoordinatesMultiLinestring(spatialstr);
	if (Geometry == SF_MultiPolygon)
		AssignCoordinatesMultiPolygon(spatialstr);
	//if (Geometry == GeometryCollection)
	//	AssignCoordinatesGeometryCollection(spatialstr);
}
//---------------------------------------------------------------------
void CSimpleFeature::AssignCoordinatesPoint(string &str_in)
{
	nFeatures			= 1;
	nPoints				= 1;
	nPoints_Feature.resize(nFeatures);
	nPoints_Feature[0]	= nPoints;
	Resize_SF_TPoint(nFeatures, nPoints_Feature);
	AssignCoordinatesSingleFeature(str_in);
}
//---------------------------------------------------------------------
void CSimpleFeature::AssignCoordinatesLinestring(string &str_in)
{
	nFeatures			= 1;
	nPoints				= CountCharInString(str_in, ',') + 1;
	nPoints_Feature.resize(nFeatures);
	nPoints_Feature[0]	= nPoints;
	Resize_SF_TPoint(nFeatures, nPoints_Feature);
	AssignCoordinatesSingleFeature(str_in);
}
//---------------------------------------------------------------------
void CSimpleFeature::AssignCoordinatesMultiPoint(string &str_in)
{
	// Maybe here the number of features is equal to the number of points
	nFeatures			= 1;
	nPoints				= CountCharInString(str_in, ',') + 1;
	nPoints_Feature.resize(nFeatures);
	nPoints_Feature[0]	= nPoints;
	Resize_SF_TPoint(nFeatures, nPoints_Feature);
	AssignCoordinatesSingleFeature(str_in);
}
//---------------------------------------------------------------------
void CSimpleFeature::AssignCoordinatesPolygon(string &str_in)
{
	nFeatures			= 1;
	nPoints				= CountCharInString(str_in, ',') + 1;
	nPoints_Feature.resize(nFeatures);
	nPoints_Feature[0]	= nPoints;
	Resize_SF_TPoint(nFeatures, nPoints_Feature);
	AssignCoordinatesSingleFeature(str_in);
}
//---------------------------------------------------------------------
void CSimpleFeature::AssignCoordinatesMultiLinestring(string &str_in)
{
	nFeatures			= CountCharInString(str_in, '(') - 1;
	//total number of points in entire SF
	nPoints				= CountCharInString(str_in, ',') + 1;
	// Assign number of points to each feature
	nPoints_Feature.resize(nFeatures);
	nPoints_Feature		= Assign_nPointsToFeature(str_in, nFeatures);

	Resize_SF_TPoint(nFeatures, nPoints_Feature);
	// Assign values (x, y coordinates) to features
	AssignCoordinatesMultiFeature(str_in, nFeatures);
}
//---------------------------------------------------------------------
void CSimpleFeature::AssignCoordinatesMultiPolygon(string &str_in)
{
	nFeatures			= (CountCharInString(str_in, '(') - 1) / 2;
	//total number of points in entire SF
	nPoints				= CountCharInString(str_in, ',') + 1;
	// Assign number of points to each feature
	nPoints_Feature.resize(nFeatures);
	nPoints_Feature		= Assign_nPointsToFeature(str_in, nFeatures);

	Resize_SF_TPoint(nFeatures, nPoints_Feature);
	// Assign values (x, y coordinates) to features
	AssignCoordinatesMultiFeature(str_in, nFeatures);
}
//---------------------------------------------------------------------
// Assigns coordinates to SF_Point.x and SF_Point.y
void CSimpleFeature::AssignCoordinatesSingleFeature(string &str_in)
{
	unsigned int	start = FindFirstCharPosition(str_in, '(');
	unsigned int	begin = 0;
	unsigned int	end = 0;
	string			conv;
	string			temp;
	unsigned int	coordIndex = 0;

	for (unsigned int i = start+1 ; i < str_in.length(); i++ )
	{
		if (IsNumber(str_in[i]))
		{
			// Get x_coord
			begin = i;
			temp = str_in.substr(begin,str_in.length()-begin);
			end = FindFirstCharPosition(temp,' ');
			conv = str_in.substr(begin, end);
			i += end;
			SF_Point[0][coordIndex].x = convert_sl::StringToDouble(conv);

			// Get y_coord
			begin = i;
			temp = str_in.substr(begin, str_in.length()-begin);
			if (coordIndex == nPoints - 1)
			{
				// if last tuple
				end = FindFirstCharPosition(temp, ')');
			} else {
				end = FindFirstCharPosition(temp,',');
			}
			conv = str_in.substr(begin, end);
			i += end;
			SF_Point[0][coordIndex].y = convert_sl::StringToDouble(conv);

			coordIndex++;
		}
	}
}
//---------------------------------------------------------------------
// Assigns coordinates to SF_Point.x and SF_Point.y
void CSimpleFeature::AssignCoordinatesMultiFeature(string &str_in, int nFeatures)
{
	unsigned int	begin	= 0;		// begin position of substring
	unsigned int	end		= 0;		// end position of substring
	string			temp	= str_in;	// temporary string (substring)
	string			conv;
	int	feature	= 0;		// feature counter
	unsigned int	blank	= 0;

	// count number of points in all features
	for (unsigned int i = 0; i < str_in.length() && feature < nFeatures; i++ )
	{
		for (int points = 0; points < nPoints_Feature[feature]; points++)
		{
			if (IsNumber(str_in[i]))
			{
				// Get x_coord
				begin = i;
				temp = str_in.substr(begin,str_in.length()-begin);
				blank = FindFirstCharPosition(temp,' ');
				conv = str_in.substr(begin, blank);
				i += blank;
				SF_Point[feature][points].x = convert_sl::StringToDouble(conv);

				// Get y_coord
				begin	= i;
				temp = str_in.substr(begin, str_in.length()-begin);
				if (points == nPoints_Feature[feature] - 1)
				{
					// if last tuple
					end = FindFirstCharPosition(temp, ')');
				} else {
					end = FindFirstCharPosition(temp,',');
				}
				conv = str_in.substr(begin, end);
				i += end;
				SF_Point[feature][points].y = convert_sl::StringToDouble(conv);
			} else {
				i++;
				points--;
			}	// end if (IsNumber...)
		}		// end for (int points...)
		feature++;
	}			
}
//---------------------------------------------------------------------
// Assigns the number of points to each feature in the SimpleFeature
vector_int CSimpleFeature::Assign_nPointsToFeature(string &str_in, int nFeatures)
{
	vector_int nP_Feature;
	nP_Feature.resize(nFeatures);
	unsigned int	begin	= 0;		// begin position of substring
	unsigned int	end		= 0;		// end position of substring
	string			temp	= str_in;	// temporary string (substring)
	int				feature	= 0;		// feature counter

	// count number of points in all features
	for (unsigned int i = 0; i < str_in.length() && feature < nFeatures; i++ )
	{
		begin	= i;
		end		= FindFirstCharPosition(temp, ')');
		if (GeoType == SF_MultiPolygon) end += 1;
		temp	= str_in.substr(begin, end);
		nP_Feature[feature] = CountCharInString(temp, ',') + 1;
		i		+= end + 1;
		temp	= str_in.substr(i, str_in.length()-i);
		feature++;
	}
	return(nP_Feature);
}
//---------------------------------------------------------------------

void CSimpleFeature::Resize_SF_TPoint(int nFeatures, vector_int nPoints_Feature)
{
	SF_Point.resize(nFeatures);
	for (int i = 0; i < nFeatures; i++)
		SF_Point[i].resize(nPoints_Feature[i]);
}
//---------------------------------------------------------------------

// returns the position of the first appearance
// of character 'search_string'
unsigned int CSimpleFeature::FindFirstCharPosition(string &s, char search_char)
{
	unsigned int	pos = 0;

	for (int i = 0 ; s[i] != '\0'; i++ )
	{
		if (s[i] != search_char)
		{
			pos ++;
		} else {
			return (pos);
		}
	}
	return(pos);
}
//---------------------------------------------------------------------
// Counts the number of search_char in str_in
unsigned int CSimpleFeature::CountCharInString(string &str_in, char search_char)
{
	unsigned int n = 0;

	for (int i = 0 ; str_in[i] != '\0'; i++ )
	{
		if (str_in[i] == search_char)
			n++;
	}
	return(n);
}
//---------------------------------------------------------------------
// returns true, if char c is a number
bool CSimpleFeature::IsNumber(char c)
{
	return(c >= '0' && c <= '9');
}
//---------------------------------------------------------------------

