/******************************** LICENSE ********************************

 Copyright 2007 European Centre for Medium-Range Weather Forecasts (ECMWF)

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at 

    http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.

 ******************************** LICENSE ********************************/

/*! \file ShapeDecoder.cc
    \brief Implementation of the Template class ShapeDecoder.
    
    Magics Team - ECMWF 2005
    
    Started: Mon 12-Dec-2005
    
    Changes:
    
*/


#include "ShapeDecoder.h"
#include "CustomisedPoint.h"
#include "shapefil.h"
#include "Polyline.h"

#include <boost/geometry/algorithms/make.hpp>
//#include <boost/geometry/algorithms/envelope.hpp>

// #define BOOST_VERSION 104700

ShapeDecoder::ShapeDecoder() :holes_(false)
{
}

ShapeDecoder::~ShapeDecoder() 
{
}


/*!
 Class information are given to the output-stream.
*/
void ShapeDecoder::print(ostream& out)  const
{
	out << "ShapeDecoder[";
	out << "]";
}

void ShapeDecoder::decode(const Transformation& transformation)
{
	vector<string> all;
	string no;
	decode(transformation, no, all);
}

/*! \brief Method to read llocation and names of state capitals
  
  \todo When we can handle Unicode we should change "nameascii" back to "name"
  
*/
void ShapeDecoder::customisedPoints(const std::set<string>&, CustomisedPointsList& out)
{
	try {
		SHPHandle hSHP;
		DBFHandle hDBF;
		char szTitle[12];

		int     nWidth, nDecimals;
		int     nShapeType, nEntities, i;
		double  adfMinBound[4], adfMaxBound[4];
		string shp = path_ + ".shp";
		string dbf = path_ + ".dbf";
		hSHP = SHPOpen( shp.c_str(), "rb" ); 
		hDBF = DBFOpen( dbf.c_str(), "rb" );
		if ( !hSHP || !hDBF ) {
	   		    MagLog::error() << "Can not open Shapefile " << path_ << endl;
	   		    return;
	   	}

		SHPGetInfo( hSHP, &nEntities, &nShapeType, adfMinBound, adfMaxBound );

		map<string, int> attributes;
		
		for( i = 0; i < DBFGetFieldCount(hDBF); i++ ) {
		            DBFFieldType eType;
		            eType = DBFGetFieldInfo( hDBF, i, szTitle, &nWidth, &nDecimals );
		            attributes.insert(make_pair(lowerCase(szTitle), i));
		}
		
		map<string, int>::iterator index  = attributes.find("nameascii");
		map<string, int>::iterator indexc = attributes.find("featurecla");

		vector<string> needs;
		needs.push_back("Admin-0 capital");

		for( i = 0; i < nEntities; i++ )
		{
			SHPObject *psShape;
			string capital  =  DBFReadStringAttribute(hDBF, i, indexc->second);
			bool add =false;

			for (vector<string>::const_iterator need = needs.begin(); need != needs.end(); ++need)
			{
				if (*need == capital ) {
					add = true;
					break;
				}
			}

			if ( !add )
				continue;

			psShape = SHPReadObject( hSHP, i );
			string name = ( index != attributes.end() ) ? DBFReadStringAttribute(hDBF, i, index->second) : "?";

			if (psShape->nVertices == 1)
			{
			    	CustomisedPoint* point = new CustomisedPoint();
			    	point->latitude(psShape->padfY[0]);
			    	point->longitude(psShape->padfX[0]);
			    	point->identifier(name);
			    	out.push_back(point);
			}
			SHPDestroyObject( psShape );
		    }
		    SHPClose( hSHP ); 
            DBFClose ( hDBF ); 
	}
	catch (...)
	{
		MagLog::error() << "Can not open Shapefile " << path_ << endl;
	}
	MagLog::dev() << "Shape file--->" << this->size() << endl; 
}

/*

  \sa Boundaries::operator()
*/
void ShapeDecoder::decode(const Transformation& transformation, const string& filter, const vector<string>& values)
{
	if ( !this->empty() ) return;
	try {
		SHPHandle  hSHP;
		DBFHandle  hDBF;
		char    szTitle[12];
		double  minx, miny, maxx, maxy;
		transformation.smallestBoundingBox(minx, miny, maxx, maxy);

		int     nWidth, nDecimals;
		int     nShapeType, nEntities, i, iPart;
		double  adfMinBound[4], adfMaxBound[4];

		string shp = path_ + ".shp";
		string dbf = path_ + ".dbf";
		hSHP = SHPOpen( shp.c_str(), "rb" ); 
		hDBF = DBFOpen( dbf.c_str(), "rb" );

		if ( !hSHP || !hDBF ) {
			MagLog::error() << "Can not open Shapefile " << shp << endl;
			return;
		}

		SHPGetInfo( hSHP, &nEntities, &nShapeType, adfMinBound, adfMaxBound );
		map<string, int> attributes;
		
		for( i = 0; i < DBFGetFieldCount(hDBF); i++ )
		{
		            DBFFieldType eType;
		            eType = DBFGetFieldInfo( hDBF, i, szTitle, &nWidth, &nDecimals );
		            attributes.insert(make_pair(lowerCase(szTitle), i));
		}
		map<string, int>::iterator index =  filter.empty() ? attributes.end() : attributes.find(filter);

		if ( index == attributes.end() && !filter.empty() ) {
			MagLog::info() << "ShapeDecoder: can not find attribute " << filter << " -> Data will not be filtered!" << endl;
		}

		for( i = 0; i < nEntities; i++ )
		{
			int       j;
		    SHPObject *psShape;

			psShape = SHPReadObject( hSHP, i );
		
			bool add = true;
			if ( index != attributes.end() )
			{
				string s = DBFReadStringAttribute(hDBF, i, index->second);
				add = false;

				for (vector<string>::const_iterator val = values.begin(); val != values.end(); ++val) {
					string ss = s.substr(0, val->length());
					if ( magCompare(*val, ss) ) {
						add = true;
						break;
					}
				}
			}

			if (  add)  {
				bool in = true;
				bool left = true;
				bool right = true;

				if ( psShape->dfYMax <= miny ) continue;
				if ( psShape->dfYMin >= maxy ) continue;
				if ( psShape->dfXMax <= minx ) in = false;
				if ( psShape->dfXMin >= maxx ) in = false;

				if ( psShape->dfXMax-360 < minx ) left = false;
				if ( psShape->dfXMin+360 > maxx ) right = false;

				if ( !in && !right && !left ) continue;


				PointsList<GeoPoint> *inlist, *leftlist, *rightlist;
				if (in) {
					push_back(new PointsList<GeoPoint>());
					inlist = back();
				}
				if (left) {
					push_back(new PointsList<GeoPoint>());
					leftlist = back();
				}
				if (right) {
					push_back(new PointsList<GeoPoint>());
					rightlist = back();
				}

//				bool hole = false;
				for( j = 0, iPart = 1; j < psShape->nVertices; j++ )
				{
					if( iPart < psShape->nParts && psShape->panPartStart[iPart] == j )
					{
						if ( holes_ == false ) break;
						iPart++;
						if (in) {
        						push_back(new PointsList<GeoPoint>());
							inlist = back();
						}
						if (left) {
							push_back(new PointsList<GeoPoint>());
							leftlist = back();
						}
						if (right) {
							push_back(new PointsList<GeoPoint>());
							rightlist = back();
						}
					}
					if (in) {
						inlist->push_back(GeoPoint(psShape->padfX[j], psShape->padfY[j], i));
					}
					if (left) {
						leftlist->push_back(GeoPoint(psShape->padfX[j]-360., psShape->padfY[j], i));
					}
					if (right) {
						rightlist->push_back(GeoPoint(psShape->padfX[j]+360., psShape->padfY[j], i));
					}
				}
			}
			SHPDestroyObject( psShape );
		}
		SHPClose( hSHP );
        DBFClose ( hDBF ); 
	}
	catch (...)
	{
		MagLog::error() << "Can not open Shapefile " << path_ << endl;
	}
	MagLog::dev() << "Shape file--->" << this->size() << endl; 
}



void ShapeDecoder::polygons(vector<polygon_2d>& data, const Transformation& transformation)
{
	Timer timer("Read Shape file", "read shape file" + path_);
	double minx, miny, maxx, maxy;

	transformation.smallestBoundingBox(minx, miny, maxx, maxy);
//	int c = 1;
	try {
		SHPHandle  hSHP;
		int	nShapeType, nEntities, i, iPart;
		bool hole;
		double 	adfMinBound[4], adfMaxBound[4];
		string shp = path_ + ".shp";
		string dbf = path_ + ".dbf";
		hSHP = SHPOpen( shp.c_str(), "rb" );
		if ( !hSHP  ) {
		    	MagLog::error() << "Can not open Shapefile " << path_ << endl;
		    	return;
		}
		data.clear();
		SHPGetInfo( hSHP, &nEntities, &nShapeType, adfMinBound, adfMaxBound );

		vector<polygon_2d> polys;
		int nb  = 0;
		for( i = 0; i < nEntities; i++ )
		{
			int		j;
			SHPObject	*psShape;

			psShape = SHPReadObject( hSHP, i );

			bool in = true;
			bool left = true;
			bool right = true;
// Big lakes american in medium resolution...
//			if ( psShape->nVertices == 317 ) continue;
//			if ( psShape->nVertices == 446 ) continue;
//			if ( psShape->nVertices == 592 ) continue;

			if ( psShape->dfYMax <= miny ) continue;
			if ( psShape->dfYMin >= maxy ) continue;
			if ( psShape->dfXMax <= minx ) in = false;
			if ( psShape->dfXMin >= maxx ) in = false;
			if ( psShape->dfXMax-360 < minx ) left = false;
			if ( psShape->dfXMin+360 > maxx ) right = false;

			if ( !in && !right && !left ) continue;
			polygon_2d poly;
			polygon_2d polyleft;
			polygon_2d polyright;
			// cout << "[" << psShape->dfYMin << ", "  << psShape->dfXMin << "]-->[" << psShape->dfYMax << ", "  << psShape->dfXMax << "]" << psShape->nVertices << endl;
			nb++;

			for( j = 0, iPart = 1, hole = false; j < psShape->nVertices ; j++ )
			{
				if( iPart < psShape->nParts && psShape->panPartStart[iPart] == j )
				{
					iPart++;
					hole=true;
					// We create a new hole!
					if (in)
						poly.inners().push_back(polygon_2d::ring_type());
					if (left)
						polyleft.inners().push_back(polygon_2d::ring_type());
					if (right)
						polyright.inners().push_back(polygon_2d::ring_type());
				}
				{
					if ( iPart==1 ) {
						if (in) {
							GeoPoint geo(GeoPoint(psShape->padfX[j], psShape->padfY[j]));
							PaperPoint point = transformation(geo);
							poly.outer().push_back(make<point_2d>(point.x(), point.y()));
						}
						if (left) {
							GeoPoint geo(GeoPoint(psShape->padfX[j]-360, psShape->padfY[j]));
							PaperPoint point = transformation(geo);
							polyleft.outer().push_back(make<point_2d>(point.x(), point.y()));
						}
						if (right) {
							GeoPoint geo(GeoPoint(psShape->padfX[j]+360, psShape->padfY[j]));
							PaperPoint point = transformation(geo);
							polyright.outer().push_back(make<point_2d>(point.x(), point.y()));
						}
					}
					else {
						if (in) {
							GeoPoint geo(GeoPoint(psShape->padfX[j], psShape->padfY[j]));
							PaperPoint point = transformation(geo);
							poly.inners().back().push_back(make<point_2d>(point.x(), point.y()));
						}
						if (left) {
							GeoPoint geo(GeoPoint(psShape->padfX[j]-360, psShape->padfY[j]));
							PaperPoint point = transformation(geo);
							polyleft.inners().back().push_back(make<point_2d>(point.x(), point.y()));
						}
						if (right) {
							GeoPoint geo(GeoPoint(psShape->padfX[j]+360, psShape->padfY[j]));
							PaperPoint point = transformation(geo);
							polyright.inners().back().push_back(make<point_2d>(point.x(), point.y()));
						}
					 }
				}
			}
			if (in)
				data.push_back(poly);
			if (left)
				data.push_back(polyleft);
			if (right)
				data.push_back(polyright);

			SHPDestroyObject( psShape );
		}
		SHPClose( hSHP );
	}
	catch (...)
	{
		MagLog::error() << "Can not open Shapefile " << path_ << endl;
	}
}
