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

#pragma once

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

#include "MvNetCDF.h"

#include "MvProfileData.h"
#include "MvMiscellaneous.h"

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_;
    std::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()) {}

    virtual ~MvScmProfileChange() {}

    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_{nullptr};
    int step_{0};
    MvScmVar* dependantVar_{nullptr};
};

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(std::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(MvNetCDF&, std::string);
    std::string name() const { return name_; }
    std::string longName() const { return longName_; }
    std::string units() const { return units_; }
    int size() const { return size_; }
    void setSize(int n) { size_ = n; }
    const std::vector<float>& values() const { return values_; }
    bool isEmpty() const { return size_ == 0; }

protected:
    void setVar(MvNcVar*);
    void setAttributes(MvNcVar*);

    std::string name_;
    std::string longName_;
    std::string units_;
    int size_;
    std::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, MvNcVar*, const MvScmDim&);
    MvScmVar(LevelType, MvNcVar*, const MvScmDim&, const MvScmDim&);
    MvScmVar(const std::string&, const std::string&, const std::string&, MvScmVar*);
    ~MvScmVar();

    LevelType levelType() { return levelType_; }
    std::string name() const { return name_; }
    std::string longName() const { return longName_; }
    std::string units() const;
    float value(int, int);
    float consistencyValue(int, int);
    const std::vector<float>& data(int ts) const;
    const std::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, std::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(MvNetCDF&);
    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, std::string);
    bool overwrite(int);
    bool write(std::ofstream&, int, bool, bool);

protected:
    void setName(MvNcVar*);
    void setAttributes(MvNcVar*);
    void checkConsistency(int, int);

    LevelType levelType_{NoType};
    std::string name_;
    std::string longName_;
    std::string units_;
    std::vector<std::vector<float> > data_;
    bool changed_{false};
    std::vector<std::vector<float> > dataOri_;
    bool presentInFile_{false};
    bool rangeSet_{false};
    float rangeMin_{0.};
    float rangeMax_{0.};
    MvScmConsistency* consistency_{nullptr};
    MvScmUnitsConverter* unitsConverter_{nullptr};
};

class MvScm
{
    friend class MvScmVar;

public:
    MvScm(const std::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 std::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 std::vector<MvScmVar*>& modelLevel() { return ml_; }
    const std::vector<MvScmVar*>& pressureLevel() { return pl_; }
    const std::vector<MvScmVar*>& soilLevel() { return soil_; }
    const std::vector<MvScmVar*>& surfaceLevel() { return surf_; }
    const std::vector<float>& steps() const { return timeDim_.values(); }
    int stepNum() const { return timeDim_.size(); }
    int modelLevelNum() const { return modelLevDim_.size(); }
    void save();
    void reset();
    std::string id() const { return id_; }


    static int modelLevelNum(const std::string&);
    static bool mergeOutFiles(const std::string&, const std::string&, const std::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 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);

protected:
    void decodeId();
    void mapScmVars();
    void mapRttovVars();
    void decode();
    void decodeVar(MvNcVar*);
    void notifyObservers(MvScmVar*, int, int, float, float);
    static bool addDimensionsToFile(MvNetCDF&, MvNetCDF&, std::vector<MvNcDim*>&);
    static void addVarToFile(MvNcVar*, MvNetCDF&, const std::vector<MvNcDim*>&);

    std::string fileName_;
    std::string id_;
    bool convertUnits_;

    MvScmDim timeDim_;
    MvScmDim modelLevDim_;
    MvScmDim pressureLevDim_;
    MvScmDim soilLevDim_;
    std::map<std::string, std::string> dimName_;

    std::vector<MvScmVar*> ml_;
    std::vector<MvScmVar*> pl_;
    std::vector<MvScmVar*> surf_;
    std::vector<MvScmVar*> soil_;

    std::map<std::string, ModelLevelVariable> mlVarName_;
    std::map<ModelLevelVariable, int> mlVarIndex_;

    std::map<std::string, SoilVariable> soilVarName_;
    std::map<SoilVariable, int> soilVarIndex_;

    std::map<std::string, SurfVariable> surfVarName_;
    std::map<SurfVariable, int> surfVarIndex_;
    static bool needConsistency_;
};
