/******************************** 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 NetcdfMatrixInterpretor.h
    \brief Implementation of the Template class NetcdfMatrixInterpretor.
    
    Magics Team - ECMWF 2004
    
    Started: Tue 17-Feb-2004
    
    Changes:
    
*/

#include <limits>

#include "NetcdfMatrixInterpretor.h"
#include "Factory.h"
#include "Netcdf.h"
#include "Coordinate.h"
#include "Layer.h"
#include "TextVisitor.h"
#include "Tokenizer.h"
using namespace magics;

NetcdfMatrixInterpretor::NetcdfMatrixInterpretor() : baseDateX_(""), baseDateY_("")
{
}


NetcdfMatrixInterpretor::~NetcdfMatrixInterpretor() 
{
}


void NetcdfMatrixInterpretor::interpretAsMatrix(Matrix** matrix)
{
	
	MagLog::debug() << "NetcdfMatrixInterpretor::interpret()--->" << *this << "\n";
	if ( *matrix ) return;
	
	matrix_ = new Matrix();
	
	*matrix = matrix_;
	if ( !matrix_->empty() ) return;
    
	matrix_->missing(std::numeric_limits<double>::max());

	Netcdf netcdf(path_);
	string title = netcdf.getAttribute("title", "NO TITLE");


    x();
    y();
	// get the data ...
	try {
		map<string, string> first, last;
		setDimensions(dimension_, first, last);
		MagLog::debug() << "matrix_" << matrix_->size() << "\n";
        
		netcdf.get(field_, *matrix_, first, last);
		
		MagLog::debug() << "matrix_[" << matrix_->size() << ", " << scaling_ << ", " << offset_ << "]" << "\n";

		matrix_->multiply(scaling_);
		matrix_->plus(offset_);
        
       
        
		MagLog::debug() << "matrix_[" << matrix_->size() << ", " << scaling_ << ", " << offset_ << "\n";
        
		
		vector<double> col;
	  
		string missing = netcdf.getVariableAttribute(field_, missing_attribute_, "");
		if ( !missing.empty() ) {
			std::stringstream ss(missing);		
			double m;
			ss >> m;
            matrix_->missing(m);
		}
		
		
		matrix_->setColumnsAxis(columns_);
		matrix_->setRowsAxis(rows_);
		
    
		MagLog::dev() << *matrix_ << "\n";
	}
	catch (MagicsException& e)
	{
		MagLog::error() << e << "\n";
	}    
}



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



bool NetcdfMatrixInterpretor::reference_date(Netcdf& netcdf, const string& var, const string& refdate, string& basedate, vector<double>& coords)
{
	static map<string, double> factors;
	if ( factors.empty() ) {
		factors["hours"] = 3600;
		factors["days"] = 24*3600;
	}
	string date = netcdf.getVariableAttribute(var, "reference_date", "");
	if ( date.empty() ) return false;
	string units = netcdf.getVariableAttribute(var, "units", "");
	basedate = date;
	double diff = ( refdate.empty() ) ? 0 : DateTime(date) - DateTime(refdate) ;
	map<string, double>::const_iterator factor = factors.find(units);
	if ( factor != factors.end() )
		std::transform(coords.begin(), coords.end(),  coords.begin(), Multiply(factor->second, std::numeric_limits<double>::max()));
	std::transform(coords.begin(), coords.end(),  coords.begin(), Plus(diff, std::numeric_limits<double>::max()));
}

bool NetcdfMatrixInterpretor::cf_date(Netcdf& netcdf, const string& var, const string& refdate, string& basedate, vector<double>& coords)
{
	//Step 1 : try to find a attribute long_name = time
	//Step 2 : Parse the attribute  units : days since date
	static map<string, double> factors;
	if ( factors.empty() ) {
		factors["hours"] = 3600;
		factors["days"] = 24*3600;
	}
	string date = netcdf.getVariableAttribute(var, "long_name", "");
	if ( date.empty() ) return false;
	if ( date != "time" ) return false;

	string units = netcdf.getVariableAttribute(var, "units", "");
	if ( units.empty() ) return false;

	// Now we parse the string !
	vector<string> tokens;
	Tokenizer tokenizer(" ");
	tokenizer(units, tokens);



	basedate = tokens[2];
	double diff = ( refdate.empty() ) ? 0 : DateTime(basedate) - DateTime(refdate) ;
	map<string, double>::const_iterator factor = factors.find(tokens[0]);
	if ( factor != factors.end() )
		std::transform(coords.begin(), coords.end(),  coords.begin(), Multiply(factor->second, std::numeric_limits<double>::max()));
	std::transform(coords.begin(), coords.end(),  coords.begin(), Plus(diff, std::numeric_limits<double>::max()));
}
	
void NetcdfMatrixInterpretor::x()
{
	if ( !columns_.empty() )
			return;

    Netcdf netcdf(path_);
    map<string, string> first, last;
    setDimensions(dimension_, first, last);

    try {
        netcdf.get(x_, columns_, first, last);
        baseDateX_ = "";
        if ( !reference_date(netcdf, x_, refDateX_, baseDateX_, columns_) )
        	cf_date(netcdf, x_, refDateX_, baseDateX_, columns_);
	} 
    catch (...) {
        int x = netcdf.getDimension(x_);
        for (int i = 0; i < x; i++) 
            columns_.push_back(i);
    }

    if ( aux_x_.empty() )
    	return;
    try {
    	vector<double> aux;

    	netcdf.get(aux_x_,aux, first, last);
    	if ( !aux.empty() ) {
    		ostringstream geominx, geomaxx;
    		geominx << columns_.front() << "/" << aux.front() << endl;
    		geomaxx << columns_.back() << "/" << aux.back() << endl;
    		geoMinX_ = geominx.str();
    		geoMaxX_ = geomaxx.str();
    	}
    }
    catch (...) {
    }

}

void NetcdfMatrixInterpretor::y()
{

	if ( !rows_.empty() )
		return;

    Netcdf netcdf(path_);
    map<string, string> first, last;
    setDimensions(dimension_, first, last);
    try {
    	 netcdf.get(y_, rows_, first, last);
    	 baseDateY_ = "";
    	 if ( !reference_date(netcdf, y_, refDateY_, baseDateY_, rows_) )
    	      cf_date(netcdf, y_, refDateY_, baseDateY_, rows_);
	} 
    catch (...) {
        int y = netcdf.getDimension(y_);
        for (int i = 0; i < y; i++) 
            rows_.push_back(i);
    } 

    if ( aux_y_.empty() )
    	return;
    try {
    	vector<double> aux;
    	netcdf.get(aux_y_,aux, first, last);
    	if ( !aux.empty() ) {
    		ostringstream geominx, geomaxx;
    		geominx << rows_.front() << "/" << aux.front() << endl;
    		geomaxx << rows_.back() << "/" << aux.back() << endl;
    		geoMinY_ = geominx.str();
    		geoMaxY_ = geomaxx.str();
    	}
    }
    catch (...) {
    }
}
void NetcdfMatrixInterpretor::getReady(const Transformation& transformation)
{
	// adjust the data to the transformation..

	refDateX_ = ( !transformation.getAutomaticX() ) ? "" : transformation.getReferenceX();
	columns_.clear();
	x();
	refDateY_ = ( !transformation.getAutomaticY() ) ? "" : transformation.getReferenceY();
	rows_.clear();
	y();

}

void NetcdfMatrixInterpretor::visit(Transformation& transformation)
{
	// get the data ...
		// by default, respect the internal organisation of the data..
		try {

			refDateX_ = ( transformation.getAutomaticX() ) ? "" : transformation.getReferenceX();
			x();
			refDateY_ = ( transformation.getAutomaticY() ) ? "" : transformation.getReferenceY();
			y();

			if ( transformation.getAutomaticX() ) {
				if ( !this->baseDateX_.empty() ) {
					transformation.setDataMinX(columns_.front(), this->baseDateX_);
					transformation.setDataMaxX(columns_.back(), this->baseDateX_);
				}
				else if ( !this->geoMinX_.empty() ) {
					transformation.setDataMinX(columns_.front(), this->geoMinX_);
					transformation.setDataMaxX(columns_.back(), this->geoMaxX_);
				}
				else
				{
					transformation.setMinX(columns_.front());
					transformation.setMaxX(columns_.back());
				}
			}
			if ( transformation.getAutomaticY() ) {
				if ( !this->baseDateY_.empty() ) {
					transformation.setDataMinY(rows_.front(), this->baseDateY_);
					transformation.setDataMaxY(rows_.back(), this->baseDateY_);
				}
				else if ( !this->geoMinY_.empty() ) {
					transformation.setDataMinY(rows_.front(), this->geoMinY_);
					transformation.setDataMaxY(rows_.back(), this->geoMaxY_);
				}
				else {
					transformation.setMinY(rows_.front());
					transformation.setMaxY(rows_.back());
				}
			}

		}
		catch ( ... ) {}
		

}

void NetcdfMatrixInterpretor::statsData(map<string,vector<double> >& stats)
{
	if(matrix_)
	{
	  	for(unsigned int i=0; i < matrix_->size(); i++)
		{  
			if(matrix_->at(i) != matrix_->missing())
			{
			  	stats["value"].push_back(matrix_->at(i));
			}
		}
	}	
}

void NetcdfMatrixInterpretor::visit(MetaDataCollector& mdc)
{
	mdc["_datatype"]="NetCDF_matrix";
	mdc["path"]=path_;
	mdc["x"]=x_;
	mdc["y"]=y_;
	mdc["value"]=field_;	
	mdc["statsType"]="scalar";
	
	Netcdf nc(path_);
	
	string attrKey;
	string attrVal;		

	//Value attributes
	getAttributes(nc,field_,attrKey,attrVal);		
	if(!attrKey.empty())
	{
		mdc["valueAttrKey"]=attrKey;
		mdc["valueAttrValue"]=attrVal;
	}
}

void NetcdfMatrixInterpretor::visit(ValuesCollector& vcp,PointsList&)
{
	vcp.setCollected(true);
	
	assert(matrix_); 
		
	const Transformation& transformation = vcp.transformation();
  	MatrixHandler* box =  transformation.prepareData(*matrix_);
	for (ValuesCollector::iterator point =  vcp.begin(); point != vcp.end(); ++point) {
		point->push_back(new ValuesCollectorData(point->x(),point->y(),box->nearest(point->y(), point->x()),-1.));
	}

	//for (ValuesCollector::iterator point =  points.begin(); point != points.end(); ++point) {
	//	point->push_back(new ValuesCollectorData(point->x(), point->y(),matrix_->nearest(point->y(), point->x()),-1.));
	//}
}

void NetcdfMatrixInterpretor::visit(TextVisitor& title)
{
	vector<string> titles;

	title.titles(titles);


	for ( vector<string>::const_iterator t = titles.begin(); t != titles.end(); ++t ) {
		MagLog::debug() << "NetcdfMatrixInterpretor::visit" << *t << endl;

	}
	Netcdf netcdf(path_);
	title.addAutomaticTitle(netcdf.getAttribute("title", "NO TITLE"));
}


void NetcdfMatrixInterpretor::interpretAsPoints(PointsList& points, const Transformation& transformation)
{
	refDateX_ = transformation.getReferenceX();
	refDateY_ = transformation.getReferenceY();

	Matrix* data = 0;

	interpretAsMatrix(&data);

	vector<double>::iterator d = data->begin();
	for (vector<double>::iterator row = rows_.begin(); row != rows_.end(); ++row)
		for (vector<double>::iterator column = columns_.begin(); column != columns_.end(); ++column) {
			UserPoint* point = new UserPoint(*column, *row, *d);
			++d;
			points.push_back(point);
		}


}
