/******************************** 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 CoastPlotting.cc
    \brief Implementation of the Template class CoastPlotting.
    
    Magics Team - ECMWF 2004
    
    Started: Mon 2-Feb-2004
    
    Changes:
    
*/

#include "CoastPlotting.h"
#include "Timer.h"
#include "SceneVisitor.h"
#include "PolyCoast.h"
#include "ViewNode.h"
#include "ShapeDecoder.h"
#include "LegendVisitor.h"
#include "Polyline.h"

//#define BOOST_VERSION 104701
#define BOOST_GEOMETRY_OVERLAY_NO_THROW

#include <boost/geometry/geometry.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/geometries/polygon.hpp>
#include <boost/geometry/geometries/box.hpp>

using namespace boost::geometry;

using namespace magics;


#define PATH(a) getEnvVariable("MAGPLUS_HOME") + MAGPLUS_PATH_TO_SHARE_ + a;


bool test110(const GeoPoint& from, const GeoPoint& to)
{
	if  (from.x_ == to.x_ )
		return true;
	if (abs(from.x_ - to.x_) == 360. )
		return true;
	return false;
}

bool test(const GeoPoint& from, const GeoPoint& to)
{
	return ( abs(from.x_ - to.x_ ) > 1 || abs(from.y_ - to.y_ ) > 1);
}


CoastPlotting::CoastPlotting() 
{
}

NoCoastPlotting::NoCoastPlotting() 
{
	riversMethods_["on"] = &CoastPlotting::rivers;
	riversMethods_["off"] = &CoastPlotting::ignore;
}

void NoCoastPlotting::visit(LegendVisitor&)
{
}

void CoastPlotting::visit(LegendVisitor& legend)
{
	return;
/*
	Polyline* coast  = new Polyline();
	coast->setThickness(thickness_);
	coast->setColour(*colour_);
	coast->setLineStyle(style_);	

	LineEntry* entry = new LineEntry("Coastlines", coast);
	legend.add(entry);
*/
}


void CoastPlotting::operator()(PreviewVisitor& parent)
{
	const Transformation& transformation = parent.transformation();
	//transformation.coastSetting(coastSet_, 10, 5);
	CoastPlotting& preview = parent.coastlines();
	transformation.coastSetting(preview.coastSet_, 10, 5);
	preview.decode(parent);

	for (vector<Polyline* >::iterator poly = preview.ocean_.begin(); poly != preview.ocean_.end(); ++poly)
	{
		Polyline* npoly= new Polyline();
		npoly->setThickness(thickness_);
		npoly->setColour(*colour_);
		(*poly)->setLineStyle(style_);
		FillShadingProperties* shading = new FillShadingProperties();
		npoly->setFillColour(Colour("white"));
		npoly->setShading(shading);
		npoly->setFilled(true);
		std::copy((*poly)->begin(), (*poly)->end(), back_inserter(*npoly));
		parent.push_back(npoly);
	}

	for (vector<Polyline* >::iterator poly = preview.coast_.begin(); poly != preview.coast_.end(); ++poly)
	{
		Polyline* npoly= new Polyline();
		npoly->setThickness(thickness_);
		npoly->setColour(*colour_);
		npoly->setLineStyle(style_);
		FillShadingProperties* shading = new FillShadingProperties();
		npoly->setFillColour(Colour("cream"));
		npoly->setShading(shading);
		npoly->setFilled(true);
		std::copy((*poly)->begin(), (*poly)->end(), back_inserter(*npoly));
		parent.push_back(npoly);
	}

	// Now we add the frame
	Polyline* frame = new Polyline();
	frame->setAntiAliasing(false);
	frame->setThickness(thickness_);
	frame->setColour(Colour("tan"));
	frame->setLineStyle(style_);	
	frame->push_back(PaperPoint(transformation.getMinX(), transformation.getMinY()));
	frame->push_back(PaperPoint(transformation.getMaxX(), transformation.getMinY()));
	frame->push_back(PaperPoint(transformation.getMaxX(), transformation.getMaxY()));
	frame->push_back(PaperPoint(transformation.getMinX(), transformation.getMaxY()));
	frame->push_back(PaperPoint(transformation.getMinX(), transformation.getMinY()));
	parent.push_back(frame);
}


void NoCoastPlotting::operator()(DrawingVisitor& parent)
{
	const Transformation& transformation = parent.transformation();
	transformation.coastSetting(coastSet_, parent.layout().absoluteWidth(), parent.layout().absoluteHeight());
	(*boundaries_)(coastSet_, parent.layout());
	layers(riversMethods_, rivers_, parent);
	(*cities_)(coastSet_, parent.layout());
}


void NoCoastPlotting::rivers(DrawingVisitor& visitor)
{
	const string file = PATH(coastSet_["rivers"]);

	ShapeDecoder rivers;
	rivers.setPath(file);
	rivers.needHoles(true);
	const Transformation& transformation = visitor.transformation();
	rivers.decode(transformation);

	for ( ShapeDecoder::const_iterator river = rivers.begin(); river != rivers.end(); ++river)
	{
		Polyline* poly = new Polyline();
		poly->setColour(*rivers_colour_);
		poly->setThickness(rivers_thickness_);

		poly->setLineStyle(rivers_style_);
		(**river).setToFirst();
		while ((**river).more())
		{
		  poly->push_back(transformation((**river).current()));
		  (**river).advance();
		}
		transformation(*poly, visitor.layout());
	}
}

void NoCoastPlotting::layers(map<string, Action>& methods, const string& val, DrawingVisitor& visitor)
{
	const string lval(lowerCase(val));
	map<string, Action>::iterator method = methods.find(lval);
	if ( method != methods.end() )
		(this->*method->second)(visitor);
}


struct SortHelper
{
	bool operator() (PolyCoast*first, PolyCoast* second) {
		return first->coastlines().size() > second->coastlines().size();
	}
};

/*! \brief Method to set resource files for GIS information
 
  \note We have to use '10m' resolution for administrative Provinces since only this 
   resolution contains data from OUTSIDE the USA and Canada
*/
void CoastPlotting::operator()(DrawingVisitor& parent)
{
  Timer timer("coastlines", "prepare the coastlines");
  //if ( !layer_) layer_ = &parent;

  const Transformation& transformation = parent.transformation();

  coast_.clear();
  ocean_.clear();
  lake_.clear();

  coastSet_["administrative_boundaries"] = "10m/10m_admin_1_states_provinces_shp";
  coastSet_["boundaries"] = "50m/50m_admin_0_boundary_lines_land";

  if ( magCompare(CoastPlottingAttributes::resolution_, "full") ) {
        string resol = "10m";
        coastSet_["resolution"] = "10m full";
        coastSet_["lakes"]      = resol + "_full/" + resol + "_lakes";
        coastSet_["land"]       = resol + "_full/" + resol + "_land";
        coastSet_["rivers"]     = resol + "_full/" + resol + "_rivers_lake_centerlines";
//        coastSet_["boundaries"] = resol + "/" + resol + "_admin_0_boundary_lines_land";
  }
  else if ( magCompare(CoastPlottingAttributes::resolution_, "high") ) {
        string resol = "10m";
        coastSet_["resolution"] = resol;
        coastSet_["lakes"]      = resol + "/" + resol + "_lakes";
        coastSet_["land"]       = resol + "/" + resol + "_land";
        coastSet_["rivers"]     = resol + "/" + resol + "_rivers_lake_centerlines";
//        coastSet_["boundaries"] = resol + "/" + resol + "_admin_0_boundary_lines_land";
  }
  else if ( magCompare(CoastPlottingAttributes::resolution_, "medium") ) {
        string resol = "50m";
        coastSet_["resolution"] = resol;
        coastSet_["lakes"]      = resol + "/" + resol + "_lakes";
        coastSet_["land"]       = resol + "/" + resol + "_land";
        coastSet_["rivers"]     = resol + "/" + resol + "_rivers_lake_centerlines";
        coastSet_["boundaries"] = resol + "/" + resol + "_admin_0_boundary_lines_land";
  }
  else if ( magCompare(CoastPlottingAttributes::resolution_, "low") ) {
        string resol = "110m";
        coastSet_["resolution"] = resol;
        coastSet_["lakes"]      = resol + "/" + resol + "_lakes";
        coastSet_["land"]       = resol + "/" + resol + "_land";
        coastSet_["rivers"]     = resol + "/" + resol + "_rivers_lake_centerlines";
        coastSet_["boundaries"] = resol + "/" + resol + "_admin_0_boundary_lines_land";
  }
  else {       // automatic
        transformation.coastSetting(coastSet_, parent.layout().absoluteWidth(), parent.layout().absoluteHeight());
  }

  decode(parent.layout());

  // Now we have the caostlines and lakes..
  // let's call the relevant method!
  if ( land_ ) {
		if ( sea_ )
			landsea(parent.layout());
		else
			landonly(parent.layout());
  }
  else {
		if ( sea_ )
			seaonly(parent.layout());
  }

  nolandsea(parent.layout());
  (*boundaries_)(coastSet_, parent.layout());
  (*cities_)(coastSet_, parent.layout());
  layers(riversMethods_, rivers_, parent);
}


/*!
  Here we send send a big rectangle for the ocean
  We send the coastlines as land and lakes as holes of coastlines as polylines.
*/
void CoastPlotting::landsea(Layout& out)
{
	landonly(out);
	seaonly(out);
/*	for (vector<Polyline* >::iterator poly = coast_.begin(); poly != coast_.end(); ++poly)
	{
			setLine(**poly);
			setLandShading(**poly);
			out.push_back(*poly);
	}
	for (vector<Polyline* >::iterator poly = lake_.begin(); poly != lake_.end(); ++poly)
	{
		setLine(**poly);
		setSeaShading(**poly);
		out.push_back(*poly);
		// Now we add the holes as land Polylines!
		for (Polyline::Holes::const_iterator hole = (*poly)->beginHoles(); hole!= (*poly)->endHoles(); ++hole) {
			Polyline* h = (*poly)->getNew();
			setLandShading(*h);
			(*poly)->hole(hole, *h);
			out.push_back(h);
		}
	}
*/
}


Polyline*  CoastPlotting::ocean(Layout& out)
{
	Polyline* ocean = new Polyline();
	setSeaShading(*ocean);

	const Transformation& transformation = out.transformation();

	ocean->push_back(PaperPoint(transformation.getMinPCX(), transformation.getMinPCY()));
	ocean->push_back(PaperPoint(transformation.getMinPCX(), transformation.getMaxPCY()));
	ocean->push_back(PaperPoint(transformation.getMaxPCX(), transformation.getMaxPCY()));
	ocean->push_back(PaperPoint(transformation.getMaxPCX(), transformation.getMinPCY()));
	ocean->push_back(PaperPoint(transformation.getMinPCX(), transformation.getMinPCY()));
	out.push_back(ocean);
	return ocean;
}



/*!
  Here we send the coastlines as land.
  We send the lakes as holes ... and the holes in the lakes as land!
*/
void CoastPlotting::landonly(Layout& out)
{
	for (vector<Polyline* >::iterator poly = coast_.begin(); poly != coast_.end(); ++poly)
	{
		setLandShading(**poly);

		for (vector<Polyline* >::iterator lake = lake_.begin(); lake != lake_.end(); ++lake)
		{
			if ( !*lake ) continue;
			if ( (*lake)->in(**poly) == false )
				continue;

			(*poly)->newHole(**lake);
/*
			for (Polyline::Holes::const_iterator island = (*lake)->beginHoles(); island!= (*lake)->endHoles(); ++island) {
				Polyline newisland;
				(*poly)->hole(island, newisland);
				(*poly)->newHole(newisland);
			}
*/
			delete *lake;
			*lake = 0;
		}
		out.push_back(*poly);
	}
}


/*!
 Here we send send a big rectangle for the ocean.
 We send the coastlines as holes of the big one.
 We send the lakes as polylines ... and the holes in the lakes as holes!
*/
void CoastPlotting::seaonly(Layout& out)
{
	Polyline* big = ocean(out);

	for (vector<Polyline* >::iterator poly = coast_.begin(); poly != coast_.end(); ++poly)
	{
		big->newHole(**poly);

		// Now we add the holes as sea polylines!
		for (Polyline::Holes::const_iterator hole = (*poly)->beginHoles(); hole!= (*poly)->endHoles(); ++hole) {
			Polyline* h = (*poly)->getNew();
			setSeaShading(*h);
			(*poly)->hole(hole, *h);
			out.push_back(h);
		}
	}

	for (vector<Polyline* >::iterator poly = lake_.begin(); poly != lake_.end(); ++poly)
	{
		setLine(**poly);
		setSeaShading(**poly);
		out.push_back(*poly);
	}
}



void CoastPlotting::nolandsea(Layout& visitor)
{
	string file = PATH(coastSet_["land"]);

	ShapeDecoder land;
	land.setPath(file);
	land.needHoles(true);
	const Transformation& transformation = visitor.transformation();
	land.decode(transformation);

	for ( ShapeDecoder::const_iterator line = land.begin(); line != land.end(); ++line)
	{
		Polyline* poly = new Polyline();
		setLine(*poly);
		(**line).setToFirst();
		GeoPoint last =  (**line).current();
		PaperPoint lastxy = transformation(last);
		poly->push_back(transformation(last));
		(**line).advance();
		while ((**line).more())
		{
			GeoPoint point =  (**line).current();
			bool newline = ( coastSet_["resolution"] == "110m" ) ? test110(last, point) : test (last, point);
			PaperPoint xy = transformation(point);

			if (abs(lastxy.x_ -  xy.x_) > 10000000) {
				cout << abs(lastxy.x_ -  xy.x_) << endl;
				newline = true;
			}
			if ( newline ) {
			  // big lane ..
			  transformation(*poly, visitor);
			  poly = new Polyline();
			  setLine(*poly);
			}

			poly->push_back(xy);
			last = point;
			lastxy = xy;

			(**line).advance();
		}
		if ( poly->size() > 1 )
			transformation(*poly, visitor);
		else
			delete poly;
		}

		file = PATH(coastSet_["lakes"]);
		ShapeDecoder lakes;
		lakes.setPath(file);

		lakes.decode(transformation);

		for ( ShapeDecoder::const_iterator line = lakes.begin(); line != lakes.end(); ++line)
		{
			Polyline* poly = new Polyline();
			setLine(*poly);

			(**line).setToFirst();
			while ((**line).more())
			{
				poly->push_back(transformation((**line).current()));
				(**line).advance();
			}
			transformation(*poly, visitor);
		}
}


void CoastPlotting::setLine(Polyline& line)
{
	line.setThickness(thickness_);
	line.setColour(*colour_);
	line.setLineStyle(style_);
	line.setShading(0);
	line.setFilled(false);
}


void CoastPlotting::setSeaShading(Polyline& line)
{
	FillShadingProperties* shading = new FillShadingProperties();
	line.setFillColour(*sea_colour_);
	line.setShading(shading);
	line.setColour(*sea_colour_);
	line.setFilled(true);
	line.setStroke(false);
}


void CoastPlotting::setLandShading(Polyline& line)
{
	FillShadingProperties* shading = new FillShadingProperties();
	line.setFillColour(*land_colour_);
	line.setColour(*land_colour_);
	line.setShading(shading);
	line.setFilled(true);
	line.setStroke(false);
}


CoastPlotting::~CoastPlotting() 
{}


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


void CoastPlotting::decode(const Layout& parent )
{
	//Read the shape file ...
	Timer timer("geometry", "Simplify+clip");
	
	const Transformation& transformation = parent.transformation();
	const double minx = transformation.getMinPCX();
	const double miny = transformation.getMinPCY();;
	const double maxx = transformation.getMaxPCX();;
	const double maxy = transformation.getMaxPCY();;
	box_2d bounding_box(make<point_2d>(minx, miny), make<point_2d>(maxx, maxy));

	vector<polygon_2d> coasts;
	vector<polygon_2d> lakes;
	coast_.clear();

	ShapeDecoder coastline_decoder;
	string file = PATH(coastSet_["land"]);
	coastline_decoder.setPath(file);
	coastline_decoder.polygons(coasts, transformation);

	ShapeDecoder lake_decoder;
	file = PATH(coastSet_["lakes"]);
	lake_decoder.setPath(file);
	lake_decoder.polygons(lakes, transformation);

//	vector<polygon_2d> result;
	vector<polygon_2d > clip_coast;
	vector<polygon_2d > clip_lakes;

	//! First we clip coastlines and lakes
	for (vector<polygon_2d>::iterator l = coasts.begin(); l != coasts.end(); ++l ) {
		intersection(bounding_box, *l, clip_coast);
	}
	for (vector<polygon_2d>::iterator l = lakes.begin(); l != lakes.end(); ++l ) {
			intersection(bounding_box, *l, clip_lakes);
	}

	//! Secondly we try to put the lakes in the continents!!!
	for (vector<polygon_2d>::iterator coast = clip_coast.begin(); coast != clip_coast.end(); ++coast )
	{
		vector<polygon_2d> todolakes;
		for (vector<polygon_2d>::iterator l = clip_lakes.begin(); l != clip_lakes.end(); ++l )
		{
			// we find the lake!
			bool inside = disjoint(*l, *coast);

			if ( !inside )
			{
				coast->inners().push_back(polygon_2d::ring_type());
				for (model::ring<point_2d>::iterator point = l->outer().begin(); point != l->outer().end(); ++point )
				{
					coast->inners().back().push_back(*point);
				}
			}
			else {
				todolakes.push_back(*l);
			}
		}
		clip_lakes = todolakes;
	}

	// Thirdly ... ???
	for ( std::vector<polygon_2d>::iterator clip = clip_coast.begin(); clip != clip_coast.end(); ++clip )
	{
		Polyline* current = new Polyline();
		model::ring<point_2d> outer = clip->outer();

		for (model::ring<point_2d>::iterator point = outer.begin(); point != outer.end(); ++point )
		{
			current->push_back(PaperPoint(point->x(), point->y()));
		}

		for (polygon_2d::inner_container_type::iterator hole = clip->inners().begin(); hole != clip->inners().end(); ++hole)
		{
			vector<polygon_2d> xx;
			intersection(bounding_box, *hole, xx);
			if ( xx.empty() ) continue;
			current->newHole();
			model::ring<point_2d> outer = xx.front().outer();
			for (model::ring<point_2d>::iterator point = outer.begin(); point != outer.end(); ++point ) {
				current->push_back_hole(PaperPoint(point->x(), point->y()));
			}
		}
		coast_.push_back(current);
	}
}

void NoCoastPlotting::visit(MetaDataCollector& meta)
{
	meta["coastlines_resolution"] = coastSet_["resolution"];
}
