/***************************** 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 <fstream>
#include <iostream>
#include <map>
#include <set>
#include <vector>

#include "fstream_mars_fix.h"

#include "MvMiscellaneous.h"
#include "MvSci.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);

    auto 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();

    std::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(std::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, std::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_ = std::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)
{
    setName(var);
    setAttributes(var);

    int tNum = timeDim.size();
    long counts[1];

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

// Create a new variable that is not present in the file
MvScmVar::MvScmVar(const std::string& name, const std::string& longName, const std::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(std::vector<float>(var->data(i).size(), 0.));
        dataOri_.push_back(std::vector<float>());
    }
}

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

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

    static std::vector<float> emptyVec;
    return emptyVec;
}

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

    static std::vector<float> emptyVec;
    return emptyVec;
}


std::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, std::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_) {
                        std::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_) {
                        std::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 std::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 std::vector<float>& d1 = v1->data(ts);
        const std::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 std::vector<float>& d1 = v1->data(ts);
        const std::vector<float>& d2 = v2->data(ts);
        const std::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, std::string units)
{
    if (unitsConverter_)
        return;

    unitsConverter_ = new MvScmUnitsConverter;
    unitsConverter_->offset_ = offset;
    unitsConverter_->scale_ = scale;
    unitsConverter_->units_ = units;

    for (auto& ts : data_) {
        for (unsigned int lev = 0; lev < ts.size(); lev++) {
            ts[lev] = unitsConverter_->convertTo(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(std::ofstream& out, int ts, bool useConverted, bool newline)
{
    if (ts >= 0 && ts < static_cast<int>(data_.size())) {
        for (float lev : data_.at(ts)) {
            if (!unitsConverter_ || useConverted)
                out << lev;
            else
                out << unitsConverter_->convertFrom(lev);

            if (newline)
                out << std::endl;
        }
        return true;
    }

    return false;
}


//=========================================================
//
//  MvScm
//
//=========================================================

MvScm::MvScm(const std::string& fileName, bool convertUnits) :
    fileName_(fileName),
    convertUnits_(convertUnits)
{
    // Figure out the id
    decodeId();

    // Map variables
    if (id_.find("SCM") != std::string::npos || id_.find("scm") != std::string::npos) {
        mapScmVars();
    }
    if (id_ == "NETCDF_RTTOV_INPUT") {
        mapRttovVars();
    }

    // Decode data
    decode();
}

MvScm::~MvScm()
{
    for (auto& i : ml_)
        delete i;

    for (auto& i : pl_)
        delete i;

    for (auto& i : surf_)
        delete i;

    for (auto& i : soil_)
        delete 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 != nullptr && att->name() != nullptr &&
           strcmp(att->name(),"dataID") == 0 && att->num_vals() > 0)
        {
            char* v=att->as_string(0);
            if(v) id_=std::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<std::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_));
        auto 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_));

            auto 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.);

                    auto 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_));
            auto 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 (auto& i : ml_) {
        i->saveChanges(nc);
    }
    for (auto& i : surf_) {
        i->saveChanges(nc);
    }
    for (auto& i : soil_) {
        i->saveChanges(nc);
    }
    for (auto& i : pl_) {
        i->saveChanges(nc);
    }
}

void MvScm::reset()
{
    for (auto& i : ml_) {
        i->reset();
    }
    for (auto& i : surf_) {
        i->reset();
    }
    for (auto& i : soil_) {
        i->reset();
    }
    for (auto& i : pl_) {
        i->reset();
    }
}

bool MvScm::overwrite(int fromTs)
{
    for (auto& i : ml_) {
        i->overwrite(fromTs);
    }
    for (auto& i : surf_) {
        i->overwrite(fromTs);
    }
    for (auto& i : soil_) {
        i->overwrite(fromTs);
    }
    for (auto& i : pl_) {
        i->overwrite(fromTs);
    }

    return true;
}

int MvScm::mlVarIndex(ModelLevelVariable var)
{
    auto 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)
{
    auto 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)
{
    auto 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 std::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 std::string& progvarFileName, const std::string& diagvarFileName, const std::string& diagvar2FileName)
{
    // Open progvar
    MvNetCDF nc(progvarFileName, 'w');

    // Get dims from progvar
    std::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 << std::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 (auto& j : dims) {
            if (strcmp(dim->name(), j->name()) == 0) {
                if (dim->size() != 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) {
            // std::cout << v->name() << std::endl;
            return;
        }
    }

    // Check dims
    std::vector<MvNcDim*> dims;
    for (int i = 0; i < inVar->getNumberOfDimensions(); i++) {
        MvNcDim* dim = inVar->getDimension(i);
        if (dim) {
            // std::cout << dim->sname() << " " << dim->size() << std::endl;
            for (auto outDim : outDims) {
                if (strcmp(dim->name(), outDim->name()) == 0 &&
                    dim->size() == outDim->size()) {
                    dims.push_back(outDim);
                    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 std::string& inFileName, const std::string& outFileName,
                             float sat_zenith_angle, float sat_azimuth_angle,
                             float solar_zenith_angle, float solar_azimuth_angle,
                             bool useOzone, std::string& errTxt)
{
    // Open input NetCDF file, and use original units
    MvScm in(inFileName.c_str(), false);

    // Check the required profiles
    std::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
    auto* 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
    std::ofstream out(outFileName.c_str());
    if (!out.good())
        return false;

    // Generate ASCII output
    out << "! --- Profile 1 ---" << std::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." << std::endl;

    // tSurf->write(out,0,false);
    // out << "  15248.0550  1007.30      5.000             2.0000            100000." << std::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" << std::endl;


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

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

    out << orog << " "
        << " " << lat << " " << lon << std::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 << std::endl;


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


    out << cltop << " " << clfract << std::endl;

    out << "!\n\
! --- End of profile 1 ---\n\
!";


    out.close();

    delete ppmv;

    return true;
}
