/***************************** 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 *************************************/

#ifndef MvScm_inc
#define MvScm_inc

#include <map>
#include <string>
#include <vector>

#include "netcdfcpp.h"

#include "MvProfileData.h"
#include "MvMiscelaneous.h"

using namespace std;

class MvScm;
class MvScmVar;
class MvScmProfileData;

class MvScmUnitsConverter
{
public:
	MvScmUnitsConverter() : offset_(0.), scale_(1.) {}
	
	float convertTo(float v) {return v*scale_+offset_;}
	float convertFrom(float v) {return (v-offset_)/scale_;}
	float offset_;
	float scale_;
	string units_;
};	


class MvScmConsistency
{
public:
	typedef double (*CompProc3)(double,double,double);
	
	MvScmConsistency(MvScmVar* target,CompProc3 proc,MvScmVar* var1,MvScmVar* var2,MvScmVar* var3) : 
	  target_(target), 
	  proc_(proc),
	  source1_(var1),source2_(var2),source3_(var3) {}
	MvScmVar*  target() const {return target_;}
	  
	void exec(int,int);  

protected:
	MvScmVar*  target_;
	CompProc3  proc_;
	MvScmVar* source1_;
	MvScmVar* source2_;
	MvScmVar* source3_;
};

class MvScmProfileChange : public MvProfileChange
{
public:
	MvScmProfileChange() : var_(0), step_(-1), dependantVar_(0) {}
	MvScmProfileChange(MvScmVar* var,int step,int level,float val,float prevVal) : 
	           MvProfileChange(level,val,prevVal),
	           var_(var), step_(step) {};
	
	MvScmProfileChange(const MvScmProfileChange& c) : MvProfileChange(c), var_(c.var()), step_(c.step()) {} 
			   
	bool checkData(const MvScmProfileData&) const;
	
	MvScmVar* var() const {return var_;}
	int    step() const {return step_;}
	MvScmVar* dependantVar() const {return dependantVar_;}
	void setDependantVar(MvScmVar* v) {dependantVar_=v;}
	
protected:
	MvScmVar* var_;
	int step_;
	MvScmVar* dependantVar_;
	
};

class MvScmProfileObserver
{
public:
      MvScmProfileObserver() {};
      virtual void profileEdited(const MvScmProfileChange&)=0;
};
  

class MvScmProfileData : public MvProfileData 
{

public:
    	MvScmProfileData(MvScm*,MvScmVar*,int); 
    	
	float value(int);
	float level(int);
	float auxLevel(int);
	void  levels(vector<float>&);	
	int   count() const;
	void setValue(int, float);
	MvScmVar* var() const {return var_;}
	void setObserver(MvScmProfileObserver* obs) {observer_=obs;}
	int step() const {return step_;}
	bool acceptChange(const MvProfileChange&) const;
		
protected:  
	MvScmVar* var_;
	MvScmVar* auxLevel_;
	int step_;
	MvScmProfileObserver *observer_; 
};

class MvScmDim
{
public:
	MvScmDim() : size_(0) {}
	void init(NcFile& ,string);
	string name() const {return name_;}
	string longName() const {return longName_;}
	string units() const {return units_;}
	int size() const {return size_;}
	void setSize(int n) {size_=n;}
	const vector<float>& values() const {return values_;}
	bool isEmpty() const { return size_==0 ;}

protected:
	void setVar(NcVar*);
	void setAttributes(NcVar*);
	 
	string name_;
	string longName_;
	string units_;
	int size_;
	vector<float> values_;
};	



class MvScmVar
{
public:
    	enum LevelType {NoType,ModelLevelType,PressureLevelType,SurfaceLevelType,SoilLevelType};
	
	typedef double (*CompProc1)(double);
	typedef double (*CompProc2)(double,double);
	typedef double (*CompProc3)(double,double,double);
	
  	MvScmVar(LevelType,NcVar*,const MvScmDim&);
	MvScmVar(LevelType,NcVar*,const MvScmDim&,const MvScmDim&);
	MvScmVar(const string&, const string&, const string&,MvScmVar*);
	~MvScmVar();
	
	LevelType levelType() {return levelType_;}
  	string name() const {return name_;}
	string longName() const {return longName_;}
	string units() const;
	float value(int,int);
	float consistencyValue(int,int);
	const vector<float>&  data(int ts) const;
	const vector<float>&  dataOri(int ts) const;
	int stepNum() {return static_cast<int>(data_.size());}
	int levelNum() {return (data_.size() >0)?static_cast<int>(data_.at(0).size()):0;}
	bool setValue(int,int,float,MvScmProfileChange&,bool checkCons=true);
	bool setValues(int,float,vector<MvScmProfileChange>&,bool checkCons=true);
	bool setConsistentValue(int,int,float);
	bool setValue(int,int,float,bool checkCons=true);
	bool changed(int);
	bool changed(int,int);
	bool saveChanges(NcFile &);
	void reset();
	void setRange(float min,float max) { rangeMin_=min; rangeMax_=max; rangeSet_=true;}
	bool isRangeSet() const {return rangeSet_;}
	float rangeMin() const {return rangeMin_;}
	float rangeMax() const {return rangeMax_;}
	bool fitToRange(float&);
	void compute(MvScmVar*,CompProc1);
	void compute(MvScmVar*,MvScmVar*,CompProc2);
	void compute(MvScmVar*,MvScmVar*,MvScmVar*,CompProc3);
	void setConsistency(MvScmConsistency*);
	void setUnits(float,float,string);
	bool overwrite(int);
	bool write(ofstream &,int,bool,bool);
	
protected:
  	void setName(NcVar*);
 	void setAttributes(NcVar*);
	void checkConsistency(int,int);
	
	LevelType levelType_;
	string name_;
	string longName_;
	string units_;
	vector<vector<float> > data_;
	bool changed_;
	vector<vector<float> > dataOri_;
	bool presentInFile_;
	bool rangeSet_;
	float rangeMin_;
	float rangeMax_;
	MvScmConsistency* consistency_;
	MvScmUnitsConverter *unitsConverter_;
};

class MvScm
{

friend class MvScmVar;

public:
	MvScm(const string&,bool convertUnits=true);    
  	~MvScm();
	
	enum ModelLevelVariable {PresML,HeightML,TempML,WindUML,WindVML,SpHumML,RelHumML,CloudLiqML,CloudIceML,OzoneML};
	enum SoilVariable {TempSoil,HumSoil};
	enum SurfVariable {TempSurf,TempSeaIceSurf,TempSeaSurf,SeaIceFractionSurf,
	                   Temp2Surf,Td2Surf,SpecHum2Surf,WindU10Surf,WindV10Surf,
	                   ZSurf,LsiSurf,LsmSurf,OrogSurf,LatSurf,LonSurf,WaterTypeSurf,CloudTopPressSurf,
			   CloudFractionSurf};
	
	const string& fileName() const {return fileName_;}
	
	const MvScmDim& timeDim() const {return timeDim_;}
	const MvScmDim& modelLevelDim() const {return modelLevDim_;}
	const MvScmDim& pressureLevelDim() const {return pressureLevDim_;}
	const MvScmDim& soilLevelDim() const {return soilLevDim_;}
	
	const vector<MvScmVar*>& modelLevel() {return ml_;}
	const vector<MvScmVar*>& pressureLevel() {return pl_;}
	const vector<MvScmVar*>& soilLevel() {return soil_;}
	const vector<MvScmVar*>& surfaceLevel() {return surf_;}
	const vector<float>& steps() const {return timeDim_.values();}	
	int stepNum() const {return timeDim_.size();}	
	int modelLevelNum() const {return modelLevDim_.size();}
	void save();
	void reset();
	string id() const {return id_;};
	
	
	static int modelLevelNum(const string&);
	static bool mergeOutFiles(const string&,const string&,const string&);
	
	int mlVarIndex(ModelLevelVariable);
	MvScmVar* mlVar(ModelLevelVariable);
	int soilVarIndex(SoilVariable);
	MvScmVar* soilVar(SoilVariable);
	int surfVarIndex(SurfVariable);
	MvScmVar* surfVar(SurfVariable);
	void initRelHum();
	bool overwrite(int);
	static void needConsistency(bool);
	static bool needConsistency() {return needConsistency_;}
	
	static bool 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);
	
protected:
        void decodeId();
	void mapScmVars();
	void mapRttovVars();
    	void decode();
       	void decodeVar(NcVar*);
	void notifyObservers(MvScmVar*,int,int,float,float);
       	static bool addDimensionsToFile(NcFile&,NcFile&,std::vector<NcDim*>&);
       	static void addVarToFile(NcVar *,NcFile&,const std::vector<NcDim*>&);
            	
    	string fileName_;
	string id_;
	bool convertUnits_;
	
	MvScmDim	timeDim_;
	MvScmDim	modelLevDim_;
	MvScmDim	pressureLevDim_;
	MvScmDim	soilLevDim_;
	map<string,string> dimName_;
		
	vector<MvScmVar*> ml_;
	vector<MvScmVar*> pl_;
	vector<MvScmVar*> surf_; 
	vector<MvScmVar*> soil_; 
	
	map<string,ModelLevelVariable> mlVarName_;
	map<ModelLevelVariable,int> mlVarIndex_;
	
	map<string,SoilVariable> soilVarName_;
	map<SoilVariable,int> soilVarIndex_;
	
	map<string,SurfVariable> surfVarName_;
	map<SurfVariable,int> surfVarIndex_;
	static bool needConsistency_;
};  

#endif
