/*
 * (C) Copyright 1996-2016 ECMWF.
 * 
 * This software is licensed under the terms of the Apache Licence Version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. 
 * In applying this licence, ECMWF does not waive the privileges and immunities 
 * granted to it by virtue of its status as an intergovernmental organisation nor
 * does it submit to any jurisdiction.
 */

/*! \file TableDecoder.cc
    \brief Implementation of the Template class TableDecoder.
    
    Magics Team - ECMWF 2004
    
    Started: Thu 6-May-2004
    
    Changes:
    
*/

#include "TableDecoder.h"
#include "TableReader.h"
#include "Tokenizer.h"
#include "TextVisitor.h"
#include "SciMethods.h"

using namespace::magics;


TableDecoder::TableDecoder() : matrix_(0)
{
	MetviewIcon::setInfo("_datatype","TABLE");
	MetviewIcon::setInfo("_description","Table Data");
}


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


void  TableDecoder::dateSetting(vector<string>& dates, vector<double>& values, DateTime& base)
{
	if ( dates.empty() )
		return;
	base = DateTime(dates.front());
	for (vector<string>:: iterator date = dates.begin(); date != dates.end(); ++ date ) {
		DateTime d(*date);
		values.push_back(d-base);
	}
}


void  TableDecoder::numberSetting(vector<double>& from, vector<double>& values)
{
	std::copy(from.begin(), from.end(), back_inserter(values));
}



namespace magics {

void TableDecoder::nameGeoMode(TableReader& reader)
{
	x_name_ = this->lon_;
	y_name_ = this->lat_;
	reader.setFieldContainer(-1, y_name_, this->y_values_, -999);
	reader.setFieldContainer(-1, x_name_, this->x_values_, -999);


	if ( !this->values_.empty() && this->values_ != "-1") {
		v_name_ = this->values_;
		reader.setFieldContainer(-1, v_name_, this->v_values_, -999);
	}
	if ( !this->x_component_.empty() && this->x_component_ != "-1") {
		xc_name_ = this->x_component_;
		reader.setFieldContainer(-1, xc_name_, this->xc_values_, -999);
	}
	if ( !this->y_component_.empty()  && this->y_component_ != "-1") {
		yc_name_ = this->y_component_;
		reader.setFieldContainer(-1, yc_name_, this->yc_values_, -999);
	}
}


void TableDecoder::indexGeoMode(TableReader& reader)
{
	int x_index_ = indexToNumber(this->lon_) -1;
	int y_index_ = indexToNumber(this->lat_) -1;
	int val_index= indexToNumber(this->values_);
	int xc_index= indexToNumber(this->x_component_);
	int yc_index= indexToNumber(this->y_component_);

	reader.setFieldContainer(x_index_, x_name_, this->x_values_, -999);
	reader.setFieldContainer(y_index_, y_name_, this->y_values_, -999);


	if ( val_index != -1 ) {
		reader.setFieldContainer(val_index -1, v_name_, this->v_values_, -999);
	}
	if ( xc_index != -1 ) {
		reader.setFieldContainer(xc_index -1, xc_name_, this->xc_values_, -999);
	}
	if ( yc_index != -1 ) {
		reader.setFieldContainer(yc_index -1, yc_name_, this->yc_values_, -999);
	}
}


void  TableDecoder::prepareGeo()
{
	  TableReader reader;

	  bool ok;
	  string error;
	  reader.setPath(this->path_);
	  reader.setHeaderRow(this->header_row_);
	  if ( !this->delimiter_.empty() )
		  reader.setDelimiter(this->delimiter_[0]);
	  reader.setConsecutiveDelimitersAsOne(this->combine_delimiters_);
	  reader.setDataRowOffset(this->data_row_offset_);
	  reader.setUserMetaDataRows(this->meta_data_rows_);

	  vector<string> names = reader.fieldNames();


  if ( !this->x_values_.empty() ) return;

  // first set the containers!!
  if ( magCompare(this->name_mode_, "name" ) )
	  nameGeoMode(reader);
  else
	  indexGeoMode(reader);


  // Then read and interpret!
   ok = reader.read(error);
   if ( !ok ) {
  	 MagLog::error() << error << endl;
  	 return;
   }
  
  vector<double>::iterator x = this->x_values_.begin();
	vector<double>::iterator y = this->y_values_.begin();
	vector<double>::iterator v = this->v_values_.begin();

  while ( x != this->x_values_.end() && x != this->x_values_.end() ) {
  	double val = 0;
  	if ( v != v_values_.end() ) {
  		val = *v;
  		++v;
  	}
  	push_back(new UserPoint(*x, *y, val));
  	++x;
  	++y;
  }

  	//Meta-data
	map<string,string> meta=reader.userMetaData();	
	
	map<string,string>::const_iterator metaIt;
  	if( (metaIt=meta.find("Metview::type")) != meta.end() &&
      	     metaIt->second == "FLEXTRA")
  	{
		setInfo("_datatype","TABLE_flextra");
		setInfo("_description","FLEXTRA Trajectories");
	}
	
	for(map<string,string>::const_iterator it=meta.begin(); it != meta.end(); it++)
  	{
    		setInfo(it->first,it->second);
  	}	
}


void TableDecoder::nameXYMode(TableReader& reader)
{
	 x_name_ = this->x_;
	 y_name_ = this->y_;
	 if ( magCompare(this->x_type_, "date" ) ) {
		 reader.setFieldContainer(-1, x_name_, this->x_date_values_, "none");
	 }
	 else {
		 reader.setFieldContainer(-1, x_name_, this->x_values_, -999);
	 }
	 if ( magCompare(this->y_type_, "date" ) ) {
		 reader.setFieldContainer(-1, y_name_, this->y_date_values_, "none");
	  }
	  else {
		reader.setFieldContainer(-1, y_name_, this->y_values_, -999);
	  }

	 if ( !this->values_.empty() && this->values_ != "-1" ) {
		 v_name_ = this->values_;
		 reader.setFieldContainer(-1, v_name_, this->v_values_, -999);
	 }
	 if ( !this->x_component_.empty() && this->x_component_ != "-1" ) {
		 xc_name_ = this->x_component_;
		 reader.setFieldContainer(-1, xc_name_, this->xc_values_, -999);
	 }
	 if ( !this->y_component_.empty() && this->y_component_ != "-1") {
		 yc_name_ = this->y_component_;
		 reader.setFieldContainer(-1, yc_name_, this->yc_values_, -999);
	 }
}


void TableDecoder::indexXYMode(TableReader& reader)
{
	int x_index = indexToNumber(this->x_) -1;
	int y_index = indexToNumber(this->y_) -1;
	int val_index= indexToNumber(this->values_);
	int xc_index= indexToNumber(this->x_component_);
	int yc_index= indexToNumber(this->y_component_);

	if ( magCompare(this->x_type_, "date" ) ) {

	    		reader.setFieldContainer(x_index, x_name_, this->x_date_values_, "none");
	     }
	     else {
	    	 reader.setFieldContainer(x_index, x_name_, this->x_values_, -999);
	     }
	     if ( magCompare(this->y_type_, "date" ) ) {
	    	 reader.setFieldContainer(y_index, y_name_, this->y_date_values_, "none");
	      }
	      else {
	    	reader.setFieldContainer(y_index, y_name_, this->y_values_, -999);
	      }

	     if ( val_index != -1 ) {
	    	 reader.setFieldContainer(val_index-1, v_name_, this->v_values_, -999);
	     }
	     if ( xc_index != -1 ) {
	    	 reader.setFieldContainer(xc_index-1, xc_name_, this->xc_values_, -999);
	     }
	     if ( yc_index != -1 ) {
	    	 reader.setFieldContainer(yc_index-1, yc_name_, this->yc_values_, -999);
	     }
}


void  TableDecoder::prepareXY()
{
	  TableReader reader;

	  bool ok;
	  string error;
	  reader.setPath(this->path_);
	  reader.setHeaderRow(this->header_row_);
	  if ( !this->delimiter_.empty() )
		  reader.setDelimiter(this->delimiter_[0]);
	  reader.setConsecutiveDelimitersAsOne(this->combine_delimiters_);
	  reader.setDataRowOffset(this->data_row_offset_);
	  reader.setUserMetaDataRows(this->meta_data_rows_);

	  vector<string> names = reader.fieldNames();


    if ( !this->x_values_.empty() ) return;

    // first set the containers!!
    // first set the containers!!
    if ( magCompare(this->name_mode_, "name" ) )
    	  nameXYMode(reader);
      else
    	  indexXYMode(reader);
    // Then read and interpret!
     ok = reader.read(error);
     if ( !ok) {
    	 MagLog::error() << error << endl;
    	 return;
     }

     // Now we interpret
    if ( magCompare(this->x_type_, "date" ) ) {
    	this->dateSetting(this->x_date_values_, this->x_values_, baseDateX_);
    }

    if ( magCompare(this->y_type_, "date" ) ) {
      	this->dateSetting(this->y_date_values_, this->y_values_, baseDateY_);

     }

    vector<double>::iterator x = this->x_values_.begin();
	vector<double>::iterator y = this->y_values_.begin();
	vector<double>::iterator v = this->v_values_.begin();

    while ( x != this->x_values_.end() && x != this->x_values_.end() ) {
    	double val = 0;
    	if ( v != v_values_.end() ) {
    		val = *v;
    		++v;
    	}
    	push_back(new UserPoint(*x, *y, val));
    	if ( same(*x, x_missing_) ||  same(*y, y_missing_) )
    		back()->flagMissing();
    	++x;
    	++y;
    }

	//Meta-data
	map<string,string> meta=reader.userMetaData();	
	
	map<string,string>::const_iterator metaIt;
  	if( (metaIt=meta.find("Metview::type")) != meta.end() &&
      	     metaIt->second == "FLEXTRA")
  	{
		setInfo("_datatype","TABLE_flextra");
		setInfo("_description","FLEXTRA Trajectories");
	}
	
	for(map<string,string>::const_iterator it=meta.begin(); it != meta.end(); it++)
  	{
    		setInfo(it->first,it->second);
  	}	
}


void TableDecoder::customisedPoints(const Transformation& transformation, const std::set<string>&, CustomisedPointsList& out)
{
	if ( transformation.coordinateType() == Transformation::GeoType )
		prepareGeo();
	else
		prepareXY();

	vector<double>::const_iterator x = x_values_.begin();
	vector<double>::const_iterator y = y_values_.begin();
	vector<double>::const_iterator xc = this->xc_values_.begin();
	vector<double>::const_iterator yc = this->yc_values_.begin();
	vector<double>::const_iterator v = this->v_values_.begin();
	while (  x != x_values_.end() && y != y_values_.end()  ) {
				double valx = *x;
				double valy = *y;
				if ( magCompare(this->x_type_, "date" ) ) {
					DateTime ref(transformation.getReferenceX());
					double shift = ref - this->baseDateX_;
					valx -= shift;
				}
				if ( magCompare(this->y_type_, "date" ) ) {
					DateTime ref(transformation.getReferenceY());
					double shift = ref - this->baseDateY_;
					valy -= shift;
				}
		    	CustomisedPoint* point = new CustomisedPoint();
		    	bool todelete = true;
		    	if ( transformation.in(valx, valy) ) {
		    		point->longitude(valx);
		    		point->latitude(valx);
		    		out.push_back(point);
		    		todelete = false;
		    	}


		    	(*point)["x"] = valx;
		    	++x;
		    	(*point)["y"] = valy;
		    	++y;


		        double speed = 0;
		    	if ( xc != this->xc_values_.end() && yc != this->yc_values_.end() ) {
		    		speed = sqrt( (*xc * *xc)  +  (*yc * *yc) );
		    		(*point)["x_component"] = *(xc++);
		    		(*point)["y_component"] = *(yc++);
		    	}
		    	if ( v != this->v_values_.end() ) {
		    		(*point)["colour_component"] = *(v++);
		    	}
		    	else
		    		(*point)["colour_component"] = speed;

		        if ( todelete ) delete point;
		    	
		    }

}
  	
  	
MatrixHandler& TableDecoder::matrix()
{



	if  ( !matrix_ ) {
		prepareXY();
		matrix_ = (*table_binning_)(*this);
	}

	this->matrixHandlers_.push_back(new MatrixHandler(*matrix_));

	return *(this->matrixHandlers_.back());
}


void TableDecoder::getReady(const Transformation& transformation)
{
	/*
	try {
		for ( vector<string>::const_iterator x = date_x_.begin(); x != date_x_.end(); ++x )
			x_.push_back(transformation.x(*x));
		}

	catch (...) {}
	try {
		for ( vector<string>::const_iterator y = date_y_.begin(); y != date_y_.end(); ++y )
			y_.push_back(transformation.y(*y));
	}
	catch (...) {}
	*/
}

void TableDecoder::visit(Transformation& transformation)
{
	// get the data ...
	try {
		if ( transformation.coordinateType() == Transformation::GeoType )
			prepareGeo();
		else
			prepareXY();

		if ( transformation.getAutomaticX() ) {

			if ( magCompare(this->x_type_, "date" ) ) {
				double min = ( this->x_values_.empty() ) ? 0 : *std::min_element(this->x_values_.begin(), this->x_values_.end());
				double max = ( this->x_values_.empty() ) ? 24*3600 : *std::max_element(this->x_values_.begin(), this->x_values_.end());
				transformation.setDataMinMaxX(min, max, this->baseDateX_);

			}
			else {
				double min = ( this->x_values_.empty() ) ? 0 : *std::min_element(this->x_values_.begin(), this->x_values_.end());
				double max = ( this->x_values_.empty() ) ? 100 : *std::max_element(this->x_values_.begin(), this->x_values_.end());
				transformation.setMinMaxX(min, max);

			}
		}
		if ( transformation.getAutomaticY() ) {
			if ( magCompare(this->y_type_, "date" ) ) {
				double min = ( this->y_values_.empty() ) ? 0 : *std::min_element(this->y_values_.begin(), this->y_values_.end());
				double max = ( this->y_values_.empty() ) ? 24*3600 : *std::max_element(this->y_values_.begin(), this->y_values_.end());

				transformation.setDataMinMaxY(min, max, this->baseDateY_);
			}
			else {
				double min = ( this->y_values_.empty() ) ? 0 : *std::min_element(this->y_values_.begin(), this->y_values_.end());
				double max = ( this->y_values_.empty() ) ? 100 : *std::max_element(this->y_values_.begin(), this->y_values_.end());

				transformation.setMinMaxY(min, max);
			}
		}
	}
	catch ( ... ) {}
}


void TableDecoder::customisedPoints(const std::set<string>&, CustomisedPointsList& out)
{
	prepareXY();
	vector<double>::const_iterator x = x_values_.begin();
	vector<double>::const_iterator y = y_values_.begin();
	vector<double>::const_iterator xc = this->xc_values_.begin();
	vector<double>::const_iterator yc = this->yc_values_.begin();
	vector<double>::const_iterator v = this->v_values_.begin();
	while (  x != x_values_.end() || y != y_values_.end()  ) {
		    	CustomisedPoint* point = new CustomisedPoint();

		    		point->longitude(*x);
		    		point->latitude(*y);
		    		out.push_back(point);

		    	if ( x != x_values_.end() ) (*point)["x"] = *(x++);
		    	if ( y != y_values_.end() ) (*point)["y"] = *(y++);
		        double speed = 0;
		    	if ( xc != this->xc_values_.end() && yc != this->yc_values_.end() ) {
		    		speed = sqrt( (*xc * *xc)  +  (*yc * *yc) );
		    		(*point)["x_component"] = *(xc++);
		    		(*point)["y_component"] = *(yc++);
		    	}
		    	if ( v != this->v_values_.end() ) {
		    		(*point)["colour_component"] = *(v++);
		    	}
		    	else
		    		(*point)["colour_component"] = speed;
	}
}


void TableDecoder:: visit(TextVisitor& title)
{
	//if ( !title_.empty() )
	//	title.add(new TextEntry(title_));

	if(MetviewIcon::info("_datatype") == "TABLE_flextra")  
	{	
	  	string s="FLEXTRA: ";		

		string date=MetviewIcon::info("startDate");
		string time=MetviewIcon::info("startTime");
		string lat=MetviewIcon::info("startLat");
		string lon=MetviewIcon::info("startLon");
		string lev=MetviewIcon::info("startZ");
				
		s+=MetviewIcon::info("direction") + " " + MetviewIcon::info("type");
		
		if(!date.empty())
		{
			s+= " " + date;
			if(!time.empty())
			{
				s+= " " + time;
			}	
		}	
			
		if(!lev.empty())
		{
			s+= " " + lev +  "m";
		}
		
		if(!lat.empty() && !lon.empty())
		{
			s+= " " + MetviewIcon::info("startComment") + " (" + lon + ", " + lat + ")";
		}


		title.addAutomaticTitle(s);
	}
}


string TableDecoder::legendText(double valMin,double valMax)
{
	if(MetviewIcon::info("_datatype") == "TABLE_flextra")  
	{
		ostringstream sst;
	    	sst << valMin; 
		string valStr=sst.str();
		
		string id,date,time,lev;		
		id=MetviewIcon::info("id_TR");
		date=MetviewIcon::info("startDate_TR");
		time=MetviewIcon::info("startTime_TR");
		lev=MetviewIcon::info("startZ_TR");

		if(id.empty() || time.empty() || date.empty() || lev.empty())
		{
		  	return string();
		}
		
		Tokenizer tk("/");
		vector<string> idVec,dateVec,timeVec,levVec;
		tk(id,idVec);
		tk(date,dateVec);
		tk(time,timeVec);
		tk(lev,levVec);		
		
		if(idVec.size() != timeVec.size() || timeVec.size() != dateVec.size() || 
		     dateVec.size() != levVec.size() )
		{
		  	return string();
		}
		
		for(unsigned int i=0; i < idVec.size(); i++)
		{
		  	if(idVec[i] == valStr)
			{
				return dateVec[i].substr(dateVec[i].size()-2,2) + "/" + timeVec[i].substr(0,2) + 
				" " + levVec[i] + " m";
			}
		}
	}
	return string();
}


PointsHandler& TableDecoder::points(const Transformation& transformation, bool all)
{

	// fisrt we adjust point!
	// we create another poin list with adjisted points!
	PointsList* list = new PointsList();

	for (vector<UserPoint*>::iterator point = begin(); point != end(); ++point) {
		UserPoint *pt = new UserPoint(**point);
		stack<UserPoint> duplicates;
		check(transformation, pt, duplicates);
		while ( !duplicates.empty() ) {
			list->push_back(new UserPoint(duplicates.top()));
			duplicates.pop();
		}
	}

	// then we filter them  we filter the points
	if ( all )
		this->pointsHandlers_.push_back(new PointsHandler(*list));
	else
		this->pointsHandlers_.push_back(new BoxPointsHandler(*list, transformation, true));

	return *(this->pointsHandlers_.back());
}


bool TableDecoder::check(const Transformation& transformation, UserPoint* point,stack<UserPoint>& duplicates )
{
	// We need to adjust the point if we have a date axis!
	if ( magCompare(x_type_, "date" ) ) {
		DateTime ref(transformation.getReferenceX());
		double shift = ref - baseDateX_;
		point->x_ -= shift;
	}
	if ( magCompare(y_type_, "date" ) ) {
		DateTime ref(transformation.getReferenceY());
		double shift = ref - baseDateY_;
		point->y_ -= shift;
	}

	transformation.wraparound(*point, duplicates);
	if ( duplicates.empty()  ) {
		// we falg the point and add to the list!
		point->flagMissing();
		duplicates.push(*point);
		return false;// point has been adjusted and is not in the transformation view
	}
	return true; // point and eventual duplicates! has been adjusted and is in the transformation view!
}


void TableDecoder::customisedPoints(const Transformation& t, const std::set<string>& n, CustomisedPointsList& out, bool)
{
	customisedPoints(t, n, out);
}


void TableDecoder::visit(ValuesCollector& points)
{

	const Transformation& transformation = points.transformation();
	
	points.setCollected(true);
  	 
	if(points.size() <=0 || size() == 0)
	  	return;
	
	if(values_.empty())
		points.setHasValue(false);
		
	for (ValuesCollector::iterator point =  points.begin(); point != points.end(); ++point)
	{
	  	double y=(*point).y(); //lat
	  	double x=(*point).x(); //lon
		
		vector<int> idxV;		
		for(int i=0; i < size(); i++)
		{
			if(fabs(at(i)->y()-y) < points.searchRadiusY() &&
			   fabs(at(i)->x()-x) <  points.searchRadiusX())
			{
			  	idxV.push_back(i);
			}
		}
		
		if(idxV.size() ==0)
			continue;  
		
		double dist=10000000.;
		int minIdx=-1;
		
		//MagLog::debug() << "odb collect idxV : " << lat << " " << lon << " " << idxV.size() << endl;
 		
		for(int i=0; i < idxV.size(); i++)
		{  			
		  	int idx=idxV[i];
			double d;
			if(transformation.coordinateType() == Transformation::GeoType )
			{  
				d=magics::geoDistanceInKm(at(idx)->y(),at(idx)->x(),y,x);
			}
			else
			{
			  	d=(at(idx)->x()-x)*(at(idx)->x()-x) +
			        	 (at(idx)->y()-y)*(at(idx)->y()-y);
			}
			  					
			if(d < dist)
			{
			  	minIdx=idx;
				dist=d;
			}			
		}	
		if(minIdx>=0)  
			(*point).push_back(new ValuesCollectorData(at(minIdx)->x(),
							       at(minIdx)->y(),
							       at(minIdx)->value(),
							       dist));					     			
	}	  
}

double TableDecoder::indexToNumber(const string& str)
{
	if ( str.empty() )
		return -1;
	
	double r;
	std::stringstream ss(str);
	ss >> r;
	return r;
}

} //end namespace
