/******************************** 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 "PolyCoast.h"
#include "Clipper.h" 
#include <iomanip>
#include "MatrixHandler.h"
#include "PointsHandler.h"
#include "MetaData.h"



using namespace magics;


Transformation::Transformation() : 
	coordinateType_(GeoType),
	dataMinX_(INT_MAX), dataMaxX_(INT_MIN),
	dataMinY_(INT_MAX), dataMaxY_(INT_MIN),topAxis_(true)
{
}

Transformation::~Transformation() 
{
}

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); 
		
	//	aspectRatio(width, height);
	
}

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;
}



template <class P>
bool inArea(double left, double right, double bottom, double top, const Polyline& poly)
{
	return (top >= poly.getMaxY() &&
			left <= poly.getMinX() &&
			bottom <= poly.getMinY() &&
			right >= poly.getMaxX());
} 

bool Transformation::out(const Polyline& poly) const
{
	return ( poly.getMaxX() < getMinX() || 
			poly.getMinX() > getMaxX() ||
			poly.getMaxY() < getMinY() ||
			poly.getMinY() > getMaxY() );
}


bool Transformation::in(const Polyline& poly) const
{
	return (getMaxY() >= poly.getMaxY() &&
			getMinX() <= poly.getMinX() &&
			getMinY() <= poly.getMinY() &&
			getMaxX() >= poly.getMaxX());
} 


void Transformation::rotate(vector<GeoPoint>& poly) const
{
	if ( poly.empty() ) return;
	if  ( poly.back() != poly.front() ) return;



	for ( vector<GeoPoint>::iterator point = poly.begin(); point != poly.end(); ++point)
	{
		if ( !in(*point) )
		{
			// First point out ... Rotate
			std::rotate(poly.begin(), point, poly.end());
			break;
		} 
	}
	//poly.push_back(poly.front());
} 




	template <class P>
void rotate(double left, double right, double bottom, double top, Polyline& poly)
{
	bool toclose = poly.isClosed();
	for ( typename Polyline::iterator point = poly.begin(); point != poly.end(); ++point)
	{
		if ( !((*point).y() < top && (*point).y() > bottom && (*point).x() > left && (*point).x() < right))
		{
			// First point out ... Rotate
			std::rotate(poly.begin(), point, poly.end());
			break;
		} 
	} 
	if ( toclose) poly.push_back(poly.front());
} 




void Transformation::thin(MatrixHandler<GeoPoint>& matrix, double x, double y, vector<GeoPoint>& 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<GeoPoint> thin_matrix(matrix, xfactor , yfactor);
	BoxMatrixHandler<GeoPoint> box_matrix(thin_matrix, *this);
	

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

	
		for ( int lat = 0; lat < rows; lat++)		
			for ( int lon = 0; lon < columns; lon++) 
			if ( in(box_matrix.column(lat, lon), box_matrix.row(lat, lon)) )
				out.push_back(GeoPoint(box_matrix.column(lat, lon), box_matrix.row(lat, lon), box_matrix(lat, lon)));
	columns = matrix.columns();
	rows = matrix.rows();



}
void Transformation::thin(MatrixHandler<UserPoint>& 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<UserPoint> thin_matrix(matrix, xfactor , yfactor);
	BoxMatrixHandler<UserPoint> box_matrix(thin_matrix, *this);
	

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

	
		for ( int lat = 0; lat < rows; lat++)		
			for ( int lon = 0; lon < columns; lon++) 
			if ( in(box_matrix.column(lat, lon), box_matrix.row(lat, lon)) )
				out.push_back(UserPoint(box_matrix.column(lat, lon), box_matrix.row(lat, lon), box_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)  {

   int x = (xy.x()-xmin_)/xres_;
   int y = (xy.y()-ymin_)/yres_;

   if ( x < 0 ) return false;
   if ( y < 0 ) return false;
   if ( x >= xdim_) return false;
   if ( y >= ydim_) return false;
   bool info = !done[y*xdim_+x];
   done[y*xdim_+x] = true;
   return info;
   }
 */
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::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)
{
	ostringstream java;
	double w = getMaxPCX() - getMinPCX();
	double h = getMaxPCY() - getMinPCY();
	java << "{";
	java << "\"name\" : \"cylindrical\",";		

	java << "\"top\" : \"" << top <<  "\",";		
	java << "\"left\" : \"" << left <<  "\",";		
	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());
}


void Transformation::operator()(const Polyline& from, BasicGraphicsObjectContainer& out) const
{	
	Clipper<BasicGraphicsObjectContainer> clipper(*this);	
	clipper.ignoreMissing(false);
	clipper(from); 
	clipper.feed(out);
}



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();
}



bool Transformation::in(const GeoPoint& point) const
{
	return ( getAbsoluteMinX() <= point.x() && 
			point.x() <=  getAbsoluteMaxX() && 
			getAbsoluteMinY() <= point.y() && 
			point.y() <= getAbsoluteMaxY()); 
}

bool Transformation::in(const UserPoint& point) const
{
	return ( getAbsoluteMinX() <= point.x() && 
			point.x() <=  getAbsoluteMaxX() && 
			getAbsoluteMinY() <= point.y() && 
			point.y() <= getAbsoluteMaxY()); 
}

bool Transformation::in(const PaperPoint& point) const
{
	return ( getAbsoluteMinPCX() <= point.x() && 
			point.x() <=  getAbsoluteMaxPCX() && 
			getAbsoluteMinPCY() <= point.y() && 
			point.y() <= getAbsoluteMaxPCY());
} 

bool Transformation::clip(PaperPoint& point) const
{
	bool done = false;
	if ( point.x() < getAbsoluteMinPCX() ) {
		done = true;
		point.x(getAbsoluteMinPCX());
	}
	if ( point.y() < getAbsoluteMinPCY() ) {
		done = true;
		point.y(getAbsoluteMinPCY());
	}

	if ( point.x() > getAbsoluteMaxPCX() ) {
		done = true;
		point.x(getAbsoluteMaxPCX());
	}
	if ( point.y() > getAbsoluteMaxPCY() ) {
		done= true;
		point.y(getAbsoluteMaxPCY());
	}
	return  done;


}

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

	BoxPointsHandler<GeoPoint> box(points, *this);
	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<UserPoint>& points, vector<PaperPoint>& thin,  vector<PaperPoint>& all) const
{

}

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

	BoxMatrixHandler<GeoPoint> 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<GeoPoint> 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();		
	}  







}


void Transformation::operator()(PolyCoast& coast, BasicGraphicsObjectContainer& list) const
{
	// Special case for the south pole! 
	// Add to point to ease the Shading!
	vector<GeoPoint>& geo =  coast.coastlines();

	if ( coast.level() == 0 ) {

		GeoPoint front = geo.front();
		GeoPoint back = geo.back();
		front.latitude(-90.);
		back.latitude(-90.);
		geo.insert(geo.begin(), front);
		geo.push_back(back);
	}

	Clipper<BasicGraphicsObjectContainer> clipper(*this);

	rotate(geo);


	clipper.close(coast);
	clipper.feed(list);

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

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

void Transformation::reprojectComponents(const GeoPoint&, pair<double, double>&) const
{
	
}
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)));
}

PaperPoint Transformation::clip_point(const PaperPoint& in) const
{
	double minx = std::min(getMinPCX(), getMaxPCX());
	double maxx = std::max(getMinPCX(), getMaxPCX());
	double miny = std::min(getMinPCY(), getMaxPCY());
	double maxy = std::max(getMinPCY(), getMaxPCY());

	PaperPoint out = in;
	if ( in.x_ < minx ) {
		if ( in.y_ < miny ) {
			out.x_ = minx;
			out.y_ = miny;
			return out;
		}
		if ( in.y_ < maxy ) {
			out.x_ = minx;
			return out;
		}
		out.x_ = minx;
		out.y_ = maxy;
		return out;

	}
	if (in.x_ < maxx ) {
		if ( in.y_ < miny ) {
			out.y_ = miny;
			return out;
		}
		if ( in.y_ < maxy ) {
			return out;
		}
		out.y_ = maxy;
		return out;
	}

	if ( in.y_ < miny ) {
		out.y_ = miny;
		out.x_ = maxx;
		return out;
	}
	if ( in.y_ < maxy ) {
		out.x_ = maxx;
		return out;
	}
	out.y_ = maxy;
	out.x_ = maxx;
	return out;


}
