/***************************** LICENSE START ***********************************

 Copyright 2012 ECMWF and INPE. This software is distributed under the terms
 of the Apache License version 2.0. In applying this license, ECMWF does not
 waive the privileges and immunities granted to it by virtue of its status as
 an Intergovernmental Organization or submit itself to any jurisdiction.

 ***************************** LICENSE END *************************************/

#include "MvScm.h"

#include "MvMiscelaneous.h"

#include <fstream>
#include <set>
#include <sstream>
#include <string.h>

//A nicer solution is needed!
bool MvScm::needConsistency_=false;

//=========================================================
//
//  MvScmConsistency
//
//=========================================================

void MvScmConsistency::exec(int ts,int level)
{
  	double d1=source1_->consistencyValue(ts,level);
	double d2=source2_->consistencyValue(ts,level);
	double d3=source3_->consistencyValue(ts,level);
	
	float val=static_cast<float>((*proc_)(d1,d2,d3));
	
	target_->setConsistentValue(ts,level,val);
}	

//=========================================================
//
//  MvScmProfileChange
//
//=========================================================

bool MvScmProfileChange::checkData(const MvScmProfileData& data) const
{
	 return (var_ == data.var() && step_ == data.step());
}	

//=========================================================
//
//  MvScmProfileData
//
//=========================================================

MvScmProfileData::MvScmProfileData(MvScm *scm,MvScmVar *var, int step) :
 var_(var),
 auxLevel_(0),
 step_(step),
 observer_(0)
{
  	if(!var_)
	  	return;
    
	name_=var_->longName();	
	units_=var_->units();

	stringstream sst;
   	sst << scm->steps().at(step_)/3600;  	 
  	stepString_=sst.str();
	stepString_+=" h";
	
  	if(var_->levelType() == MvScmVar::ModelLevelType)
	{	
		levelName_="Model level";
		levelUnits_="eta";
		levelDirection_=TopDown;
		
	  	auxLevel_=scm->mlVar(MvScm::PresML);
		if(auxLevel_)
		{
		  	auxLevelDefined_=true;
			auxLevelName_="Pressure";
			auxLevelUnits_="hPa";
		}	
	} 
	else if(var_->levelType() == MvScmVar::SoilLevelType)
	{	
		levelName_="Soil";
		levelDirection_=TopDown;
	} 
	else if(var_->levelType() == MvScmVar::SurfaceLevelType)
	{	
		levelName_="Surface";
		levelDirection_=TopDown;
	} 
		
	if(var->isRangeSet())
		setRange(var->rangeMin(),var->rangeMax());  
}  


float MvScmProfileData::value(int index)
{
	return var_->data(step_).at(index);
}

float MvScmProfileData::level(int index)
{
	if(var_->levelType() == MvScmVar::ModelLevelType)
	{	
		return index+1;
	}
	else if(var_->levelType() == MvScmVar::SoilLevelType)
	{	
		return index+1;
	}
	
	
	return -1;
}	
		
float MvScmProfileData::auxLevel(int index)
{
  	if(auxLevelDefined_ && auxLevel_)
	{
		  return auxLevel_->data(step_).at(index);
	}
	return -1;
}	

void MvScmProfileData::levels(vector<float>& lev)
{
  	if(var_->levelType() == MvScmVar::ModelLevelType)
	{
	  	for(int i=0; i < count(); i++)
		{
		  	lev.push_back(i+1);
		}
	}
	else if(var_->levelType() == MvScmVar::SoilLevelType)
	{
	  	for(int i=0; i < count(); i++)
		{
		  	lev.push_back(i+1);
		}
	}
	else if(var_->levelType() == MvScmVar::SurfaceLevelType)
	{
	  		lev.push_back(1);
	}
	
}	

int MvScmProfileData::count() const
{
  	return static_cast<int>(var_->data(step_).size());
}

void MvScmProfileData::setValue(int index, float val)
{
	MvScmProfileChange ch;
	var_->setValue(step_,index,val,ch); 
	
	if(observer_)
	  	observer_->profileEdited(ch);
}

bool MvScmProfileData::acceptChange(const MvProfileChange& ch) const
{
	return ch.checkData(*this);
}

//=========================================================
//
//  MvScmDim
//
//=========================================================

void MvScmDim::init(NcFile& nc,string name)
{  
	name_=name;
  //	NcDim *dim;
	
	//We need this solution because nc.get_dim(name_.c_str());
	//crahes when the dimension does not exist
	
	int dnum=nc.num_dims();
	bool found=false;
	for(int i=0; i < dnum; i++)
	{
	  	NcDim *dim=nc.get_dim(i);
		if(dim && strcmp(dim->name(),name.c_str()) == 0)
		{
			 size_=dim->size();
			 found=true;
			 break;
		}  	
	}
	
	if(!found)
		return;
	
	int varNum=nc.num_vars();
	for(int i=0; i < varNum; i++)
	{
		NcVar *var=nc.get_var(i);
		if(var && strcmp(var->name(),name_.c_str()) == 0)
		{
		  	setVar(var);
			return;
		}	
	}
}	
	
void MvScmDim::setVar(NcVar *var)
{	
  	values_.clear();
  	
  	if(!var)
	  	return;
	
	if(var->num_dims() != 1)
	  	return;
  
	if(strcmp(var->get_dim(0)->name(),name_.c_str()) == 0)
	{  		
		int num=size_; 
  		long counts[1];	
		float* vals = new float[num];
	
		counts[0]=num;
  		var->get(vals,counts);  
		values_=vector<float>(vals,vals+num);	
		delete [] vals;
	}
	else
	{
	 	for(int i=0; i < size_; i++)
			values_.push_back(i+1);
	}	
	
	setAttributes(var);
	
}

void MvScmDim::setAttributes(NcVar *var)
{
	int n=var->num_atts();  
  	for(int i=0; i < n; i++)
	{
	  	NcAtt* att=var->get_att(i);
		if(att)
		{
		  	if(strcmp(att->name(),"long_name") == 0 && att->num_vals() > 0)
			{
				char *ch=att->as_string(0);
				if(ch)
			  		longName_=string(ch);
			}
			else if(strcmp(att->name(),"units") == 0 && att->num_vals() > 0)
			{
				char *ch=att->as_string(0);
				if(ch)
			  		units_=string(ch);
			}
		
			delete att;
		}
	}	
}

//=========================================================
//
//  MvScmVar
//
//=========================================================

MvScmVar::MvScmVar(LevelType levelType, NcVar *var,const MvScmDim& timeDim) :
  levelType_(levelType),
  changed_(false),
  presentInFile_(true),
  rangeSet_(false),
  consistency_(0),
  unitsConverter_(0)
{
	setName(var);
  	setAttributes(var);
  
  	int tNum=timeDim.size();
  	long counts[1];
	float* vals = new float[1];
	
	for(int i=0; i < tNum; i++)
	{
		counts[0]=1;		
  		var->set_cur(i);
  		var->get(vals,counts); 
		data_.push_back(vector<float>(vals,vals+1));
		
		dataOri_.push_back(vector<float>());
	}
	
	delete [] vals;
}

MvScmVar::MvScmVar(LevelType levelType, NcVar *var,const MvScmDim& timeDim, const MvScmDim &levDim) :
  levelType_(levelType), 
  changed_(false),
  presentInFile_(true),
  rangeSet_(false),
  consistency_(0),
  unitsConverter_(0)
{
	setName(var);
  	setAttributes(var);
  
  	int tNum=timeDim.size();
	int levNum=levDim.size();
  	long counts[2];
	float* vals = new float[levNum];
	
	for(int i=0; i < tNum; i++)
	{
		counts[0]=1;
		counts[1]=levNum;		
  		var->set_cur(i,0);
  		var->get(vals,counts);
		data_.push_back(vector<float>(vals,vals+levNum));
		dataOri_.push_back(vector<float>());
	}
	
	delete [] vals;
}

//Create a new variable that is not present in the file
MvScmVar::MvScmVar(const string& name, const string& longName, const string& units,MvScmVar *var) :
   name_(name),
   longName_(longName),
   units_(units),
   presentInFile_(false),
   rangeSet_(false),
   consistency_(0),
   unitsConverter_(0)
{
	levelType_=var->levelType();
	
	for(int i=0; i < var->stepNum(); i++)
	{
	  	var->data(i);
		data_.push_back(vector<float>(var->data(i).size(),0.));
		dataOri_.push_back(vector<float>());
	}
} 

MvScmVar::~MvScmVar()
{
	if(consistency_)
	  	delete consistency_;
}

const vector<float>&  MvScmVar::data(int ts) const
{
    if(ts >=0 && ts < static_cast<int>(data_.size())) 
        return data_.at(ts);
    
    static vector<float> emptyVec;    
    return emptyVec;     
}

const vector<float>&  MvScmVar::dataOri(int ts) const
{
    if(ts >=0 && ts < static_cast<int>(dataOri_.size())) 
        return dataOri_.at(ts);
     
    static vector<float> emptyVec;
    return emptyVec;   
}




string MvScmVar::units() const 
{
  	if(unitsConverter_)
	  	return unitsConverter_->units_;
	else	   
  		return units_;
}	

void MvScmVar::setName(NcVar *var)
{
	const char *ch=var->name();
	if(ch)
	  	name_=string(ch);
}	
  
void MvScmVar::setAttributes(NcVar *var)
{
	int n=var->num_atts();  
  	for(int i=0; i < n; i++)
	{
	  	NcAtt* att=var->get_att(i);
		if(att)
		{
		  	if(strcmp(att->name(),"long_name") == 0 && att->num_vals() > 0)
			{
				char *ch=att->as_string(0);
				if(ch)
			  		longName_=string(ch);
			}
			else if(strcmp(att->name(),"units") == 0 && att->num_vals() > 0)
			{
				char *ch=att->as_string(0);
				if(ch)
			  		units_=string(ch);
			}
		
			delete att;
		}
	}	
}

float MvScmVar::value(int ts,int lev)
{
	if(ts >=0 && ts < static_cast<int>(data_.size()))
	{ 
	  	if(lev >=0 && lev < static_cast<int>(data_.at(ts).size()))
		{
			return  data_.at(ts)[lev];
		}	
	}
	
	return -9999.;
}

float MvScmVar::consistencyValue(int ts,int lev)
{
	if(ts >=0 && ts < static_cast<int>(data_.size()))
	{ 
	  	if(lev >=0 && lev < static_cast<int>(data_.at(ts).size()))
		{
			if(!unitsConverter_)
			  	return  data_.at(ts)[lev];
			else
			  	return  unitsConverter_->convertFrom(data_.at(ts)[lev]);
		}	
	}
	
	return -9999.;
}	

bool MvScmVar::setConsistentValue(int ts,int lev,float val)
{
  	if(!unitsConverter_)
	  	return setValue(ts,lev,val,false);
	else
	  	return setValue(ts,lev,unitsConverter_->convertTo(val),false);
}

bool MvScmVar::setValue(int ts,int lev,float val,bool checkCons)
{
	if(ts >=0 && ts < static_cast<int>(data_.size()))
	{
	  	if(lev >=0 && lev < static_cast<int>(data_.at(ts).size()))
		{
			if(dataOri_.at(ts).size() == 0)
			{	  
		 		dataOri_.at(ts)=data_.at(ts); 
				changed_=true;
			}
				
			data_.at(ts)[lev]=val;
			
			if(checkCons)			  
				checkConsistency(ts,lev);
			
			return true;
		}
	}
	
	return false;
}
 
bool MvScmVar::setValue(int ts,int lev,float val,MvScmProfileChange& ch,bool checkCons)
{
	if(ts >=0 && ts < static_cast<int>(data_.size()))
	{
	  	if(lev >=0 && lev < static_cast<int>(data_.at(ts).size()))
		{
			if(dataOri_.at(ts).size() == 0)
			{	  
		 		dataOri_.at(ts)=data_.at(ts); 
				changed_=true;
			}
		
			float prevVal=data_.at(ts)[lev];		
			data_.at(ts)[lev]=val;
			
			ch=MvScmProfileChange(this,ts,lev,val,prevVal);
			
			if(MvScm::needConsistency() && checkCons)			  
			{			  	
			  	checkConsistency(ts,lev);
				if(consistency_)
				  	ch.setDependantVar(consistency_->target());
			}
			
			return true;
		}
	}
	
	return false;  
 
}

bool MvScmVar::setValues(int ts,float val,vector<MvScmProfileChange>& ch,bool checkCons)
{
	if(ts >=0 && ts < static_cast<int>(data_.size()))
	{	  	
	  	int levnum=static_cast<int>(data_.at(ts).size());
	  	for(int lev=0; lev < levnum; lev++)
		{
			if(dataOri_.at(ts).size() == 0)
			{	  
		 		dataOri_.at(ts)=data_.at(ts); 
				changed_=true;
			}
		
			float prevVal=data_.at(ts)[lev];		
			data_.at(ts)[lev]=val;			
			
			MvScmProfileChange chItem(this,ts,lev,val,prevVal);			
			
			if(lev==0)
			  	chItem.setGroup(MvProfileChange::GroupStart);
			else if(lev==levnum-1)
			  	chItem.setGroup(MvProfileChange::GroupEnd);
			else
			  	chItem.setGroup(MvProfileChange::GroupMember);
			
			if(MvScm::needConsistency() && checkCons)			  
			{			  	
			  	checkConsistency(ts,lev);
				if(consistency_)
				  	chItem.setDependantVar(consistency_->target());
			}
			
			ch.push_back(chItem);			
			
		}		
		
		return true;
		
	}
	
	return false;  
 
}
 
bool MvScmVar::changed(int ts)
{
	if(!changed_)
	  	return false;
  
	if(ts >=0 && ts < static_cast<int>(data_.size()))
	{
		return static_cast<int>(data_.at(ts).size()) > 0;
	}
	
	return false;
}	
		
	
bool MvScmVar::changed(int ts,int lev)
{
	if(!changed_)
	  	return false;
  
  	if(ts >=0 && ts < static_cast<int>(data_.size()))
	{
	  	if(lev >=0 && lev <= static_cast<int>(data_.at(ts).size()))
		{  
  			if(dataOri_.at(ts).size() == 0)
			{
			  	return false;
			}
			
			return (data_.at(ts)[lev] != dataOri_.at(ts)[lev]);
		}
	}
	
	return false;
}	
		
		
bool MvScmVar::saveChanges(NcFile &nc)
{
  	if(!changed_)
	  	return true;
  	
	if(presentInFile_)
	{		
		NcVar *outVar=0;
	
		for(int i=0; i < nc.num_vars(); i++)
		{
			NcVar *v=nc.get_var(i);
			if(v && strcmp(nc.get_var(i)->name(),name_.c_str()) ==0)
			{
				outVar=v;
				break;
			}	
		}
		if(!outVar)
		{
			return false;
		}	
		
		for(unsigned int ts=0; ts < data_.size(); ts++)
		{
	  		if(dataOri_.at(ts).size() != 0)
			{		  	
		 		if(levelType_==ModelLevelType || levelType_==SoilLevelType ||
				   levelType_==PressureLevelType)
				{  
	  				long counts[2];		
					counts[0]=1;
					counts[1]=data_.at(ts).size();
					
					if(unitsConverter_)
					{
					 	float *vals=new float[data_.at(ts).size()];
					  	for(unsigned int i=0; i< data_.at(ts).size();i++)
						{
						  	vals[i]=unitsConverter_->convertFrom(data_.at(ts).at(i));
						}
						outVar->set_cur(ts,0);				
						outVar->put(vals,counts);
					
						delete [] vals;
					}
					else
					{
					  	float *vals = &(data_.at(ts)[0]);								
						outVar->set_cur(ts,0);				
						outVar->put(vals,counts);
					}	
				}
				
				else if(levelType_==SurfaceLevelType)
				{  
					long counts[1];		
					counts[0]=1;
					
					if(unitsConverter_)
					{					 	
					  	float *vals=new float[1];					  	
						vals[0]=unitsConverter_->convertFrom(data_.at(ts).at(0));
						outVar->set_cur(ts);				
						outVar->put(vals,counts);
					
						delete [] vals;
					}
					else
					{
					  	float *vals = &(data_.at(ts)[0]);								
						outVar->set_cur(ts);				
						outVar->put(vals,counts);
					}	
				}
							
			}	
		}
	}
	
	for(unsigned int ts=0; ts < data_.size(); ts++)
	{
		dataOri_.at(ts).clear();
	}

	changed_=false;
	
	return true;
}			  
		  
void MvScmVar::reset()
{
 	for(unsigned int ts=0; ts < data_.size(); ts++)
	{
		if(dataOri_.at(ts).size() > 0)
		{
		  	for(unsigned int lev=0; lev < data_.at(ts).size(); lev++)
			{
			  	data_.at(ts).at(lev)=dataOri_.at(ts).at(lev);
			}	
		dataOri_.at(ts).clear();
		}
	}
	
	changed_=false;
} 

bool MvScmVar::fitToRange(float& val)
{
	if(rangeSet_)
	{
		if(val < rangeMin_)
		{
		  	val=rangeMin_;
			return false;
		}	
		else if(val > rangeMax_)
		{
		  	val=rangeMax_;
			return false;
		}
	}
	
	return true;
}

void MvScmVar::compute(MvScmVar *v1,CompProc1 proc)
{
  	for(unsigned int ts=0; ts < data_.size(); ts++)
	{
	  	const vector<float>& d1=v1->data(ts);
		
	  	for(unsigned int lev=0; lev < data_.at(ts).size(); lev++)
		{		  	
		  	float val=(*proc)(d1.at(lev));
		  	fitToRange(val);
			data_.at(ts)[lev]=val;
		}
	}		  
}

void MvScmVar::compute(MvScmVar *v1,MvScmVar *v2,CompProc2 proc)
{
  	for(unsigned int ts=0; ts < data_.size(); ts++)
	{
	  	const vector<float>& d1=v1->data(ts);
		const vector<float>& d2=v2->data(ts);
	  
	  	for(unsigned int lev=0; lev < data_.at(ts).size(); lev++)
		{		  	
		  	float val=(*proc)(d1.at(lev),d2.at(lev));
		  	fitToRange(val);
			data_.at(ts)[lev]=val;
		}
	}		  
}

void MvScmVar::compute(MvScmVar *v1,MvScmVar *v2,MvScmVar *v3,CompProc3 proc)
{
  	for(unsigned int ts=0; ts < data_.size(); ts++)
	{
	  	const vector<float>& d1=v1->data(ts);
		const vector<float>& d2=v2->data(ts);
	  	const vector<float>& d3=v3->data(ts);
	  
	  	for(unsigned int lev=0; lev < data_.at(ts).size(); lev++)
		{		  	
		  	float val=(*proc)(d1.at(lev),d2.at(lev),d3.at(lev));
		  	fitToRange(val);
			data_.at(ts)[lev]=val;
		}
	}		  
}

void MvScmVar::checkConsistency(int ts,int level)
{	
  	if(!consistency_ || ! MvScm::needConsistency())
	  	return;
	
	consistency_->exec(ts,level);
}	


void MvScmVar::setConsistency(MvScmConsistency *c)
{
	if(consistency_)
	{
	  	delete consistency_;
		consistency_=0;
	}
	consistency_=c;
}

void MvScmVar::setUnits(float offset,float scale,string units)
{
	if(unitsConverter_)
	  	return;
	
	unitsConverter_=new MvScmUnitsConverter;
	unitsConverter_->offset_=offset;
	unitsConverter_->scale_=scale;
	unitsConverter_->units_=units;
		
	for(unsigned int ts=0; ts < data_.size(); ts++)
	{
	  	for(unsigned int lev=0; lev < data_.at(ts).size(); lev++)
		{		  	
			data_.at(ts)[lev]=unitsConverter_->convertTo(data_.at(ts)[lev]);
		}
	}		  
}	
  
bool MvScmVar::overwrite(int fromTs)
{
	if(fromTs >=0 && fromTs < static_cast<int>(data_.size()))
	{	  	
	  	for(int ts=0; ts < static_cast<int>(data_.size()); ts++)
		{
		  	if(ts != fromTs)
			{
				if(dataOri_.at(ts).size() == 0)
				{	  
		 			dataOri_.at(ts)=data_.at(ts); 
					changed_=true;
				}
			  			  
			  	for(int lev=0; lev < static_cast<int>(data_.at(ts).size()); lev++)
				{
					
					//float prevVal=data_.at(ts)[lev];		
					data_.at(ts)[lev]=data_.at(fromTs)[lev];			
					//ch=MvScmProfileChange(this,ts,lev,val,prevVal);			
				}
			}
		}
		
		return true;
	}
	
	return false;  
 
}

bool MvScmVar::write(ofstream &out,int ts,bool useConverted,bool newline)
{
	if(ts >=0 && ts < static_cast<int>(data_.size()))
	{
	  	for(unsigned int lev=0; lev < data_.at(ts).size(); lev++)
		{		  	
		  	if(!unitsConverter_ || useConverted)
				out << data_.at(ts)[lev];
			else				  
			  	out << unitsConverter_->convertFrom(data_.at(ts)[lev]);
			
			if(newline)
			  	out << endl;
		}
		return true;
	}
	
	return false;
}  
  
  
//=========================================================
//
//  MvScm
//
//=========================================================

MvScm::MvScm(const string& fileName,bool convertUnits) : 
	fileName_(fileName),
	convertUnits_(convertUnits)
{ 
  	//Figure out the id
  	decodeId(); 
	
	//Map variables
	if(id_.find("SCM") != string::npos || id_.find("scm") != string::npos )
	{
		mapRttovVars();
	}
	if(id_ == "NETCDF_RTTOV_INPUT")
	{
		mapRttovVars();
	}
	
	//Decode data	
	decode();
}  

MvScm::~MvScm()
{
	for(int i=0; i < static_cast<int>(ml_.size()); i++)
  		delete ml_.at(i);
	
	for(int i=0; i < static_cast<int>(pl_.size()); i++)
  		delete pl_.at(i);
	
	for(int i=0; i < static_cast<int>(surf_.size()); i++)
  		delete surf_.at(i);
	
	for(int i=0; i < static_cast<int>(soil_.size()); i++)
  		delete soil_.at(i);	
}

void MvScm::decodeId()
{
	NcFile nc(fileName_.c_str());
	
	//Get id
	int n=nc.num_atts();
	for(int i=0; i < n; i++)
	{  	
		NcAtt* att=nc.get_att(i);
		if(att != NULL && att->name() != NULL && 
		   strcmp(att->name(),"dataID") == 0 && att->num_vals() > 0)
		{  
			char* v=att->as_string(0);
			if(v) id_=string(v);
			break;
		}
	}	
	
}

void MvScm::mapRttovVars()
{
  	mlVarName_["pressure"]=PresML;
	mlVarName_["height_f"]=HeightML;
	mlVarName_["t"]=TempML;
	mlVarName_["u"]=WindUML;
	mlVarName_["v"]=WindVML;
	mlVarName_["q"]=SpHumML;
	mlVarName_["relative_humidity"]=RelHumML;
	mlVarName_["ql"]=CloudLiqML;
	mlVarName_["qi"]=CloudIceML;
	mlVarName_["o3"]=OzoneML;
	
	soilVarName_["t_soil"]=TempSoil;
	soilVarName_["q_soil"]=HumSoil;
	
	surfVarName_["t_skin"]=TempSurf;
	surfVarName_["t_sea_ice"]=TempSeaIceSurf;
	surfVarName_["open_sst"]=TempSeaSurf;
	surfVarName_["sea_ice_frct"]=SeaIceFractionSurf;
	
	surfVarName_["t_2"]=Temp2Surf;
	surfVarName_["td_2"]=Td2Surf;
	surfVarName_["q_2"]=SpecHum2Surf;
	surfVarName_["u_10"]=WindU10Surf;
	surfVarName_["v_10"]=WindV10Surf;
	surfVarName_["orog"]=OrogSurf;
	surfVarName_["z_sfc"]=ZSurf;
	surfVarName_["lsm"]=LsmSurf;
	surfVarName_["surftype"]=LsiSurf;
	surfVarName_["water_type"]=WaterTypeSurf;
	surfVarName_["p_cloudtop_scs"]=CloudTopPressSurf;
	surfVarName_["cloud_fraction_scs"]=CloudFractionSurf;
	surfVarName_["lat"]=LatSurf;
	surfVarName_["lon"]=LonSurf; 
	
	dimName_["time"]="time";
	dimName_["ml"]="nlev";
	dimName_["pl"]="nlevp1";
	dimName_["soil"]="nlevs";
}  

void MvScm::mapScmVars()
{ 
  	mlVarName_["pressure_f"]=PresML;
	mlVarName_["height_f"]=HeightML;
	mlVarName_["t"]=TempML;
	mlVarName_["u"]=WindUML;
	mlVarName_["v"]=WindVML;
	mlVarName_["q"]=SpHumML;
	mlVarName_["relative_humidity"]=RelHumML;
	mlVarName_["ql"]=CloudLiqML;
	mlVarName_["qi"]=CloudIceML;
	
	soilVarName_["t_soil"]=TempSoil;
	soilVarName_["q_soil"]=HumSoil;
	
	surfVarName_["t_skin"]=TempSurf;
	surfVarName_["t_sea_ice"]=TempSeaIceSurf;
	surfVarName_["open_sst"]=TempSeaSurf;
	
	dimName_["time"]="time";
	dimName_["ml"]="nlev";
	dimName_["pl"]="nlevp1";
	dimName_["soil"]="nlevs";
	
}  
	
	
void MvScm::decode()
{
	NcFile nc(fileName_.c_str());
	
	//Init dimensions
	timeDim_.init(nc,dimName_["time"]);
	modelLevDim_.init(nc,dimName_["ml"]);
	pressureLevDim_.init(nc,dimName_["pl"]);
	soilLevDim_.init(nc,dimName_["soil"]);
		
	//Get variables
	int varNum=nc.num_vars();
	for(int i=0; i < varNum; i++)
	{
		NcVar *var=nc.get_var(i);
		decodeVar(var);
	}
	
	initRelHum();	
	
	//Get id
	/*int n=nc.num_atts();
	for(int i=0; i < n; i++)
	{  	
		NcAtt* att=nc.get_att(i);
		if(att != NULL && att->name() != NULL && 
		   strcmp(att->name(),"dataID") == 0 && att->num_vals() > 0)
		{  
			char* v=att->as_string(0);
			if(v) id_=string(v);
			break;
		}
	}*/	
}	

void MvScm::decodeVar(NcVar* var)
{
  	if(!var)
	  	return;
		
	//Dimension vars
	if(strcmp(var->name(),modelLevDim_.name().c_str()) == 0 || 
	   strcmp(var->name(),pressureLevDim_.name().c_str()) == 0 ||
	   strcmp(var->name(),soilLevDim_.name().c_str()) == 0 ||
	   strcmp(var->name(),timeDim_.name().c_str()) == 0)
	{
	  	return;
	}	
		
	std::set<string> dims;
	for(int i=0; i < var->num_dims(); i++)
	{  	
		NcDim* dim=var->get_dim(i);
		const char* ch=dim->name();
		if(ch)
		  	dims.insert(string(ch));
	}
	
	
	if(dims.find(timeDim_.name()) == dims.end())
	{
	  	return;
	}	
		
	if(dims.size() == 1)
	{	 
		surf_.push_back(new MvScmVar(MvScmVar::SurfaceLevelType,var,timeDim_)); 
		map<string,SurfVariable>::iterator it=surfVarName_.find(surf_.back()->name());
		if(it != surfVarName_.end())
		{
			surfVarIndex_[it->second]=surf_.size()-1;
		}				
	}
	else if(dims.size() == 2  )
	{
		if(dims.find(modelLevDim_.name()) != dims.end() )	
		{
		  	ml_.push_back(new MvScmVar(MvScmVar::ModelLevelType,var,timeDim_,modelLevDim_));
			
			map<string,ModelLevelVariable>::iterator it=mlVarName_.find(ml_.back()->name());
			if(it != mlVarName_.end())
			{
				mlVarIndex_[it->second]=ml_.size()-1;
			
				//Insert Relhum after T
				if(it->second== RelHumML)
				{ 
					MvScmVar* r=ml_.back();
					r->setRange(0.,1.);
					
					map<ModelLevelVariable,int>::iterator tempIt=mlVarIndex_.find(TempML);
					if(tempIt != mlVarIndex_.end())
					{
					  	int pos=tempIt->second+1;
						ml_.insert(ml_.begin()+pos,r);
						ml_.pop_back();
						mlVarIndex_[RelHumML]=pos;
						for(tempIt=mlVarIndex_.begin(); tempIt != mlVarIndex_.end(); tempIt++)
						{
						  	if(tempIt->first != RelHumML && tempIt->second >= pos)
							  	mlVarIndex_[tempIt->first]=1+tempIt->second;
						}
					}
				}	
			
			}			
		}
		else if(dims.find(pressureLevDim_.name()) != dims.end() )	
		{
			pl_.push_back(new MvScmVar(MvScmVar::PressureLevelType,var,timeDim_,pressureLevDim_)); 
		}
		else if(dims.find(soilLevDim_.name()) != dims.end() )	
		{
			soil_.push_back(new MvScmVar(MvScmVar::SoilLevelType,var,timeDim_,soilLevDim_)); 
			map<string,SoilVariable>::iterator it=soilVarName_.find(soil_.back()->name());
			if(it != soilVarName_.end())
			{
				soilVarIndex_[it->second]=soil_.size()-1;
			}					  	
		}
	}	
}

void MvScm::save()
{
	//Open progvar
	NcFile nc(fileName_.c_str(),NcFile::Write);
		
	for(unsigned int i=0; i < ml_.size(); i++)
	{	  
		ml_.at(i)->saveChanges(nc);
	} 
	for(unsigned int i=0; i < surf_.size(); i++)
	{	  
		surf_.at(i)->saveChanges(nc);
	} 
	for(unsigned int i=0; i < soil_.size(); i++)
	{	  
		soil_.at(i)->saveChanges(nc);
	} 
	for(unsigned int i=0; i < pl_.size(); i++)
	{	  
		pl_.at(i)->saveChanges(nc);
	} 
	
}  

void MvScm::reset()
{
 	for(unsigned int i=0; i < ml_.size(); i++)
	{
	 	ml_.at(i)->reset(); 	  
	}  
	for(unsigned int i=0; i < surf_.size(); i++)
	{	  
		surf_.at(i)->reset();
	} 
	for(unsigned int i=0; i < soil_.size(); i++)
	{	  
		soil_.at(i)->reset();
	} 
	for(unsigned int i=0; i < pl_.size(); i++)
	{	  
		pl_.at(i)->reset();
	} 	
}  

bool MvScm::overwrite(int fromTs)
{
  	for(unsigned int i=0; i < ml_.size(); i++)
	{
	 	ml_.at(i)->overwrite(fromTs); 	  
	}  
	for(unsigned int i=0; i < surf_.size(); i++)
	{	  
		surf_.at(i)->overwrite(fromTs);
	} 
	for(unsigned int i=0; i < soil_.size(); i++)
	{	  
		soil_.at(i)->overwrite(fromTs);
	}
	for(unsigned int i=0; i < pl_.size(); i++)
	{	  
		pl_.at(i)->overwrite(fromTs);
	} 
	
	return true;
}

int MvScm::mlVarIndex(ModelLevelVariable var)
{
  	map<ModelLevelVariable,int>::iterator it=mlVarIndex_.find(var);
	if(it != mlVarIndex_.end())
	{
		return it->second;
	}
	return -1;
}  

MvScmVar* MvScm::mlVar(ModelLevelVariable var)
{
  	int index=mlVarIndex(var);
	
	if(index != -1)
	{  	
		return ml_.at(index);
	}	
	
	
	return 0;
}  

int MvScm::soilVarIndex(SoilVariable var)
{
  	map<SoilVariable,int>::iterator it=soilVarIndex_.find(var);
	if(it != soilVarIndex_.end())
	{
		return it->second;
	}
	return -1;
}  

MvScmVar* MvScm::soilVar(SoilVariable var)
{
  	int index=soilVarIndex(var);
	
	if(index != -1)
	{  	
		return soil_.at(index);
	}		
	return 0;
}  

int MvScm::surfVarIndex(SurfVariable var)
{
  	map<SurfVariable,int>::iterator it=surfVarIndex_.find(var);
	if(it != surfVarIndex_.end())
	{
		return it->second;
	}
	return -1;
}  

MvScmVar* MvScm::surfVar(SurfVariable var)
{
  	int index=surfVarIndex(var);
	
	if(index != -1)
	{  	
		return surf_.at(index);
	}		
	return 0;
}  
void MvScm::initRelHum()
{	
	MvScmVar* t=mlVar(TempML);
	MvScmVar* p=mlVar(PresML);
	MvScmVar* q=mlVar(SpHumML);
	MvScmVar* r=mlVar(RelHumML);	
	MvScmVar* tSurf=surfVar(TempSurf);
	//MvScmVar* tSeaIce=surfVar(TempSeaIceSurf);
	MvScmVar* tSea=surfVar(TempSeaSurf);
	MvScmVar* tSoil=soilVar(TempSoil);
	
	if(convertUnits_)
	{
		if(t && t->units() == "K")
	 	 	t->setUnits(-273.16,1.,"C");
		if(tSurf && tSurf->units() == "K")
	  		tSurf->setUnits(-273.16,1.,"C");
		if(tSoil && tSoil->units() == "K")
	  		tSoil->setUnits(-273.16,1.,"C");	
		if(tSea && tSea->units() == "K")
	  		tSea->setUnits(-273.16,1.,"C");
		//if(tSeaIce && tSeaIce->units() == "K")
		//  	tSeaIce->setOffset(-273.16,"C");
	}
	
	if(!t || !p || !q || !r)
	  	return;
	
	//r->compute(t,p,q,&metview::relativeHumidity);
	
	//Consistency
	t->setConsistency(new MvScmConsistency(r,&metview::relativeHumidity,t,p,q));
	r->setConsistency(new MvScmConsistency(q,&metview::specificHumidity,t,p,r));
}	


void MvScm::needConsistency(bool b)
{
  	needConsistency_=b; 
} 

//==========================================================
// Static methods
//==========================================================

int MvScm::modelLevelNum(const string& fileName)
{
  	//Open file
	NcFile nc(fileName.c_str());
		
	int num=-1;
  	NcDim *dim=nc.get_dim("nlev");
	if(dim)
	{
	  	num=dim->size();
	}
	
	nc.close();
	
	return num;
}	
	

//We add diag and diag2 contents to progvar
bool MvScm::mergeOutFiles(const string& progvarFileName,const string& diagvarFileName,const string& diagvar2FileName)
{
	//Open progvar
	NcFile nc(progvarFileName.c_str(),NcFile::Write);
		
	//Get dims from progvar
	vector<NcDim*> dims;
	for(int i=0; i < nc.num_dims(); i++)
	{  	
		NcDim* dim=nc.get_dim(i);
		if(dim)
		{
		  	dims.push_back(dim); 
			cout << dim->name() << " " << dim->size() << endl;
		}	
		else
		{
		  	cout << "bad dim" << endl;
		}	
	}
	
	//---------------------
	// Diag
	//---------------------
	
	NcFile ncDiag(diagvarFileName.c_str(),NcFile::ReadOnly);
		
	//Dimensions
	if(!addDimensionsToFile(ncDiag,nc,dims))
	{
	  	return false;
	}	
		
	//Variables
	int varNum=ncDiag.num_vars();
	for(int i=0; i < varNum; i++)
	{
		NcVar *var=ncDiag.get_var(i);
		addVarToFile(var,nc,dims);
	}
	ncDiag.close();
		
	//---------------------
	// Diag2
	//---------------------

	NcFile ncDiag2(diagvar2FileName.c_str(),NcFile::ReadOnly);	

	//Dimensions
	if(!addDimensionsToFile(ncDiag2,nc,dims))
	{
	  	return false;
	}	


	//Variables
	varNum=ncDiag2.num_vars();
	for(int i=0; i < varNum; i++)
	{
		NcVar *var=ncDiag2.get_var(i);
		addVarToFile(var,nc,dims);
	}
	ncDiag2.close();
	
	
	//Add global attributes
	nc.add_att("dataID","SCM_OUTPUT");
	
	nc.close();
	
	return true;
}  


bool MvScm::addDimensionsToFile(NcFile& inNc,NcFile& outNc,std::vector<NcDim*>& dims)
{
	for(int i=0; i < inNc.num_dims();i++)
	{
		NcDim* dim=inNc.get_dim(i);
		bool found=false;
		for(unsigned int j=0; j < dims.size();j++)
		{		  	
	  		cout << dims[j]->name() << " " << dims[j]->size() << endl;
			
			if(strcmp(dim->name(),dims.at(j)->name()) == 0)
			{
		   		if(dim->size() !=  dims.at(j)->size())
		        	{
		  	   		return false;
				}
				else
				{
				  	found=true;
				}
			}	
		}	
		if(!found)
		{
			dims.push_back(outNc.add_dim(dim->name(),dim->size()));
		}
	}
	
	return true;
}	

void MvScm::addVarToFile(NcVar *inVar,NcFile& outNc,const std::vector<NcDim*>& outDims)
{
	cout << inVar->name() << endl;
  
  	//Check var name
	for(int i=0; i < outNc.num_vars(); i++)
	{  
		NcVar *v=outNc.get_var(i);
		if(v && strcmp(v->name(),inVar->name()) ==0)
		{
		  	cout << v->name() << endl;
			return;
		}
	}
	
	//Check dims
	vector<NcDim*> dims;
	for(int i=0; i < inVar->num_dims(); i++)
	{  	
		NcDim* dim=inVar->get_dim(i);
		if(dim)
		{
			cout << dim->name() << " " << dim->size() << endl;
			for(unsigned int j=0; j < outDims.size(); j++)
			{
			  	if(strcmp(dim->name(),outDims.at(j)->name()) == 0 &&
				   dim->size() ==  outDims.at(j)->size())
				{
					dims.push_back(outDims.at(j));
					break;
				}
			}
			
		}
		else if(!dim || static_cast<int>(dims.size()) != i+1)
		{
		  	return;
		}	
	}	
	
	//Create var
	NcVar *outVar=0;
	if(inVar->num_dims() == 0)
	{
	  	outVar=outNc.add_var(inVar->name(),inVar->type());
	}
	else if(inVar->num_dims() == 1)
	{
	  	outVar=outNc.add_var(inVar->name(),inVar->type(),dims[0]);
	}
	else if(inVar->num_dims() == 2)
	{
	  	outVar=outNc.add_var(inVar->name(),inVar->type(),dims[0],dims[1]);
	}
	else
	{
	  	return;
	}
	
	//Add attributes
	for(int i=0; i < inVar->num_atts(); i++)
	{  
		NcAtt *att=inVar->get_att(i);
		if(att)
		{
		  	if(att->type() == ncChar)
			{
				char* v=new char[att->num_vals()];
				for(int j=0; j < att->num_vals(); j++) 			  	
				{
					v[j]=att->as_char(j);
				}
				outVar->add_att(att->name(),att->num_vals(),v);
				delete [] v;
				
			}
			delete att;
		}	
	}
	
	//Add values_
	if(inVar->num_dims() == 0)
	{
		float vals;				
  		inVar->get(&vals,0,0,0,0,0); 		
		outVar->put(&vals,0,0,0,0,0);
	}
	if(inVar->num_dims() == 1)
	{
  		long counts[1];		
		counts[0]=dims[0]->size();
		float* vals = new float[counts[0]];				
  		inVar->get(vals,counts); 		
		outVar->put(vals,counts);	 	
	}
	if(inVar->num_dims() == 2)
	{
  		long counts[2];		
		counts[0]=dims[0]->size();
		counts[1]=dims[1]->size();
		float* vals = new float[counts[0]*counts[1]];				
  		inVar->get(vals,counts); 		
		outVar->put(vals,counts);	 	
	}
}	

//Create an ASCII input file for RTTOV
bool MvScm::createRttovInput(const string& inFileName,const string& outFileName,
			    float sat_zenith_angle,float sat_azimuth_angle,
			    float solar_zenith_angle,float solar_azimuth_angle,
			    bool useOzone,string &errTxt)
{
	//Open input NetCDF file, and use original units
	MvScm in(inFileName.c_str(),false);
		
	//Check the required profiles
	string nfTxt=" not found!";
	
	MvScmVar* t=in.mlVar(TempML);
	if(!t)
	{
	  	errTxt="Temperature" + nfTxt;
		return false;
	}	
		  
	MvScmVar* p=in.mlVar(PresML);
	if(!p)
	{
	  	errTxt="Pressure" + nfTxt;
		return false;
	}	
	  
	
	MvScmVar* q=in.mlVar(SpHumML);
	if(!q)
	{
	  	errTxt="Specific humidity" + nfTxt;
		return false;
	}	
	
	//Compute water vapour ppmv
	MvScmVar *ppmv=new MvScmVar("ppmv", "ppmv","ppmv",q);
	ppmv->compute(p,q,&metview::vapourPPMV);	
	
	//Ozone 
	MvScmVar *ozppmv=0;
	if(useOzone)
	{
		MvScmVar* oz=in.mlVar(OzoneML);
		if(!oz)
		{
	  		errTxt="Ozone" + nfTxt;
			return false;
		}	  	
	
		//Compute ozone ppmv
		ozppmv=new MvScmVar("ozppmv", "ozppmv","ppmv",oz);
		ozppmv->compute(oz,&metview::ozonePPMV);	
	}
	
	//Surface pressure in Pa
	double psurf=p->value(0,p->levelNum()-1);
	
	//Skin tempearture in K
	MvScmVar* v;
	double tskin=0.;	
	v=in.surfVar(TempSurf);
	if(!v)
	{
	  	errTxt="Skin temperature" + nfTxt;
		return false;
	}
	else
	  	tskin=v->value(0,0);

	//2m tempearture in K
	double t2=0.;	
	v=in.surfVar(Temp2Surf);
	if(!v)
	{
	  	errTxt="2m temperature" + nfTxt;
		return false;
	}
	else
		t2=v->value(0,0);
	
	
	//2m q in ppmv
	double q2ppmv=0.;
	v=in.surfVar(SpecHum2Surf);
	if(!v)
	{
	  	errTxt="2m specific humidity" + nfTxt;
		return false;
	}
	else
		q2ppmv=metview::vapourPPMV(psurf,v->value(0,0));
	
		
	//10m u in m/s
	double u10=0.;
	v=in.surfVar(WindU10Surf);
	if(!v)
	{
	  	errTxt="Wind U 10 m" + nfTxt;
		return false;
	}
	else
	  	u10=v->value(0,0);
	
	//10m v m/s
	double v10=0.;
	v=in.surfVar(WindV10Surf);
	if(!v)
	{
	  	errTxt="Wind V 10 m" + nfTxt;
		return false;
	}
	else
	  	v10=v->value(0,0);
		
	//Lat
	double lat=0.;
	v=in.surfVar(LatSurf);
	if(!v)
	{
	  	errTxt="Lat" + nfTxt;
		return false;
	}	
	else
	  	lat=v->value(0,0);
	
	//Lon
	double lon=0.;
	v=in.surfVar(LonSurf);
	if(!v)
	{
	  	errTxt="Lon" + nfTxt;
		return false;
	}	
	else
	  	lon=v->value(0,0);
	
	//orog in km
	double orog=0.;	
	v=in.surfVar(OrogSurf);
	if(!v)
	{
	  	errTxt="Orography" + nfTxt;
		return false;
	}	
	else
	  	orog=0.001*v->value(0,0);
	
	//Surftype	
	double surfType=0.;
	v=in.surfVar(LsiSurf);
	if(!v)
	{
	  	errTxt="Surface type" + nfTxt;
		return false;
	}
	else
		surfType=v->value(0,0);
	
	/*double surfType=0.;
	v=in.surfVar(LsmSurf);
	if(!v)
	{
	  	errTxt="LSM" + nfTxt;
		return false;
	}
	else
	{
	  	MvScmVar* v1=in.surfVar(SeaIceFractionSurf);
		if(!v1)
		{
		  	errTxt="Sea ice fraction" + nfTxt;
			return false;
		}
		//Sea
		if(v->value(0,0) > 0.5)
		{
		  	if(v1->value(0,0) > 0.5)
			  	surfType=2;
			else
			  	surfType=1;
		}
		else
		  	surfType=0;
		
	} */ 
	  
	//Watertype
	double waterType=0.;
	v=in.surfVar(WaterTypeSurf);
	if(!v)
	{
	  	errTxt="Water type" + nfTxt;
		return false;
	}	
	else
	  	waterType=v->value(0,0);
		
	//cltoppres
	double cltop=0.;
	v=in.surfVar(CloudTopPressSurf);
	if(!v)
	{
	  	errTxt="Cloud top pressure" + nfTxt;
		return false;
	}	
	else
	  	cltop=v->value(0,0);
	
	//clfract
	double clfract=0.;
	v=in.surfVar(CloudFractionSurf);
	if(!v)
	{
	  	errTxt="Cloud fraction" + nfTxt;
		return false;
	}	
	else
	  	clfract=v->value(0,0);	
	
	//Open ASCII output
	ofstream out(outFileName.c_str());	
	if(!out.good())
	  	return false;
	
	//Generate ASCII output
	out << "! --- Profile 1 ---" << endl;
	
out << "! \n\
! Pressure levels (hPa) \n\
!\n";
	if(p->units() == "Pa")	
	  	p->setUnits(0.,0.01,"hPa");
	p->write(out,0,true,true);
	
out << "! \n\
! Temperature profile (K) \n\
!\n";	
		
	t->write(out,0,false,true);

out << "! \n\
! Water vapour profile (ppmv) \n\
!\n"; 
	ppmv->write(out,0,false,true);
	
if(useOzone)
{

out << "! \n\
! Ozone profile (ppmv) \n\
!\n"; 
	ozppmv->write(out,0,false,true);	
}

out << "!\n\
!\n\
! Near-surface variables:\n\
!  2m T (K)    2m q (ppmv) 2m p (hPa) 10m wind u (m/s)  10m wind v (m/s)  wind fetch (m)\n\
!\n";
		
	
out << t2 << " " << q2ppmv << " " << psurf/100. << " " <<  u10 << " " << v10 <<  " 100000." << endl;
	
	//tSurf->write(out,0,false);
	//out << "  15248.0550  1007.30      5.000             2.0000            100000." << endl;
	
out << "!\n\
! Skin variables:\n\
!  Skin T (K)  FASTEM parameters for land surfaces\n\
!\n";
	out << tskin << "   3.0 5.0 15.0 0.1 0.3" << endl;

	
out << "!\n\
! Surface type (0=land, 1=sea, 2=sea-ice) and water type (0=fresh, 1=ocean)\n\
!\n";
     	out << surfType << " " << waterType << endl;

out << "!\n\
! Elevation (km), latitude and longitude (degrees)\n\
!\n";

out << orog << " "  << " " << lat << " " << lon << endl; 

out << "!\n\
! Sat. zenith and azimuth angles, solar zenith and azimuth angles (degrees)\n\
!\n";
out << sat_zenith_angle << "  " << sat_azimuth_angle << "  " <<
    solar_zenith_angle << "  " << solar_azimuth_angle << endl;

	
out << "!\n\
! Cloud top pressure (hPa) and cloud fraction for simple cloud scheme\n\
!\n";


   out << cltop << " " << clfract << endl;

out << "!\n\
! --- End of profile 1 ---\n\
!";
	
	
	out.close();
	
	delete ppmv;
	
	return true;
}	
	
