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

// Header file for Xsect application. This is really 3 applications : Xsection,
// Average and Vertical Profile, depending on which request it receives.


#pragma once

#include "Metview.h"
#include "MvNetCDF.h"

const double XMISSING_VALUE = 1.0E22;

// Used in the interpolation procedure to create more 'valid' points near
// the orography. This is to avoid having empty areas in the plot.
const double THRESHOLD_INTERPOLATION = 3000.;  // 30hPa

// Offset used to add extra levels when converting model to pressure levels
const float OFFSET_LEVELINFO = 1000.;  // 10hPa

// Level type data combined with specific fields
// cML_UKMO_ND =  n;       // UKMO New Dynamics - defined elsewhere
enum
{
    XS_ML,       // ECMWF model levels, no lnsp specified
    XS_PL,       // Pressure levels
    XS_ML_LNSP,  // ECMWF model levels with lnsp, otput will be pressure
    XS_ML_GHBC,  // User specified vertical coordinate fields with model levels
    XS_GHBC,     // User specified vertical coordinate fields not on model levels
    XS_ASIS      // level as is (not falling into any other category)
};

enum ExtrapolateMode
{
    ConstantExtrapolation,
    LinearExtrapolation
};

const double cCDR = atan(1.0) / 45.0;  //-- conversion factor Degrees->Radians
                                       //-- atan(1)/45 = (PI/4)/45 = PI/180

const std::string XS_VARLEVEL = "nlev";
const std::string XS_VARTIME = "time";
const std::string XS_VARLAT = "lat";
const std::string XS_VARLON = "lon";
const std::string XS_VARLATPLOT = "lat_for_plot";
const std::string XS_VARLONPLOT = "lon_for_plot";

//-- Q&D trick: still use global vars
//-- but let them be non-const!
#ifdef INITIALISE_HERE
int U_FIELD = 131;
int V_FIELD = 132;
int W_FIELD = 135;
int LNSP_FIELD = 152;
int T_FIELD = 130;
#else
extern int U_FIELD;
extern int V_FIELD;
extern int W_FIELD;
extern int LNSP_FIELD;
extern int T_FIELD;
#endif

// Type of input request to be processed
enum
{
    XS_DATA_DROPPED,
    XS_DATA_MODULE,
    XS_DATA_MODULE_DROPPED
};

// Holds info about the values for 1 level.
class LevelInfo
{
public:
    double* XValues() { return xvalues_; }
    double YValue() { return yvalue_; }

private:
    friend class ParamInfo;

    void UpdateLevel(double lev)
    {
        yvalue_ = lev;
    }

    LevelInfo(double lev) :
        xvalues_(nullptr), yvalue_(lev) {}

    LevelInfo() :
        xvalues_(nullptr),
        yvalue_(DBL_MAX) {}

    double *xvalues_, yvalue_;
};

// Do a numeric comparison for the level map, instead
// of the normal alphabetic one.
class LevelCompare
{
public:
    bool operator()(const std::string& s1, const std::string& s2) const
    {
        return (atof(s1.c_str()) < atof(s2.c_str()));
    }
};

using LevelMap = std::map<std::string, LevelInfo*, LevelCompare>;
using LevelIterator = LevelMap::iterator;


// Info about a parameter at one date, time and step.
// Contains  a LevelMap with info about the levels.
class ParamInfo
{
public:
    ParamInfo() :
        param_(0),
        date_(0),
        step_(0),
        time_(0),
        paramName_(" "),
        paramLongName_(" "),
        expver_("_"),
        units_(" "),
        levType_(" ") {}

    ParamInfo(int i1, int i2, int i3, int i4, std::string sn, std::string ln, std::string u, std::string lt) :
        param_(i1),
        date_(i2),
        step_(i4),
        time_(i3),
        paramName_(sn),
        paramLongName_(ln),
        expver_("_"),
        units_(u),
        levType_(lt) {}

    ~ParamInfo();

    int Date() { return date_; }
    int Time() { return time_; }
    int Step() { return step_; }
    int Parameter() { return param_; }
    std::string ParamName() { return paramName_; }
    std::string ParamLongName() { return paramLongName_; }
    std::string Units() { return units_; }
    std::string LevelType() { return levType_; }
    bool IsSurface();
    const char* ExpVer() { return expver_.c_str(); }
    std::string ExpVerTitle();
    void ExpVer(const char* xx) { expver_ = xx; }
    void Level(double*, std::string, int, bool);
    LevelMap& Levels() { return levels_; }
    MvDate ReferenceDate();
    MvDate VerificationDate();

    void AddLevel(std::string);
    void UpdateLevels(bool);

    int NrLevels() { return levels_.size(); }
    void Print()
    {
        printf("P: %d %d %d %d %s %s %s\n", param_, date_, step_, time_, paramName_.c_str(), units_.c_str(), levType_.c_str());
    }

    friend int operator<(const ParamInfo& p1, const ParamInfo& p2)
    {
        return (p1.param_ < p2.param_ || p1.date_ < p2.date_ || p1.time_ < p2.time_ || p1.step_ < p2.step_);
    }

    double* getOneLevelValues(const std::string&);

private:
    int param_, date_, step_, time_;
    std::string paramName_;
    std::string paramLongName_;
    std::string expver_;
    std::string units_;
    std::string levType_;
    LevelMap levels_;
};

using ParamMap = std::map<std::string, ParamInfo*>;
using ParamIterator = ParamMap::iterator;
using ParamInsertPair = std::pair<ParamIterator, bool>;
using ParamPair = std::pair<const std::string, ParamInfo*>;


//------------------------------ General info for application.

class ApplicationInfo
{
public:
    ApplicationInfo() = default;

    double topLevel() { return topLevel_; }
    double bottomLevel() { return bottomLevel_; }
    void topLevel(double d) { topLevel_ = d; }
    void bottomLevel(double d) { bottomLevel_ = d; }

    // Handle geographical coordinates
    void computeLine(int);
    void computeLine(int&, MvField*);
    const std::vector<double>& getLongitude() const { return lon_; }
    const std::vector<double>& getLatitude() const { return lat_; }
    const std::vector<double>& getLatitudeForPlotting() const { return latForPlotting_; }
    const std::vector<double>& getLongitudeForPlotting() const { return lonForPlotting_; }

    void setAreaLine(double, double, double, double);
    void getAreaLine(double&, double&, double&, double&);
    void getAreaLine(double*);
    double X1() { return x1_; }
    double Y1() { return y1_; }
    double X2() { return x2_; }
    double Y2() { return y2_; }

    void Grid(double ns, double ew);
    double GridNS() { return gridNS_; }
    double GridEW() { return gridEW_; }

    double Ancos();
    double Ansin();

    void actionMode(std::string action) { actionMode_ = action; }
    std::string actionMode() { return actionMode_; }

    void processType(int type) { procType_ = type; }
    int processType() { return procType_; }

    int NrPoints() { return nrPoints_; }
    void NrPoints(int np) { nrPoints_ = np; }

    int NTimes() { return ntimes_; }
    void NTimes(int ntimes) { ntimes_ = ntimes; }

    // Handle level info
    void levelType(int lt) { levType_ = lt; }
    int levelType() { return levType_; }
    void determineLevelType(bool, bool);
    void updateLevels(ParamMap&);
    void setMinMaxLevels(double, double, int = 0);
    void getMinMaxLevels(double&, double&, int&);
    void outputLevels(int n) { nOutputLevels_ = n; }
    int outputLevels() { return nOutputLevels_; }
    static double tohPa(double v) { return v / 100.; }
    static double toPa(double v) { return v * 100.; }

    // Get level values
    void getLevelValues(ParamInfo*, std::vector<double>&);

    // Compute indexes levels (vertical values)
    void computeLevelInfoUI(ParamInfo*, std::vector<double>&, double*, MvField&);  // from UI

    int computeLevelInfoWithCount(std::vector<double>&);          // compute equal separated levels
    void computeLevelInfoGHBC(ParamInfo*, std::vector<double>&);  // model level & GHBC

    bool computeLevelMatrixValues(ParamMap&, const std::string&, ParamInfo*, std::vector<double>&, std::vector<double>&, MvField&);
    bool computeLevelMatrixValues(ParamMap&, const std::string&, std::vector<double>&, std::vector<double>&, MvField&);

    void scaleVerticalVelocity_mv3(ParamInfo*);
    void computeVerticalVelocity(ParamInfo*, ParamInfo*, ParamInfo*, MvField& field, double);
    void scaleVerticalVelocity(ParamInfo*, double);

    void InterpolateVerticala(std::vector<double>&, ParamInfo*, std::vector<double>&);
    void InterpolateVerticalb(MvField&, std::vector<double>&, ParamInfo*, double*, std::vector<double>&);
    void InterpolateVerticalGHBC(std::vector<double>&, ParamInfo*, ParamInfo*, std::vector<double>&);

    void getPressureOnLevel(double* splin, MvField& field, std::vector<double>& p, double level);
    bool findModelPressure(LevelMap&, double, double, MvField&, LevelInfo*&, LevelInfo*&, double&, double&);

    bool findModelPressureGHBC(LevelMap&, LevelMap&, double, int, LevelInfo*&, LevelInfo*&, double&, double&, bool&);

    bool findPressure(LevelMap&, double, LevelInfo*&, LevelInfo*&);

    bool generateAverageData(ParamMap&, MvNetCDF&, const std::string&, MvField&, double*, double*, double*);

    // Handle vertical interpolation settings
    // Two ways to set a vertical interpolation flag:
    // a) model to pressure level (setVerticalInterpolationFlag)
    // b) wind plotting (setVerticalInterpolationFlag(flag). This
    //    is because Magics is not doing the interpolation for winds.
    void setVerticalInterpolationFlag();
    void setVerticalInterpolationFlag(bool);
    bool isInterpolate() { return interpolate_; }

    bool viaPole() { return viaPole_; }
    bool haveLatsForPlotting() { return haveLatsForPlotting_; }
    void haveLatsForPlotting(bool b) { haveLatsForPlotting_ = b; }
    bool haveLonsForPlotting() { return haveLonsForPlotting_; }
    void haveLonsForPlotting(bool b) { haveLonsForPlotting_ = b; }

    // Handle LNSP field
    bool haveLNSP() { return haveLNSP_; }
    void haveLNSP(bool flag) { haveLNSP_ = flag; }
    bool isLNSP(int iparam) { return iparam == LNSP_FIELD; }
    int LNSP() { return LNSP_FIELD; }

    // Handle generic coordinates
    bool haveGHBC() { return haveGHBC_; }
    void haveGHBC(bool flag) { haveGHBC_ = flag; }
    void setGHBC(int iparam) { paramGHBC_ = iparam; }
    void setGHBCExtrapolate(bool b) { extrapolateGHBC_ = b; }
    bool extrapolateGHBC() const { return extrapolateGHBC_; }
    void setExtrapolateGHBCMode(ExtrapolateMode m) { extrapolateGHBCMode_ = m; }
    void setExtrapolateGHBCFixedSign(bool b) { extrapolateGHBCFixedSign_ = b; }
    bool isGHBC(int iparam) const { return iparam == paramGHBC_; }
    int GHBC() { return paramGHBC_; }

    // Handle horizontal point mode
    void horPointMode(std::string mode) { hor_point_ = mode; }
    std::string horPointMode() { return hor_point_; }
    bool isHorInterpolate() { return hor_point_ == "INTERPOLATE"; }

    // Retrieve information about one parameter
    ParamInfo* getParamInfo(ParamMap&, const std::string&);
    ParamInfo* getParamInfo(ParamMap&, int, const std::string&);

    // Handle parameters retrieved from the User Interface
    std::string levelSelectionType() { return uiLSType_; }
    void levelSelectionType(const std::string& type) { uiLSType_ = type; }

    int levelCount() { return uiLSCount_; }
    void levelCount(int count) { uiLSCount_ = count; }

    std::vector<double> levelList() { return uiLSList_; }
    void levelList(std::vector<double>&);

    bool vLinearScaling() { return uiVLinearScaling_; }
    void vLinearScaling(bool bvs) { uiVLinearScaling_ = bvs; }

private:
    double GetInterpolatedYValue(int);
    int computeLevelInfo(ParamInfo*, std::vector<double>&);                                               // model/pressure level
    int computeLevelInfoFromData(ParamInfo*, std::vector<double>&, double*, MvField&, bool useSampling);  // model level & LNSP
    void computeLevelInfoGHBCFromData(ParamInfo*, std::vector<double>&, bool useAdaptive);

    double x1_{0.}, x2_{0.}, y1_{0.}, y2_{0.};
    double gridNS_{0.}, gridEW_{0.};
    double topLevel_{0.}, bottomLevel_{0.};
    double levelValueFactor_{1.};

    int nrPoints_{0};       // number of horizontal points
    int nInputLevels_{0};   // number of input data model/pressure levels
    int nOutputLevels_{0};  // number of output levels (may be different than nInputLevels_)
    int ntimes_{0};         // number of times
    int levType_{XS_PL};    // level type data, e.g., XS_ML/XS_PL/XS_ML_LNSP/XS_ML_GHBC
    bool viaPole_{false};
    bool haveLatsForPlotting_{false};
    bool haveLonsForPlotting_{false};
    bool haveLNSP_{false};
    bool haveGHBC_{false};  // vertical coordinates flag
    int paramGHBC_{-991};   // vertical coordinate parameter
    bool extrapolateGHBC_{false};
    ExtrapolateMode extrapolateGHBCMode_{ConstantExtrapolation};
    bool extrapolateGHBCFixedSign_{true};
    std::string actionMode_;  // e.g. "examine"/"save"/"execute"/...
    int procType_{0};         // processing type

    std::string hor_point_{"INTERPOLATE"};  // horizontal point mode
    std::vector<double> lat_;               // geographical coordinates
    std::vector<double> lon_;
    std::vector<double> latForPlotting_;  // used when line is over a pole and Magics cannot plot the lat co-ordinates
    std::vector<double> lonForPlotting_;  // used when line is over a pole and Magics cannot plot the lon co-ordinates

    bool interpolate_{false};  // vertical interpolation flag

    // Parameters from the User Interface
    std::string uiLSType_{"FROM_DATA"};  // level selection type: FROM_DATA, COUNT, LEVEL_LIST
    int uiLSCount_{100};                 // level selection count
    std::vector<double> uiLSList_;       // level list
    bool uiVLinearScaling_{true};        // vertical scaling: [true=LINEAR, false=LOG]
};
