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

 Copyright 2012 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 <map>

#include "Factory.h"
#include "MvHovmFrame.h"
#include "MvNetCDF.h"
#include "MvFwd.h"

enum
{
    HOV_TIME,
    HOV_GEO
};  // vertical axis: time or 'second' axis
enum
{
    HOV_NS,
    HOV_EW
};  // average direction: North-South or East-West

// Type of input request to be processed
enum
{
    HOV_DATA_DROPPED,
    HOV_DATA_MODULE,
    HOV_DATA_MODULE_DROPPED
};

const std::string HOV_VARLEVEL = "nlev";
const std::string HOV_VARTIME = "time";
const std::string HOV_VARLAT = "lat";
const std::string HOV_VARLON = "lon";

class HovToolkit
{
public:
    HovToolkit() = default;
    virtual ~HovToolkit();

    HovToolkit(const HovToolkit&) = delete;
    HovToolkit& operator=(const HovToolkit&) = delete;

    // Release memory
    void ReleaseAllMemory();
    void ReleaseMemory();
    void ReleaseMemoryParam();

    // Initialise internal variables
    bool GetInputParameters(MvRequest&);
    virtual bool GetInputInfo(MvRequest&) = 0;
    bool GetInputCommonInfo(MvRequest&);  // from user interface
    virtual bool GetInputInfo(MvNetCDF*) = 0;
    bool GetInputCommonInfo(MvNetCDF*);  // from the netcdf file

    // Compute Hovmoeller diagram (main routine)
    // Create a new netcdf file
    virtual bool Compute(MvRequest&, MvRequest&);

    // Compute Hovmoeller diagram (main routine)
    // Expand a existing netcdf file
    virtual bool Compute(std::string&, MvRequest&, std::string, MvRequest&);

    // Handle creating variables, data and file
    virtual bool ComputeValues(MvField&, int = 0) = 0;
    virtual bool GenerateData(MvRequest&);
    virtual bool Initialiser();

    // Create output request using information from the application
    MvRequest CreateOutputRequest();

    // Create output request using information from the request
    MvRequest CreateOutputRequest(MvRequest&);

    // Create View request
    virtual MvRequest CreateViewRequest() = 0;

    // Initialize Param Info structure
    bool CreateParamInfo(MvRequest&);

    // Create access keys
    virtual void GenerateKey(std::string&, MvRequest&);
    void GenerateTimeKey(std::string&, MvRequest&);

    // Access time/date information
    double GetRefTime() { return refTime_; }
    void SetRefTime(double ref) { refTime_ = ref; }
    MvDate GetDate(MvField&);
    void GetComponentsFromTimeKeyf(const std::string& timekey, MvDate& date, double& step);
    double GetDateDif(const std::string&);
    std::string TimeKeyToUserString(const std::string& timekey);
    bool GetTimeInfo();
    void GetTimeInfoFromParam();

    // Handle number of points of the second coordinates
    int NPoints() { return nrPoints_; }
    void NPoints(int size) { nrPoints_ = size; }

    // Handle number of indexes
    int NrIndexes() { return nrIndexes_; }

    // Functions to handle list of levels
    virtual void InitializeLevelList() {}
    virtual void collectVerticalData(MvField&, const std::string&, double) {}
    virtual bool collectLnsp(MvRequest& /*data*/) { return false; }

    // Functions to handle the netcdf file
    bool NcAddCommonVariables();
    bool NcWriteData(const std::string& key);
    void NcAddMetadata(MvNcVar*, ParamInfo*);
    std::string getNetcdfVarname(std::string);  // Get netCDF variable name
    virtual std::string GetTitle(ParamInfo*) = 0;
    virtual const char* ApplicationType() = 0;
    virtual void NcWriteGlobalAttributes();
    virtual void NcWriteGlobalAttributesApp() = 0;
    virtual bool NcWriteTimeInfo();
    virtual bool NcWriteSecondCoord() = 0;

    // Update output matrix of values
    void NcFillValues(MvNcVar*, ParamInfo*);
    void NcWriteValues(MvNcVar*, IndexesInfo*, int);

    // Functions to handle the Expand option
    void ExpandOpenNcFile();
    void ExpandCloseNcFile();
    void ExpandFillValues(MvNcVar*, int&);
    bool ExpandFillValues(const char*, int, std::vector<double>&);
    bool ExpandGetTimeInfo();

    MvRequest GetVariableData(MvRequest&);
    void InitializeAxes(MvRequest&);

    // Functions to handle axes parameters
    virtual const char* GetSecondCoordName() = 0;  // the first axis name is "time"
    virtual const char* GetSecondAuxiliaryCoordName() { return " "; }
    virtual bool SecondAuxiliaryCoord() = 0;
    virtual bool ComputeSecondCoord() = 0;
    virtual std::string GeolineConvention() { return glConvention_; }

    // Functions to handle action mode
    void ActionMode(std::string action) { actionMode_ = action; }
    std::string ActionMode() { return actionMode_; }
    bool IsVisualise();
    bool IsDataModule(const char*);

    // Get Parameter info given the key
    ParamInfo* GetParamInfo(const std::string&);

    // Get application view
    MvRequest GetAppView(MvRequest&);

    // Check if parameters between two requests are consistent
    virtual bool consistencyCheck(MvRequest&, MvRequest&) = 0;

    // Add information to help the Macro Converter to translate
    // the output request to a Macro code
    MvRequest buildMacroConverterRequest();

    bool isParamLnsp(int paramId) const;
    bool isParamVertCoord(int paramId) const;

protected:
    enum Statistics
    {
        NoStats,
        MeanStats,
        MinStats,
        MaxStats,
        ModeStats,
        MedianStats,
        StdevStats,
        VarianceStats
    };
    static std::string statsName(Statistics s);
    static Statistics statsType(MvRequest& in);
    static Statistics statsType(const char* name);
    StatsComputePtr makeStatsCompute() const;

    bool swapAxes_{false};           // swap default axes
    int procType_{HOV_DATA_MODULE};  // process type
    int verticalAxis_{HOV_TIME};     // Time axis or Geo axis
    int nrPoints_{0};                // number of points of the second axis
    int nrIndexes_{0};               // number of points of the time axis
    double x1_{0.};
    double x2_{0.};
    double y1_{0.};
    double y2_{0.};  // geographical coordinates
    double gridNS_{1.};
    double gridEW_{1.};   // grid interval
    double refTime_{0.};  // reference Time value
    double dateMinD_{0.};
    double dateMaxD_{0.};                 // min/max date values
    std::string ncdfPath_;                // netCDF file path
    std::string actionMode_;              // "examine"/"save"/"execute"/...
    std::string glConvention_{"lonlat"};  // geoline convention: lonlat or latlon
    MvNetCDF* netcdf_{nullptr};           // netcdf structure
    MvRequest dataRequest_;               // grib data
    MvRequest origReq_;                   // original input request

    // Variables related to Param info and values
    double* xint_{nullptr};       // point values
    std::vector<double> coord1_;  // second coordinate values (lat/lng/level)
    std::vector<double> coord2_;  // second coordinate values (lat/lng)
    std::vector<double> vtime_;   // time values
    ParamMap params_;             // structure holding computed values

    // Variables related to the Expand option
    bool expand_{false};           // true: expand option
    int nrIndexesIn_{0};           // number of initial time points
    std::string netfnameIn_;       // input netcdf filename
    MvNetCDF* netcdfIn_{nullptr};  // Input netcdf structure
    static std::string moduleLabel_;

    int lnspParamId_{152};
    int vertCoordParamId_{-1};

    static std::map<std::string, Statistics> statsMap_;
    Statistics stats_{NoStats};
};
