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


#ifndef MVXSECTFRAME_H
#define MVXSECTFRAME_H

#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 or level as is (not falling into any other category)
    XS_ML_LNSP, //ECMWF model levels with lnsp
    XS_ML_GHBC //User specified vertical coordinate fields (NOTE: the name is
               // misleading since it does not have to be ML at all!
};

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

const string XS_VARLEVEL   = "nlev";
const string XS_VARTIME    = "time";
const string XS_VARLAT     = "lat";
const string XS_VARLON     = "lon";
const string XS_VARLATPLOT = "lat_for_plot";
const 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, bool modlev)
    {
        yvalue_ = modlev ? lev : lev * 100.;
    }

    LevelInfo(double lev, bool modlev) :
        xvalues_(nullptr)
    {
        yvalue_ = modlev ? lev : lev * 100.;
    }

    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 string& s1, const string& s2) const
    {
        return (atof(s1.c_str()) < atof(s2.c_str()));
    }
};

typedef map<string, LevelInfo*, LevelCompare> LevelMap;
typedef LevelMap::iterator LevelIterator;


// 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, string sn, string ln, string u, 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_; }
    string ParamName() { return paramName_; }
    string ParamLongName() { return paramLongName_; }
    string Units() { return units_; }
    string LevelType() { return levType_; }
    bool IsSurface();
    const char* ExpVer() { return expver_.c_str(); }
    string ExpVerTitle();
    void ExpVer(const char* xx) { expver_ = xx; }
    void Level(double*, string, int, bool);
    LevelMap& Levels() { return levels_; }
    MvDate ReferenceDate();
    MvDate VerificationDate();

    void AddLevel(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 string&);

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

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


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

class ApplicationInfo
{
public:
    // Constructor
    ApplicationInfo();

    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 vector<double>& getLongitude() const { return lon_; }
    const vector<double>& getLatitude() const { return lat_; }
    const vector<double>& getLatitudeForPlotting() const { return latForPlotting_; }
    const 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(string action) { actionMode_ = action; }
    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_; }
    int levelType(bool, bool);
    bool levelTypeFlag();
    void updateLevels(ParamMap&);
    void setMinMaxLevels(double, double, int = 0);
    void getMinMaxLevels(double&, double&, int&);
    void outputLevels(int n) { nOutputLevels_ = n; }
    int outputLevels() { return nOutputLevels_; }

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

    // Compute indexes levels (vertical values)
    int computeLevelInfoUI(ParamInfo*, vector<double>&, double*, MvField&);  // from UI
    int computeLevelInfo(ParamInfo*, vector<double>&);                       // model/pressure level
    int computeLevelInfo(ParamInfo*, vector<double>&, double*, MvField&);    // model level & LNSP
    int computeLevelInfo(vector<double>&);                                   // compute equal separated levels
    void computeLevelInfoGHBC(ParamInfo*, vector<double>&);                   // model level & GHBC

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

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

    void InterpolateVerticala(vector<double>&, ParamInfo*, vector<double>&);
    void InterpolateVerticalb(MvField&, vector<double>&, ParamInfo*, double*, vector<double>&);
    void InterpolateVerticalGHBC(vector<double>&, ParamInfo*, ParamInfo*, 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 findPressure(LevelMap&, double, LevelInfo*&, LevelInfo*&);

    bool generateAverageData(ParamMap&, MvNetCDF&, const 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 height-based coordinates
    bool haveGHBC() { return haveGHBC_; }
    void haveGHBC(bool flag) { haveGHBC_ = flag; }
    void setGHBC(int iparam) { paramGHBC_ = iparam; }
    void setGHBCExtrapolate(bool b) { extrapolateGHBC_ = b; }
    bool isGHBC(int iparam) { return iparam == paramGHBC_; }
    int GHBC() { return paramGHBC_; }

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

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

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

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

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

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

private:
    double GetInterpolatedYValue(int);

    double x1_, x2_, y1_, y2_;
    double gridNS_, gridEW_;
    double topLevel_, bottomLevel_;

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

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

    bool interpolate_;  // vertical interpolation flag

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

#endif
