/******************************** 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 Netcdf.cc
/*!
 Sylvie Lamy-Thepaut - ECMWF Apr 02
 
 Changes:
 
   Apr 06: update for GCC 4.0 (Stephan) 
*/
#include <Netcdf.h>
#include <MagException.h>
#include <MagLog.h>
 

using namespace magics;


static bool isVariable(NcVar* var)
{
    if (var->num_dims() != 1) return true;
    
    string name0(var->name());
    string name1(var->get_dim(0)->name());
    if ( name0 == name1) return false;
    return true; 
}

template <class From, class To>
Convertor<From,To>::Convertor(NetVariable& var) : variable_(var)
{
	To scale(1);
	To offset(0);
	scale_factor_ = variable_.getAttribute("scale_factor", scale);
	add_offset_ = variable_.getAttribute("add_offset", offset);
}

/*
template <class T>
void Accessor<T>::access(vector<T>& data, vector<long> start, vector<long> edges, NetVariable& var) 
{
        typename map<NcType, Accessor<T>*>::const_iterator accessor = accessors_->find(var.id_->type());
        if ( accessor == accessors_->end() ) throw new MagicsException("No accessor available");
        
        (*(*accessor).second)(data, start, edges, var);
}
*/

//template <class T>
//void Accessor<T>::access(vector<T>& data, NetDimension& dim) 
//{
//       typename map<NcType, Accessor<T>*>::const_iterator accessor = accessors_->find(var.id_->type());
//       if ( accessor == accessors_->end() ) throw new MagicsException("No accessor available");

//        (*(*accessor).second)(data, dim);
//}

template <class F, class T>   
void TypedAccessor<F,T>::operator() (vector<T>& to, vector<long>& start, vector<long>& edges, NetVariable& var) const
{
	F* from = new F[to.size()];
	var.id_->set_cur(&start[0]);
	var.id_->get(from, &edges[0]);
	// Convert the data....       
	std::transform(from, from + to.size(), to.begin(), Convertor<F, T>(var));
	delete[] from;
	
	
}

template <class F, class T> 
void TypedAccessor<F,T>::get (vector<F>& from, vector<long>& start, vector<long>& edges, NetVariable& var)const
{
	var.id_->set_cur(&start[0]);
	var.id_->get(from, &edges[0]);
} 

Netcdf::Netcdf(const string& path) : file_(path.c_str())
{
	if (file_.is_valid() == false) throw NoSuchNetcdfFile(path);
	for ( int v = 0; v < file_.num_vars(); v++)
	{ 
		NcVar* var = file_.get_var(v); 
		//MagLog::debug() << "Netcdf [" << path << "] " << ": add Variable --->" << var->name() << "\n"; 
		variables_.insert(std::make_pair(var->name(), NetVariable(var->name(), var, file_)));
		// Check that the variable is not a dimension! if not, add it to the dataset.
	    //MagLog::debug() << "Netcdf [" << path << "] " << ": add Variable --->" << var->name() << "\n"; 

		if (isVariable(var)) dataset_.insert(std::make_pair(var->name(), NetVariable(var->name(), var, file_)));
	}
	MagLog::debug() << "Initialisation of  Netcdf [" << path << "] OK! " << "\n";  
	for ( int v = 0; v < file_.num_atts(); v++)
		{
			NcAtt* attr = file_.get_att(v);
			//MagLog::debug() << "Netcdf [" << path << "] " << ": add Variable --->" << var->name() << "\n";
			attributes_.insert(std::make_pair(attr->name(), NetAttribute(attr->name(), attr)));



		}
}


Netcdf::~Netcdf() 
{	
}

void Netcdf::print(ostream& out)  const
{
	out << "print Netcdf: " << "\n";
	out << "Variables: " << "\n";
	for (map<string, NetVariable>::const_iterator var = variables_.begin(); var != variables_.end(); ++var)
	{
		out << (*var).second;
	}
	out << "Dataset: " << "\n";
	for (map<string, NetVariable>::const_iterator var = dataset_.begin(); var != dataset_.end(); ++var)
	{
		out << (*var).second;
	}
}


struct Index
{
	static map<NcType, Index*>* tools_;
	Index(NcType type)
	{
		if ( tools_ == 0) tools_ = new  map<NcType, Index*>();
		tools_->insert(std::make_pair(type, this));
	}
	virtual int operator()(const string&,  NcValues*, long)
	{
		throw MagicsException("Index not implemented.."); 
	}
	static int get(const NcType& type, const string& val,  NcValues* values, long nb)
	{
		map<NcType, Index*>::const_iterator tool = tools_->find(type);
		if ( tool == tools_->end() ) throw new MagicsException("No Index available");
        
		return (*(*tool).second)(val, values, nb);
	}
};

struct FloatIndex: public Index
{
	FloatIndex() : Index(ncFloat) {}
    
	virtual int operator()(const string& val,  NcValues* values, long nb )
	{
		float value = atof(val.c_str());
                if ( nb == 1 && values->as_float(0) == value) return 0;
		for (int i = 0; i < nb - 1; i++) {
			if (values->as_float(i) == value) return i;
			if (values->as_float(i+1) == value) return i+1;
			if (values->as_float(i) < value && value  < values->as_float(i+1) ) return i;  
			if (values->as_float(i+1) < value && value  < values->as_float(i) ) return i+1;     
		}
		throw MagicsException("No such value : " + val);
	}
};
struct DoubleIndex: public Index
{
	DoubleIndex() : Index(ncDouble) {}
    
	virtual int operator()(const string& val,  NcValues* values, long nb )
	{
		double value = tonumber(val);
        if ( nb == 1 && values->as_double(0) == value) return 0;
		for (int i = 0; i < nb - 1; i++) {
			if (values->as_double(i) == value) return i;
			if (values->as_double(i+1) == value) return i+1;
			if (values->as_double(i) < value && value  < values->as_double(i+1) ) return i;  
			if (values->as_double(i+1) < value && value  < values->as_double(i) ) return i+1;     
		}
		throw MagicsException("No such value : " + val);
	}
};


struct IntIndex: public Index
{
	IntIndex() : Index(ncInt) {}
    
	virtual int operator()(const string& val,  NcValues* values, long nb )
	{
		int value = atoi(val.c_str());
        	 if ( nb == 1 && values->as_int(0) == value) return 0;

		for (int i = 0; i < nb - 1; i++)
		{
			if (values->as_int(i) == value) return i;
			if (values->as_int(i+1) == value) return i+1;
			if ( values->as_int(i) < value && value < values->as_int(i+1) ) return i;   
			if ( values->as_int(i+1) < value && value < values->as_int(i) ) return i+1;     
		}
		throw MagicsException("No such value : " + val);
	}
};

struct StringIndex: public Index
{
	StringIndex() : Index(ncInt) {}
    
	virtual int operator()(const string& val,  NcValues* values, long nb )
	{
		for (int i = 0; i < nb; i++)
		{
			string read(values->as_string(i));
			if ( read == val) return i;
		}
		throw MagicsException("No such value : " + val);
	}
};

map<NcType, Index*>*  Index::tools_ = 0;

static FloatIndex float_index;
static IntIndex int_index;
static DoubleIndex double_index;

int  NetDimension::index(const string& val)
{
	return Index::get(variable_->type(), val, variable_->values(), variable_->num_vals());
}


void NetDimension::first(const string& val)
{
	if ( variable_ ) first_ = index(val);
}


void NetDimension::last(const string& val)
{
	if (!variable_ ) return;
	int last = index(val);     
	if ( last < first_ )
	{
		MagLog::warning() << "last position (" + val + ") < first position: exchange " << "\n";
		int tmp = first_;
		first_ = last;
		last = tmp;
	} 
	dim_ = last - first_ + 1;   
}


NetVariable::NetVariable(const string& name, NcVar* id, const NcFile& file): name_(name), id_(id) 
	{
		for (int d = 0; d < id_->num_dims(); d++)
		{
			NcDim* dim = id_->get_dim(d);
			NcVar* var = 0;
			string dim_name = dim->name();
		    for (int v = 0; v < file.num_vars(); v++) {
		    	 string var_name = file.get_var(v)->name();
		         if (var_name == dim_name) var = file.get_var(dim->name());
		         
		    }
			dimensions_[dim->name()]= NetDimension(dim, var, d); 
		}
		for (int a = 0; a < id_->num_atts(); a++)
		{
			NcAtt* att = id_->get_att(a);
			attributes_[att->name()] = NetAttribute(att->name(), att); 
		}
	}

namespace magics {
	template<> map<NcType, Accessor<double>*>*  Accessor<double>::accessors_ = 0;
	template<> map<NcType, Accessor<float>*>*  Accessor<float>::accessors_ = 0;
} // end namespace


static TypedAccessor<short, float>  short_float_accessor(ncShort);
static TypedAccessor<int, float>  int_float_accessor(ncInt);
static TypedAccessor<float, float>  float_float_accessor(ncFloat);
static TypedAccessor<double, float> double_float_accessor(ncDouble);

static TypedAccessor<ncbyte, double>  byte_double_accessor(ncByte);
static TypedAccessor<short, double>  short_double_accessor(ncShort);
static TypedAccessor<int, double>  int_double_accessor(ncInt);
static TypedAccessor<float, double>  float_double_accessor(ncFloat);
static TypedAccessor<double, double> double_double_accessor(ncDouble);

