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

 Copyright 2019 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 "MvSci.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(MvNetCDF& nc, string name)
{
    name_ = name;

    if (MvNcDim* dim = nc.getDimension(name)) {
        size_ = dim->size();
        if (MvNcVar *var = nc.getVariable(name)) {
            setVar(var);
        }
    }
}

void MvScmDim::setVar(MvNcVar* var)
{
    values_.clear();

    if (!var || size_ <=0)
        return;

    if (var->getNumberOfDimensions() != 1) {
        return;
    }

    MvNcDim* dim = var->getDimension(0);

    if (dim && strcmp(dim->name(), name_.c_str()) == 0) {
        values_ = vector<float>();
        var->get(values_, size_);
    } else {
        for (int i = 0; i < size_; i++)
            values_.push_back(i + 1);
    }

    setAttributes(var);
}

void MvScmDim::setAttributes(MvNcVar* var)
{
    int n = var->getNumberOfAttributes();
    for (int i = 0; i < n; i++) {
        if (MvNcAtt* att = var->getAttribute(i)) {
            if (att->sname() == "long_name" && att->values()) {
                longName_ = att->as_string(0);
            }
            else if (att->sname() == "units" && att->values()) {
                units_ = att->as_string(0);
            }           
        }
    }
}

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

MvScmVar::MvScmVar(LevelType levelType, MvNcVar* 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];

    for (int i = 0; i < tNum; i++) {
        vector<float> vals;
        counts[0] = 1;
        var->setCurrent(i);
        var->get(vals, counts);
        data_.push_back(vals);
        dataOri_.push_back(vector<float>());
    }
}

MvScmVar::MvScmVar(LevelType levelType, MvNcVar* 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];

    for (int i = 0; i < tNum; i++) {
        vector<float> vals;
        counts[0] = 1;
        counts[1] = levNum;
        var->setCurrent(i,0);
        var->get(vals, counts);
        data_.push_back(vals);
        dataOri_.push_back(vector<float>());
    }
}

//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(MvNcVar* var)
{
    name_ = var->sname();
}

void MvScmVar::setAttributes(MvNcVar* var)
{
    int n = var->getNumberOfAttributes();
    for (int i = 0; i < n; i++) {
        if (MvNcAtt* att = var->getAttribute(i)) {
            if (att->sname() == "long_name" && att->values()) {
                longName_ = att->as_string(0);
            }
            else if (att->sname() == "units" && att->values()) {
                units_ = att->as_string(0);
            }           
        }
    }
}

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(MvNetCDF& nc)
{
    if (!changed_)
        return true;

    if (presentInFile_) {
        MvNcVar* outVar = nullptr;

        for (int i = 0; i < nc.getNumberOfVariables(); i++) {
            MvNcVar* v = nc.getVariable(i);
            if (v && strcmp(v->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_) {
                        vector<float> vals(data_.at(ts).size());
                        for (size_t i = 0; i < data_.at(ts).size(); i++) {
                            vals[i] = unitsConverter_->convertFrom(data_.at(ts).at(i));
                        }
                        outVar->setCurrent(ts, 0);
                        outVar->put(vals, counts[0], counts[1]);
                    }
                    else {
                        outVar->setCurrent(ts, 0);
                        outVar->put(data_.at(ts), counts[0], counts[1]);
                    }
                }

                else if (levelType_ == SurfaceLevelType) {
                    long counts[1];
                    counts[0] = 1;

                    if (unitsConverter_) {
                        vector<float> vals;
                        vals.push_back(unitsConverter_->convertFrom(data_.at(ts).at(0)));
                        outVar->setCurrent(ts);
                        outVar->put(vals, counts[0]);
                    }
                    else {
                        outVar->setCurrent(ts);
                        outVar->put(data_.at(ts), counts[0]);
                    }
                }
            }
        }
    }

    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) {
        mapScmVars();
    }
    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()
{
    MvNetCDF nc(fileName_,'r');

    //Get id
    int n = nc.getNumberOfAttributes();
    for (int i = 0; i < n; i++) {
        MvNcAtt* att = nc.getAttribute(i);
        if (att != nullptr && att->name() != nullptr &&
            strcmp(att->name(), "dataID") == 0 && att->values()) {
            id_ = att->as_string(0);
            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()
{
    MvNetCDF nc(fileName_);

    //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.getNumberOfVariables();
    for (int i = 0; i < varNum; i++) {
        MvNcVar* var = nc.getVariable(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(MvNcVar* 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->getNumberOfDimensions(); i++) {
        MvNcDim* dim     = var->getDimension(i);
        dims.insert(dim->sname());
    }


    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
    MvNetCDF nc(fileName_, 'w');

    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, &MvSci::relativeHumidity, t, p, q));
    r->setConsistency(new MvScmConsistency(q, &MvSci::specificHumidity, t, p, r));
}


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

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

int MvScm::modelLevelNum(const string& fileName)
{
    //Open file
    MvNetCDF nc(fileName);

    int num    = -1;
    if (MvNcDim* dim = nc.getDimension("nlev")) {
        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
    MvNetCDF nc(progvarFileName, 'w');

    //Get dims from progvar
    vector<MvNcDim*> dims;
    for (int i = 0; i < nc.getNumberOfDimensions(); i++) {
        MvNcDim* dim = nc.getDimension(i);
        if (dim) {
            dims.push_back(dim);            
        }
        else {
            std::cout << "MvScm::mergeOutFiles --> bad dimension at position=" << i << endl;
        }
    }

    //---------------------
    // Diag
    //---------------------

    MvNetCDF ncDiag(diagvarFileName, 'r');

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

    //Variables
    int varNum = ncDiag.getNumberOfVariables();
    for (int i = 0; i < varNum; i++) {
        MvNcVar* var = ncDiag.getVariable(i);
        addVarToFile(var, nc, dims);
    }
    ncDiag.close();

    //---------------------
    // Diag2
    //---------------------

    MvNetCDF ncDiag2(diagvar2FileName, 'r');

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


    //Variables
    varNum = ncDiag2.getNumberOfVariables();
    for (int i = 0; i < varNum; i++) {
        MvNcVar* var = ncDiag2.getVariable(i);
        addVarToFile(var, nc, dims);
    }
    ncDiag2.close();


    //Add global attributes
    nc.addAttribute("dataID", "SCM_OUTPUT");

    nc.close();

    return true;
}


bool MvScm::addDimensionsToFile(MvNetCDF& inNc, MvNetCDF& outNc, std::vector<MvNcDim*>& dims)
{
    for (int i = 0; i < inNc.getNumberOfDimensions(); i++) {
        MvNcDim* dim = inNc.getDimension(i);
        bool found = false;
        for (unsigned int j = 0; j < dims.size(); j++) {           
            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.addDimension(dim->sname(), dim->size()));
        }
    }

    return true;
}

void MvScm::addVarToFile(MvNcVar* inVar, MvNetCDF& outNc, const std::vector<MvNcDim*>& outDims)
{
    //Check var name
    for (int i = 0; i < outNc.getNumberOfVariables(); i++) {
        MvNcVar* v = outNc.getVariable(i);
        if (v && strcmp(v->name(), inVar->name()) == 0) {
            //cout << v->name() << endl;
            return;
        }
    }

    //Check dims
    vector<MvNcDim*> dims;
    for (int i = 0; i < inVar->getNumberOfDimensions(); i++) {
        MvNcDim* dim = inVar->getDimension(i);
        if (dim) {
            //cout << dim->sname() << " " << 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
    MvNcVar* outVar = 0;
    if (inVar->getNumberOfDimensions() == 0) {
        outVar = outNc.addVariable(inVar->sname(), inVar->type(), -1);
    }
    else if (inVar->getNumberOfDimensions() == 1 && dims.size() == 1) {
        outVar = outNc.addVariable(inVar->sname(), inVar->type(), 1,
                                   const_cast<const MvNcDim**>(dims.data()));
    }
    else if (inVar->getNumberOfDimensions() == 2 && dims.size() == 2) {
        outVar = outNc.addVariable(inVar->sname(), inVar->type(), 2,
                                   const_cast<const MvNcDim**>(dims.data()));
    }
    else {
        return;
    }

    //Add attributes
    for (int i = 0; i < inVar->getNumberOfAttributes(); i++) {
        if (MvNcAtt* att = inVar->getAttribute(i)) {
            outVar->addAttribute(att);
        }
    }

    //Add values_
    if (inVar->getNumberOfDimensions() == 0) {
        std::vector<float> vals;
        inVar->get(vals, 0, 0, 0, 0, 0);
        outVar->put(vals, 0, 0, 0, 0, 0);
    }
    if (inVar->getNumberOfDimensions() == 1) {
        long counts[1];
        counts[0]   = dims[0]->size();
        //float* vals = new float[counts[0]];
        std::vector<float> vals;
        inVar->get(vals, counts[0]);
        outVar->put(vals, counts[0]);
    }
    if (inVar->getNumberOfDimensions() == 2) {
        long counts[2];
        counts[0]   = dims[0]->size();
        counts[1]   = dims[1]->size();
        std::vector<float> vals;
        //float* vals = new float[counts[0] * counts[1]];
        inVar->get(vals, counts[0], counts[1]);
        outVar->put(vals, counts[0], counts[1]);
    }
}

//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, &MvSci::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, &MvSci::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 = MvSci::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;
}
