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

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

  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 Transformation.cc
// Magics Team - ECMWF 2004

#include "BasicGraphicsObject.h"
#include "Transformation.h"
#include "Layout.h"
#include <iomanip>
#include "MatrixHandler.h"
#include "PointsHandler.h"
#include "MetaData.h"
#define BOOST_GEOMETRY_OVERLAY_NO_THROW
#include "Polyline.h"



using namespace magics;


Transformation::Transformation() : 
	coordinateType_(GeoType),
	dataMinX_(std::numeric_limits<double>::max()),
	dataMaxX_(-std::numeric_limits<double>::max()),
	dataMinY_(std::numeric_limits<double>::max()),
	dataMaxY_(-std::numeric_limits<double>::min()),
	topAxis_(true)
{
	userEnveloppe_ = new Polyline();
	PCEnveloppe_ = new Polyline();
}

Transformation::~Transformation() 
{
	delete userEnveloppe_;
	delete PCEnveloppe_;
}

void Transformation::print(ostream& out)  const
{
	out << "Transformation";
}

void Transformation::forceNewArea(double xpcmin, double ypmin, double xpcmax, double ypcmax, double& width, double& height)
{
	
}
void Transformation::fill(double& width, double& height)
{
	init();
	// with and height will only 
	double w = getAbsoluteMaxPCX() - getAbsoluteMinPCX();
	double h = getAbsoluteMaxPCY() - getAbsoluteMinPCY();
	
	double minx =  getAbsoluteMinPCX();
	double maxx =  getAbsoluteMaxPCX();
	
	double miny =  getAbsoluteMinPCY();
	double maxy =  getAbsoluteMaxPCY();
	
	
		
	
	
		  	double nw = (width/height) *h;		
		  	if ( nw > w) {
		  	// we need to extend in the x direction
		  		double more = (nw-w)/2;
		  		maxx = maxx + more;
		  		minx = minx - more;			
		  	}
			else { 
				double nh = (height/width)*w;				
				double more = (nh-h)/2;							
				maxy = maxy + more;
				miny = miny - more;				
			}
		
		
	
		setNewPCBox(minx, miny, maxx, maxy); 
		
	
}

void Transformation::aspectRatio(double& width, double& height)
{
	init();
	double w = getAbsoluteMaxPCX() - getAbsoluteMinPCX();
	double h = getAbsoluteMaxPCY() - getAbsoluteMinPCY();
	if ( w/h >= width/height) {
		double nh = (h/w) * width;
		if ( nh <= height) {
			height = nh;
		}
		else {
			width = (w/h) * height;			
		}
	}
	else 
		width = (w/h) * height;
}




void Transformation::thin(MatrixHandler& matrix, double x, double y, vector<UserPoint>& out) const
{
	int xfactor = (int) ceil((float) x);
	int yfactor = (int) ceil((float) y);
	
	if(xfactor < 1)
	{
	  	xfactor=1;
		MagLog::warning() << "Ivalid x-thinning factor: " << x << "! Reverted back to 1" << endl;
	}
	if(yfactor < 1)
	{
	  	yfactor=1;
		MagLog::warning() << "Ivalid y-thinning factor: " << y << "! Reverted back to 1" << endl;
	}	
	
	ThinningMatrixHandler thin_matrix(matrix, xfactor , yfactor);
	


	int columns =thin_matrix.columns();
	int rows = thin_matrix.rows();

	
		for ( int lat = 0; lat < rows; lat++)		
			for ( int lon = 0; lon < columns; lon++) 
				out.push_back(UserPoint(thin_matrix.column(lat, lon), thin_matrix.row(lat, lon), thin_matrix(lat, lon)));
	columns = matrix.columns();
	rows = matrix.rows();



}




ViewFilter::ViewFilter(double xmin, double xmax, double ymin, double ymax, double xres, double yres) : 
	xmin_(xmin), xmax_(xmax), ymin_(ymin), ymax_(ymax), 
	xres_(xres), yres_(yres) 
{
	xdim_ = (int) (xmax_-xmin_)/xres_;
	ydim_ = (int) (ymax_-ymin_)/yres_;

	for (int y = 0; y < ydim_; y++)
		for ( int x = 0; x < xdim_; x++)
			done.push_back(false);

}


bool ViewFilter::in(const PaperPoint& xy){
	if ( xy.x() < xmin_ ) return false;
	if ( xy.x() > xmax_ ) return false;
	if ( xy.y() < ymin_ ) return false;
	if ( xy.y() < ymin_ ) return false;
	return true;
}


void Transformation::init()
{
	if ( PCEnveloppe_->empty()) {
			PCEnveloppe_->push_back(PaperPoint(getMinPCX(), getMinPCY()));
			PCEnveloppe_->push_back(PaperPoint(getMinPCX(), getMaxPCY()));
			PCEnveloppe_->push_back(PaperPoint(getMaxPCX(), getMaxPCY()));
			PCEnveloppe_->push_back(PaperPoint(getMaxPCX(), getMinPCY()));
			PCEnveloppe_->push_back(PaperPoint(getMinPCX(), getMinPCY()));
		}
}


void Transformation::setDataMinX(double minx, const string& ref) const
{ 
	// WE will have to take into acount the date!
	dataMinX_ = std::min(minx, dataMinX_); 
	dataReferenceX_ = ref;
}

void Transformation::setDataMaxX(double maxx, const string& ref) const 
{ 
	dataMaxX_ = std::max(maxx, dataMaxX_); 
	dataReferenceX_ = ref;
}

void Transformation::setDataMinY(double miny, const string& ref) const
{ 
	dataMinY_ = std::min(miny, dataMinY_);
	dataReferenceY_ = ref; 
}

void Transformation::setDataMaxY(double maxy, const string& ref) const 
{ 
	dataMaxY_ = std::max(maxy, dataMaxY_); 
	dataReferenceY_ = ref; 
}


void Transformation::visit(MetaDataVisitor& visitor, 
	double left, double top, double width, double height,  double imgwidth, double imgheight)
{
	ostringstream java;
	double w = getMaxPCX() - getMinPCX();
	double h = getMaxPCY() - getMinPCY();
	java << "{";
	java << "\"name\" : \"cylindrical\",";		

	java << "\"top\" : \"" << top <<  "\",";		
	java << "\"left\" : \"" << left <<  "\",";		

	java << "\"img_width\" : \"" << imgwidth <<  "\",";	
	java << "\"img_height\" : \"" << imgheight <<  "\",";	
	java << "\"width\" : \"" << width <<  "\",";	
	java << "\"height\" : \"" << height <<  "\",";	

	java << "\"pcxmin\" : \"" << getMinPCX() <<  "\",";		
	java << "\"pcymin\" : \"" << getMinPCY() <<  "\",";		
	java << "\"pcwidth\" : \"" << w <<  "\",";	
	java << "\"pcheight\" : \"" << h <<  "\"";	

	java << "}";	
	visitor.add("projection", java.str());
	ostringstream wf;
	wf << (w/width)<< endl;
	wf << "0\n0\n";
	wf << -(h/height) << endl;
	wf << getMaxPCY() - (h/height)/2<< endl;
	wf <<  getMinPCX() +  (w/width)/ 2<< endl;
	visitor.add("world_file", wf.str());
}


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



void Transformation::operator()(const Polyline& from, BasicGraphicsObjectContainer& out) const
{	
	if (from.empty())
		return;
	PaperPoint ll(getMinPCX(), getMinPCY());
	PaperPoint ur(getMaxPCX(), getMaxPCY());
	boost::geometry::model::box<PaperPoint> box(ll, ur);
	boost::geometry::correct(box);
	if ( from.closed() ) {
		deque<PaperPoint> line;

		for (unsigned i = 0; i < from.size(); i++) {
			line.push_back(from.get(i));

		}

		boost::geometry::correct(line);
		vector<deque<PaperPoint> > result;
		boost::geometry::intersection(box, line, result);

		// Now we feed the graphic container!

		for (vector<deque<PaperPoint> >::iterator l = result.begin(); l != result.end(); l++)
		{
			Polyline* poly = from.getNew();

			for (deque<PaperPoint>::iterator point = l->begin(); point != l->end(); ++point)
				poly->push_back(*point);

			if ( !poly->empty() )
				out.push_back(poly);
		}
	}
	else {
		vector<PaperPoint> line;

		for (unsigned i = 0; i < from.size(); i++) {
			line.push_back(from.get(i));
		}
		boost::geometry::correct(line);
		vector<vector<PaperPoint> > result;
		boost::geometry::intersection(box, line, result);

		// Now we feed the graphic container!

		for (vector<vector<PaperPoint> >::iterator l = result.begin(); l != result.end(); l++)
		{
			Polyline* poly = from.getNew();

			for (vector<PaperPoint>::iterator point = l->begin(); point != l->end(); ++point)
				poly->push_back(*point);

			if ( !poly->empty() )
				out.push_back(poly);
		}
	}
}



void Transformation::boundingBox(double& minx, double& miny, double&maxx, double& maxy) const
{
	// Return exactly the box ... Perhaps could return a bit more to avoid side effect.
	minx= getMinX();
	miny= getMinY();
	maxx= getMaxX();
	maxy= getMaxY();

	PCEnveloppe_->push_back(PaperPoint(minx, miny));
	PCEnveloppe_->push_back(PaperPoint(minx, maxy));
	PCEnveloppe_->push_back(PaperPoint(maxx, maxy));
	PCEnveloppe_->push_back(PaperPoint(minx, maxy));
	PCEnveloppe_->push_back(PaperPoint(minx, miny));

	userEnveloppe_->push_back(PaperPoint(minx, miny));
	userEnveloppe_->push_back(PaperPoint(minx, maxy));
	userEnveloppe_->push_back(PaperPoint(maxx, maxy));
	userEnveloppe_->push_back(PaperPoint(minx, maxy));
	userEnveloppe_->push_back(PaperPoint(minx, miny));

}



bool Transformation::in(const UserPoint& point) const
{

	PaperPoint pp = (*this)(point);
	return in(pp);
}



bool Transformation::in(const PaperPoint& point) const
{
	if ( PCEnveloppe_->empty()) {
		PCEnveloppe_->push_back(PaperPoint(getMinPCX(), getMinPCY()));
		PCEnveloppe_->push_back(PaperPoint(getMinPCX(), getMaxPCY()));
		PCEnveloppe_->push_back(PaperPoint(getMaxPCX(), getMaxPCY()));
		PCEnveloppe_->push_back(PaperPoint(getMaxPCX(), getMinPCY()));
		PCEnveloppe_->push_back(PaperPoint(getMinPCX(), getMinPCY()));
	}

	return boost::geometry::covered_by(point, PCEnveloppe_->polygon_);
}


bool Transformation::in(double x, double y) const
{
	if ( PCEnveloppe_->empty()) {
				PCEnveloppe_->push_back(PaperPoint(getMinPCX(), getMinPCY()));
				PCEnveloppe_->push_back(PaperPoint(getMinPCX(), getMaxPCY()));
				PCEnveloppe_->push_back(PaperPoint(getMaxPCX(), getMaxPCY()));
				PCEnveloppe_->push_back(PaperPoint(getMaxPCX(), getMinPCY()));
				PCEnveloppe_->push_back(PaperPoint(getMinPCX(), getMinPCY()));
			}
	PaperPoint point = (*this)(UserPoint(x,y));
	return boost::geometry::covered_by(point, PCEnveloppe_->polygon_);
}

void Transformation::thin(PointsHandler& points, vector<PaperPoint>& thin,  vector<PaperPoint>& all) const
{	

	BoxPointsHandler box(points, *this,  true);
	box.setToFirst();
	while (box.more()) {               		
		PaperPoint xy = (*this)(box.current());
		if ( view_.in(xy) ) {
			thin.push_back(xy);
		}
		all.push_back(xy);

		box.advance();		
	}  

}


void Transformation::thin(MatrixHandler& points, vector<PaperPoint>& thin,  vector<PaperPoint>& all) const
{	

	BoxMatrixHandler box(points, *this);
	int row = std::max(int(view_.yres_/abs(box.YResolution())), 1);
	int column = std::max(int(view_.xres_/abs(box.XResolution())), 1);
	ThinningMatrixHandler sample(box, row, column);

	box.setToFirst();
	while (box.more()) {               		
		PaperPoint xy = (*this)(box.current());	                   
		if ( view_.in(xy) ) 
			all.push_back(xy);	           
		box.advance();		
	}  
	sample.setToFirst();
	while (sample.more()) {               		
		PaperPoint xy = (*this)(sample.current());	           
		if ( view_.in(xy) ) 
			thin.push_back(xy);	           

		sample.advance();		
	}  







}


double Transformation::unitToCm(double width, double height) const
{
	
	return height/(getAbsoluteMaxPCY() - getAbsoluteMinPCY());
}

void Transformation::operator()(const UserPoint& geo, vector<PaperPoint>& out) const
{
	PaperPoint pp = (*this)(geo);
		if ( in(pp) ) 
			out.push_back(pp);

}
void Transformation::operator()(const UserPoint& geo, Polyline& out) const
{
	PaperPoint pp = (*this)(geo);
		if ( in(pp) )
			out.push_back(pp);

}


void Transformation::reprojectComponents(const UserPoint&, pair<double, double>&) const
{
	
}

void Transformation::reprojectSpeedDirection(const PaperPoint& point, pair<double, double>&) const
{
	
}

void Transformation::revert(const vector<pair<double, double> > & in, vector<pair<double, double> > & out) const
{
	out.reserve(in.size());
	for (vector<pair<double, double> >::const_iterator p = in.begin(); p != in.end(); ++p)
		out.push_back(make_pair(this->rx(p->first), this->ry(p->second)));

}

string Transformation::writeLongitude(const UserPoint& point) const
{
		return point.asLongitude();
}
string Transformation::writeLatitude(const UserPoint& point) const
{
		return point.asLatitude();
}

void Transformation::wraparound(const UserPoint& point, stack<UserPoint>& duplicates) const
{
	if ( in(point) ) {
		duplicates.push(point);
	}
}

MatrixHandler* Transformation::prepareData(const AbstractMatrix& matrix) const {
	return new BoxMatrixHandler(matrix, *this);
}

