/******************************** 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 OdaDecoder.cc
    \brief Implementation of the Template class OdaDecoder.
    
    Magics Team - ECMWF 2004
    
    Started: Fri 16-Jan-2004
    
    Changes:
    
*/

#include <vector>
#include <map>
#include <set>

#include "oda.h"
extern "C" {
#include "odbcapi.h"
}



#include "../common/Timer.h"
#include "OdaDecoder.h"

#include "SciMethods.h"
#include "TextVisitor.h"



OdaGeoDecoder::OdaGeoDecoder() 
{
}


OdaGeoDecoder::~OdaGeoDecoder() 
{
	
}

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

double degrees(double val) 
{
	return val;
}

double radians(double val) 
{
	return val*180/3.14;
}

typedef double (*Converter)(double);

map<string, Converter> converters_;



void OdaGeoDecoder::decode(const Transformation& transformation)
{    
	if ( !empty() ) 
		return;  

	try
	{
		odb_start();
		odb::Reader oda(path_);	
		odb::Reader::iterator it = oda.begin();
	
		size_t latIndex = it->columns().columnIndex(latitude_);
		size_t lonIndex = it->columns().columnIndex(longitude_);
		size_t valueIndex = it->columns().columnIndex(value_);	
	
		MagLog::info() <<  "Indices: " << latIndex << " " << lonIndex << " " << valueIndex << endl;
	
		unsigned int row=0;
	
		for(; it != oda.end(); ++it) 
		{			
			double lat = (*it)[latIndex];
			double lon = (*it)[lonIndex];
			position(lat, lon);
	
			if ( transformation.in(lon, lat)  ) 
			{
				stats_["value"].push_back((*it)[valueIndex]);
				push_back(GeoPoint(lon, lat, (*it)[valueIndex]));
	
				row++;
				if (nb_rows_ != -1 && row >= nb_rows_)
				{
					break;
				}
			}
		}

		MagLog::info() <<  "Number of rows: " << row << endl;

		computeStats();
	}
	catch(Exception e)
	{
		MagLog::error() << "Failed to read ODB data: " << e.what();
		return;
	}
}

void OdaGeoDecoder::visit(Transformation& transformation) {

	return;	

	decode();
	if ( empty() ) return;
	double minx = front().x_;
	double maxx = front().x_;
	double miny = front().y_;
	double maxy = front().y_;

	for (iterator point = begin(); point != end(); ++point) {
		if ( minx > point->x_ ) minx = point->x_;
		if ( miny > point->y_ ) miny = point->y_;
		if ( maxx < point->x_ ) maxx = point->x_;
		if ( maxy < point->y_ ) maxy = point->y_;
	}

	transformation.setDataMinX(minx);
	transformation.setDataMinY(miny);
	transformation.setDataMaxX(maxx);
	transformation.setDataMaxY(maxy);
}
void OdaGeoDecoder::decode()
{    
	if ( !empty() ) 
		return;  

	odb_start();
    	odb::Reader oda(path_);

	odb::Reader::iterator it = oda.begin();

	size_t latIndex = it->columns().columnIndex(latitude_);
	size_t lonIndex = it->columns().columnIndex(longitude_);
	size_t valueIndex = it->columns().columnIndex(value_);	
  
	MagLog::info() <<  "Indices: " << latIndex << " " << lonIndex << " " << valueIndex << endl;

	unsigned int row=0;

	for (; it != oda.end(); ++it) 
	{			
		double lat = (*it)[latIndex];
		double lon = (*it)[lonIndex];
		position(lat, lon);
		
		stats_["value"].push_back((*it)[valueIndex]);
		push_back(GeoPoint(lon, lat, (*it)[valueIndex]));

		row++;
		if (nb_rows_ != -1 && row >= nb_rows_)
		{
				break;
		}
		
	}

	//computeStats();

	MagLog::info() <<  "Number of rows: " << row << endl;
}
void OdaGeoDecoder:: visit(TextVisitor& title)
{
	if ( !title_.empty() )
		title.add(new TextEntry(title_));
	title.add(new TextEntry("OdbDatabase: " + path_));

	if(info("stats::points").empty())
	{
		title.add(new TextEntry(" No points found " ));		
	}
	else
	{
		if(info("stats::min").empty() &&  stats_["value"].size() >0)
		{	
		  	double min = *(std::min_element(stats_["value"].begin(), stats_["value"].end()));
			setInfo("stats::min",tostring(min));
		}
		
		if(info("stats::max").empty() &&  stats_["value"].size() >0)
		{	
		  	double max = *(std::max_element(stats_["value"].begin(), stats_["value"].end()));
			setInfo("stats::max",tostring(max));
		}
		title.add(new TextEntry("Min: " + info("stats::min") + "  Max: " + info("stats::max") + "    ( " + info("stats::points") + " points) " ));
	}

	/*if ( statistics_.empty() ) {
		title.add(new TextEntry(" No points found " ));
	}
	else {
		double min = *std::min_element(statistics_.begin(), statistics_.end());
		double max = *std::max_element(statistics_.begin(), statistics_.end());
		title.add(new TextEntry("Min: " + tostring(min) + "  Max: " + tostring(max) + "    ( " + tostring(statistics_.size()) + " points) " ));
	}*/
}

static 
double speed(double x, double y)
{
	return sqrt(x*x+ y*y);
}

void OdaGeoDecoder::customisedPoints(const std::set<string>&, CustomisedPointsList& list)
{
     	odb_start();
     	odb::Reader oda(path_);

	odb::Reader::iterator it = oda.begin();
		
	size_t latIndex = it->columns().columnIndex(latitude_);
	size_t lonIndex = it->columns().columnIndex(longitude_);
	size_t valueIndex = value_.empty() ? -1 :  it->columns().columnIndex(value_);	
	size_t xIndex = it->columns().columnIndex(x_);	
	size_t yIndex = it->columns().columnIndex(y_);	
			
	MagLog::info() <<  "Indices: " << latIndex << " " << lonIndex << " " << valueIndex << endl;
	
	unsigned int row=0;
		
	for (; it != oda.end(); ++it, row++) 
	{			
		if(nb_rows_ != -1 && row >= nb_rows_)
		{
			break;
		}

		double lat = (*it)[latIndex];
		double lon = (*it)[lonIndex];
		position(lat, lon);
					
		CustomisedPoint* point = new CustomisedPoint();		
		point->longitude(lon);
		point->latitude(lat);
		(*point)["x_component"] = (*it)[xIndex];
		(*point)["y_component"] = (*it)[yIndex];
		double val =  (valueIndex != -1) ? (*it)[valueIndex] : speed((*it)[xIndex],  (*it)[yIndex]);
		(*point)["colour_component"] = val;
		list.push_back(point);	
		stats_["value"].push_back(val);		
		stats_["x"].push_back((*it)[xIndex]);
		stats_["y"].push_back((*it)[yIndex]);	
	}	

	//computeStats();	

	MagLog::info() <<  "Number of rows: " << row << endl;
	
}

void OdaGeoDecoder::position(double& lat, double& lon)
{
	if ( converters_.empty() ) {
		converters_["degrees"] = &degrees;
		converters_["radians"] = &radians;
	}


	map<string, Converter>::iterator converter = converters_.find(lowerCase(unit_));

	if (converter == converters_.end() ) {
		MagLog::warning() << "odb_coordinates_unit:" << unit_ << " is not valid -->Change back to default:degrees" << endl;
		converter = converters_.find("degrees");
	}
	lat = (*converter->second)(lat);
	lon = (*converter->second)(lon);

}

void OdaGeoDecoder::customisedPoints(const Transformation& transformation, const std::set<string>&, CustomisedPointsList& list)
{

     	odb_start();
     	odb::Reader oda(path_);

     	odb::Reader::iterator it = oda.begin();

	size_t latIndex = it->columns().columnIndex(latitude_);
	size_t lonIndex = it->columns().columnIndex(longitude_);
	size_t valueIndex = value_.empty() ? -1 :  it->columns().columnIndex(value_);	
	size_t xIndex = it->columns().columnIndex(x_);	
	size_t yIndex = it->columns().columnIndex(y_);	
		
	MagLog::info() <<  "Indices: " << latIndex << " " << lonIndex << " " << valueIndex << endl;

	unsigned int row=0;
		
	for (; it != oda.end(); ++it) 
	{
		CustomisedPoint* point = new CustomisedPoint();		
		double lat = (*it)[latIndex];
		double lon = (*it)[lonIndex];
		position(lat, lon);
		if ( transformation.in(lon, lat)  ) 
		{							  
			point->longitude(lon);
			point->latitude(lat);
			double val =  (valueIndex != -1) ? (*it)[valueIndex] : speed((*it)[xIndex],  (*it)[yIndex]);
			(*point)["x_component"] = (*it)[xIndex];
			(*point)["y_component"] = (*it)[yIndex];
			(*point)["colour_component"] =val;
			list.push_back(point);	
			stats_["value"].push_back(val);			
		  	stats_["x"].push_back((*it)[xIndex]);
			stats_["y"].push_back((*it)[yIndex]);
	
			row++;
			if(nb_rows_ != -1 && row >= nb_rows_)
			{
				break;
			}
		}
	}

	//computeStats();	 

	MagLog::info() <<  "Number of rows: " << row << endl;		
}

void OdaGeoDecoder::initInfo()
{
	if(getX().empty())
	{
  		setInfo("_datatype","ODB_geopoints");
		setInfo("statsType","scalar");
		setInfo("value",getValue());
	}
	else
	{	  
		setInfo("_datatype","ODB_geovectors");
		setInfo("statsType","vector");
		if(getValue().empty())
			setInfo("value","computed speed");
		else
		  	setInfo("value",getValue());
		setInfo("x",getX());
		setInfo("y",getY());		
	}
	
	setInfo("path",getPath());
	setInfo("lat",getLatitude());
	setInfo("lon",getLongitude());
}


void OdaGeoDecoder::visit(ValuesCollector& points)
{
	points.setCollected(true);
  	 
	if(points.size() <=0 || size() == 0)
	  	return;
		
	for (ValuesCollector::iterator point =  points.begin(); point != points.end(); ++point)
	{
	  	double lat=(*point).y();
	  	double lon=(*point).x();
		
		vector<int> idxV;		
		for(int i=0; i < size(); i++)
		{
			if(fabs(at(i).latitude()-lat) < points.searchRadiusY() && 
			   fabs(at(i).longitude()-lon) <  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=magics::geoDistanceInKm(at(idx).latitude(),at(idx).longitude(),lat,lon); 
			
			if(d < dist)
			{
			  	minIdx=idx;
				dist=d;
			}			
		}	
		if(minIdx>=0)  
			(*point).push_back(ValuesCollectorData(at(minIdx).longitude(), 
							       at(minIdx).latitude(),
							       at(minIdx).value(),
							       dist));					     			
	}	  
}



void OdaGeoDecoder::visit(MetaDataCollector& mdc)
{	
	for(map<string, string>::iterator key = mdc.begin(); key != mdc.end(); ++key )
	{	    
		if(information_.find(key->first) == information_.end() &&
		  mdc.attribute(key->first).group() == MetaDataAttribute::StatsGroup)
		{
			  computeStats();
			  break;
		}
	}
	
	MetviewIcon::visit(mdc);
}  



OdaXYDecoder::OdaXYDecoder() : matrix_(0)
{
}


OdaXYDecoder::~OdaXYDecoder() 
{
	
}

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

void OdaXYDecoder::customisedPoints(const Transformation& transformation, const std::set<string>&, CustomisedPointsList& list)
{  
      	odb_start();
     	odb::Reader oda(path_);

     	odb::Reader::iterator it = oda.begin();

	int xIndex = static_cast<int>(it->columns().columnIndex(x_));
	int yIndex = static_cast<int>(it->columns().columnIndex(y_));
	int valueIndex = value_.empty() ? -1 :  static_cast<int>(it->columns().columnIndex(value_));	
	int xcIndex = x_component_.empty() ? -1 : static_cast<int>(it->columns().columnIndex(x_component_));
	int ycIndex = y_component_.empty() ? -1 : static_cast<int>(it->columns().columnIndex(y_component_));
	 
	MagLog::info() <<  "Indices: " << xIndex << " " << yIndex << " " << valueIndex << " " << xcIndex << " " <<  xcIndex << endl;

	unsigned int row=0;
		
	for (; it != oda.end(); ++it) 
	{
		CustomisedPoint* point = new CustomisedPoint();		
		double x = (*it)[xIndex];
		double y = (*it)[yIndex];
		if ( transformation.in(x,y)  ) 
		{							  
			point->longitude(x);
			point->latitude(y);
			(*point)["x"] = x;
			(*point)["y"] = y;
			
			if(xcIndex != -1 && ycIndex != -1)
			{		  	
				(*point)["x_component"] = (*it)[xcIndex];
				(*point)["y_component"] = (*it)[ycIndex];
				double val =  (valueIndex != -1) ? (*it)[valueIndex] : speed((*it)[xcIndex],  (*it)[ycIndex]);
				(*point)["colour_component"] =val;				
				stats_["value"].push_back(val);			
			}
			else if(valueIndex != -1)
			{
				double val =  (*it)[valueIndex];
				(*point)["colour_component"] = val;
				stats_["value"].push_back(val);	
			}
			
			list.push_back(point);			
		  	stats_["x"].push_back((*it)[xIndex]);
			stats_["y"].push_back((*it)[yIndex]);
	
			row++;
			if(nb_rows_ != -1 && row >= nb_rows_)
			{
				break;
			}
		}
	}

	//computeStats();	 

	MagLog::info() <<  "Number of rows: " << row << endl;	
}

void OdaXYDecoder::customisedPoints(const std::set<string>&, CustomisedPointsList& list)
{     	
  
  	odb_start();
     	odb::Reader oda(path_);

     	odb::Reader::iterator it = oda.begin();

	int xIndex = static_cast<int>(it->columns().columnIndex(x_));
	int yIndex = static_cast<int>(it->columns().columnIndex(y_));
	int valueIndex = value_.empty() ? -1 :  static_cast<int>(it->columns().columnIndex(value_));	
	int xcIndex = x_component_.empty() ? -1 : static_cast<int>(it->columns().columnIndex(x_component_));
	int ycIndex = y_component_.empty() ? -1 : static_cast<int>(it->columns().columnIndex(y_component_));
	 
	MagLog::info() <<  "Indices: " << xIndex << " " << yIndex << " " << valueIndex << " " << xcIndex << " " <<  xcIndex << endl;

	unsigned int row=0;
		
	for (; it != oda.end(); ++it) 
	{
		CustomisedPoint* point = new CustomisedPoint();		
		double x = (*it)[xIndex];
		double y = (*it)[yIndex];
							  
		point->longitude(x);
		point->latitude(y);
		(*point)["x"] = x;
		(*point)["y"] = y;
			
		if(xcIndex != -1 && ycIndex != -1)
		{		  	
			(*point)["x_component"] = (*it)[xcIndex];
			(*point)["y_component"] = (*it)[ycIndex];
			double val =  (valueIndex != -1) ? (*it)[valueIndex] : speed((*it)[xcIndex],  (*it)[ycIndex]);
			(*point)["colour_component"] =val;				
			stats_["value"].push_back(val);			
		}
		else if(valueIndex != -1)
		{
			double val =  (*it)[valueIndex];
			(*point)["colour_component"] = val;
			stats_["value"].push_back(val);	
		}
			
		list.push_back(point);			
		stats_["x"].push_back((*it)[xIndex]);
		stats_["y"].push_back((*it)[yIndex]);
	
		row++;
		if(nb_rows_ != -1 && row >= nb_rows_)
		{
				break;
		}
	}

	//computeStats();	 

	MagLog::info() <<  "Number of rows: " << row << endl;	
}

void OdaXYDecoder:: visit(TextVisitor& title)
{
	if ( !title_.empty() )
		title.add(new TextEntry(title_));
	title.add(new TextEntry("OdbDatabase: " + path_));
	
	string numStr=info("stats::points");
	if(numStr.empty() && stats_["x"].size() >0)
	{
	  	numStr=tostring(stats_["x"].size());
	}	
	  	
	
	if(numStr.empty())
	{
		title.add(new TextEntry(" No points found " ));		
	}
	else
	{
		title.add(new TextEntry("points: " + numStr));
	}	
}

void OdaXYDecoder::decode(const Transformation& transformation)
{
    if ( !empty() ) 
		return;  

	odb_start();
    	odb::Reader oda(path_);

	odb::Reader::iterator it = oda.begin();

	size_t xIndex = it->columns().columnIndex(x_);
	size_t yIndex = it->columns().columnIndex(y_);
	size_t valueIndex = value_.empty() ? -1 :  it->columns().columnIndex(value_);	
  
	MagLog::info() <<  "Indices: " << xIndex << " " << yIndex << " " << valueIndex << endl;

	unsigned int row=0;

	for (; it != oda.end(); ++it) 
	{			
		double x = (*it)[xIndex];
		double y = (*it)[yIndex];		

		if ( transformation.in(x, y)  ) 
		{
			double val =0;
			if (valueIndex!=-1) {
				val = (*it)[valueIndex];
				stats_["value"].push_back(val);
			}
			push_back(UserPoint(x, y, val));

			stats_["x"].push_back(x);
			stats_["y"].push_back(y);

			row++;
			if (nb_rows_ != -1 && row >= nb_rows_)
			{
				break;
			}
		}
	}
 
	//computeStats();

	MagLog::info() <<  "Number of rows: " << row << endl;

}

void OdaXYDecoder::decode()
{
    if ( !empty() ) 
		return;  

	odb_start();
    	odb::Reader oda(path_);

	odb::Reader::iterator it = oda.begin();

	size_t xIndex = it->columns().columnIndex(x_);
	size_t yIndex = it->columns().columnIndex(y_);
	size_t valueIndex = value_.empty() ? -1 :  it->columns().columnIndex(value_);	
  
	MagLog::info() <<  "Indices: " << xIndex << " " << yIndex << " " << valueIndex << endl;

	unsigned int row=0;

	for (; it != oda.end(); ++it) 
	{			
		double x = (*it)[xIndex];
		double y = (*it)[yIndex];
				
		double val =0;
		if (valueIndex!=-1) {
			val = (*it)[valueIndex];
			stats_["value"].push_back(val);
		}

		stats_["x"].push_back(x);
		stats_["y"].push_back(y);
		push_back(UserPoint(x, y, val));

		row++;
		if (nb_rows_ != -1 && row >= nb_rows_)
		{
			break;
		}
		
	}
 
	//computeStats();

	MagLog::info() <<  "Number of rows: " << row << endl;

}
void OdaXYDecoder::visit(Transformation& transformation) {
	decode();
	if ( empty() ) return;
	double minx = front().x_;
	double maxx = front().x_;
	double miny = front().y_;
	double maxy = front().y_;

	for (iterator point = begin(); point != end(); ++point) {
		if ( minx > point->x_ ) minx = point->x_;
		if ( miny > point->y_ ) miny = point->y_;
		if ( maxx < point->x_ ) maxx = point->x_;
		if ( maxy < point->y_ ) maxy = point->y_;
	}

	transformation.setDataMinX(minx);
		transformation.setDataMinY(miny);
		transformation.setDataMaxX(maxx);
		transformation.setDataMaxY(maxy);

}

MatrixHandler<UserPoint>& OdaXYDecoder::matrix()
{
	if  ( !matrix_ ) {
		decode();
		matrix_ = (*OdaXYDecoderAttributes::binning_)(*this);
	}

	matrixHandlers_.push_back(new MatrixHandler<UserPoint>(*matrix_));
	return *(matrixHandlers_.back());
}


void OdaXYDecoder::initInfo()
{
	setInfo("_datatype","ODB_xy");
	setInfo("statsType","scatter");
	setInfo("path",getPath());
	setInfo("value",getValue());
	setInfo("x",getX());
	setInfo("y",getY());
}

void OdaXYDecoder::visit(ValuesCollector& points)
{
	if(stats_.find("value") == stats_.end())
	{
	  	points.setHasValue(false);
	}
  
  	if(!matrix_ && stats_.find("value") == stats_.end())
	{
		points.setCollected(true);	
	}
	else
	{  	
		points.setCollected(true);
	}
	
	
	if( points.size() <=0)
	   return;

	for (ValuesCollector::iterator point =  points.begin(); point != points.end(); ++point)
	{
	  	double y=(*point).y();
	  	double x=(*point).x();
		
		
		if(!matrix_)
		{
			vector<int> idxV;		
			for(int i=0; i < size(); i++)
			{
				if(fabs(at(i).x()-x) < points.searchRadiusX()&& 
			  	   fabs(at(i).y()-y) < points.searchRadiusY())		  
				{
			  		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=(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(ValuesCollectorData(at(minIdx).x(), 
							       at(minIdx).y(),
							       at(minIdx).value(),
							       dist));
					     		
		}			
		else
		{
			double val,xp,yp;
			val=matrix_->nearest(y,x,yp,xp);
			if(!same(val,matrix_->missing()))
			{  
				(*point).push_back(ValuesCollectorData(xp,yp,val,-1.));
			}	
		}	
	}
}

void OdaXYDecoder::visit(MetaDataCollector& mdc)
{	
	for(map<string, string>::iterator key = mdc.begin(); key != mdc.end(); ++key )
	{	    
		if(information_.find(key->first) == information_.end() &&
		  mdc.attribute(key->first).group() == MetaDataAttribute::StatsGroup)
		{
			  computeStats();
			  break;
		}
	}
	
	MetviewIcon::visit(mdc);
}  

