/***************************** 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 "MvRequest.h"
#include "MvFilter.h"
#include "MvGrid.h"
#include "MvFwd.h"

//-- activate next line if you need old ml-to-pressure methods
// for-the-moment//#define INCLUDE_OLD_CODE


const double cValueNotGiven = DBL_MAX;
const int cML_UKMO_ND = 205;                //-- level type for UKMO New Dynamics data
const std::string cML_UKMO_ND_STR = "205";  //-- level type for UKMO New Dynamics data

#if 0
double interpolateValue( field* Grib, double lat, double lon );
#endif


class MvFieldSet;
class MvFieldState;
class MvGridBase;

//=============================================================================

//! Class to handle field data (stored as a GRIB message)
/*! MvField contains the physical structure of a field in GRIB format
 *  and its description as MvRequest.
 *  The class is based on libMars structure \c field, implemented in files
 *  \c libMars/field.h and \c libMars/field.c.
 */
class MvField
{
    friend class MvFieldSet;
    friend class MvFieldState;

    MvRequest Request;
    MvGridBase* mvgrid_;


public:
    //! Constructor
    MvField();

    //! Destructor
    ~MvField();

    //! Copy constructor 1
    /*! Builds a new MvField object by copying the internal pointers
     *  (GRIB data and request) from argument \c f .\n \n
     *  Note that both objects will share the same data!
     */
    MvField(const MvField& f);

    //! Copy constructor 2
    /*! Builds a new MvField object from libMars \c field struct \c f. \n \n
     *  Note that the created object will share the GRIB data with struct \c f !
     */
    MvField(field* f);

    //! Assignment operator
    /*! This operator copies the internal pointers (GRIB data and request)
     *  of object pointed by argument \c f to the other MvField
     *  in an assignment operation.\n \n
     *  Note that both objects will share the same data!
     */
    MvField& operator=(const MvField& f);

    //! Returns a pointer to the internal libMars \c field struct
    field* libmars_field() const { return mvgrid_ ? mvgrid_->field_ : 0; }

    //! Returns a pointer to the internal MvField object
    MvGridBase* mvGrid() const { return mvgrid_; }


    //! Assigns the internal \c field struct pointer to point to \c f
    void set_libmars_field(field* f);

    // Converters
    //! Returns a copy of the RETRIEVE command that describes the field contents.
    /*! Creates the request if the request has not been given in the constructor
     *  or if not already created.
     */
    MvRequest& getRequest();  // Create a MvRequest

    // Performs computations with a StatsCompute object
    double computeInArea(double north, double west, double south, double east, bool weighted, StatsComputePtr comp);
    bool computeAlong(double* n, double, double, double, double, int, int,
                      double res, bool useNearestPoint, double* coords, metview::StatsComputePtr);

    bool averageAlong(double* n, double, double, double, double, int, int,
                      double res = 0., bool useNearestPoint = true, double* coords = 0);

    //! Obsolete method. Internally calls integrate()
    double averageAt(double, double, double, double);

    //! Interpolates a value for the given geographical location
    /*! Uses the internal MvGridBase object (method MvGridBase::interpolatePoint)
     *  to do the calculation (which depend on the underlying grid
     *  presentation.\n \n
     *  Note that arguments are (longitude, latitude).
     */
    double interpolateAt(double x_lon, double y_lat);

    //! Returns the value from a grid point nearest to the given geographical location
    /*! Uses the internal MvGridBase object (method MvGridBase::nearestGridpoint)
     *  to do the search (which depends on the underlying grid presentation.\n \n
     *  Use method nearestGridPointLocation() to get the location of the nearest grid point.\n \n
     *  Note that arguments are (longitude, latitude).
     */
    double nearestGridpoint(double x_lon, double y_lat, bool nearestValid);


    //! Returns the value from a grid point nearest to the given geographical location
    /*! Same as nearestGridpoint, but we return the whole MvGridPoint.
     */
    MvGridPoint nearestGridpointInfo(double x_lon, double y_lat, bool nearestValid);


    //! (Not used. Obsolete?)
    double cornerGridpoint(double, double, int);

    //! Get a matrix of values around the given geographical location
    /*! Uses the internal MvGridBase object (method MvGridBase::getMatrixNN)
     *  to extract the requested matrix. Used mainly for satellite images.
     */
    bool getMatrixNN(double, double, MvMatrix&);

    //-- new versions --

    //! Evaluates the corresponding pressure level for the given model level \c ml
    /*! This method is for the vertical discretisation used at ECMWF, based on
     *  model level layers.
     *  The pressure level is evaluated using \c sp and the coefficient pairs ( \c C1
     *  and \c C2 ) for the two surrounding "half-levels" ( \c ml-0.5 and \c ml+0.5 ),
     *  stored in the GRIB header, using the layer mean.\n \n
     *  If the GRIB header contains only one pair of coefficients \c C1 and \c C2
     *  then method ML_to_Pressure_bySP() is called internally.
     */
    double meanML_to_Pressure_bySP(double sp, int ml);

    //! Evaluates the corresponding pressure level for a given model level \c ml
    /*! Internally converts \c lnsp to \c sp and calls method meanML_to_Pressure_bySP()
     */
    double meanML_to_Pressure_byLNSP(double lnsp, int ml);

    //! Evaluates the corresponding pressure level for the given model level \c ml
    /*! The pressure level is evaluated using \c sp and the coefficient pair \c C1
     *  and \c C2 stored in the GRIB header.
     */
    double ML_to_Pressure_bySP(double sp, int ml);

    //! Evaluates the corresponding pressure level for a given model level \c ml
    /*! Internally converts \c lnsp to \c sp and calls method ML_to_Pressure_bySP()
     */
    double ML_to_Pressure_byLNSP(double lnsp, int ml);

    //! Computes the pressure level for a single model level
    /*! Arguments \c C1 and \c C2 are the vertical coordinate coefficient pair
     *  and \c sp is the surface pressure.
     */
    double pressureLevel(double C1, double C2, double sp)
    {
        return C1 + C2 * sp;
    }

    //! Computes the pressure level for a model level layer
    double meanPressureLevel(double C1a, double C1b,
                             double C2a, double C2b, double sp)
    {
        return (C1a + C1b + (C2a + C2b) * sp) / 2.0;
    }

    //! Extracts vertical coordinate coefficient pair ( \c C1 , \c C2 ) from the GRIB header
    bool vertCoordCoefs(double& C1, double& C2);

    //! Gets a vertical coordinate coefficient pair
    /*! Vertical coordinate coefficient pair ( \c C1 , \c C2 )
     *  for level \c level is extracted from the GRIB header
     */
    bool vertCoordCoefs(int level, double& C1, double& C2);

    //! Returns the number of vertical coordinate coefficient pairs stored in the GRIB header
    int vertCoordCoefPairCount();

    //! Grid point weighted integration over the given area
    /*! Weighting is done according to the geographical area that
     *  each grid point represents, e.g. in cylindrical projection
     *  grid points closer to the Poles represent smaller areas
     *  than those closer to the Equator
     */
    double integrate(double north, double west, double south, double east);

    //! Grid point weighted standard deviation calculated over the given area
    /*! About the weighting used, see integrate().
     */
    double stdev(double north, double west, double south, double east);

    //! Grid point weighted covariance over the given area
    /*! About the weighting used, see integrate().
     */
    double covar(MvField& other, double north, double west, double south, double east);

    //! Returns the offset of the GRIB message
    /*! This is the offset from the beginning of the input file, in bytes
     */
    long getOffset()
    {
        if (libmars_field())
            return (long)libmars_field()->offset;
        else
            return -1;
    }

    //! Returns the length of the GRIB message, in octets (bytes)
    long getLength()
    {
        if (libmars_field())
            return libmars_field()->length;
        else
            return -1;
    }

    // Data access, field should be expanded
    // inline for speed
    //! Returns the number of grid points (or spectral coefficients) in the field
    int countValues() { return libmars_field()->value_count; }

    //! Returns the value of the \c n'th element in the field
    /*! Element type depends on the field presentation:
     *  it can be a value of a grid point or of a spectral coefficient.\n
     *  For the first element \c n = 0 ( \c n = 0, 1, 2, ...)
     */
    double& operator[](int n) { return libmars_field()->values[n]; }

    //! Returns \c true if the GRIB message contains a missing field
    bool isMissing() { return libmars_field()->missing; }

    //! Returns \c true if the grid presentation is cylindrical latitude-longitude grid
    bool isLatLon();

    //! Returns the number of grid points in latitude (j) direction
    int numberOfLat();

    //! Returns the number of grid points in longitude (i) direction
    int numberOfLon();

    //! Returns the Northern coordinate of the grid area
    double north();

    //! Returns the Southern coordinate of the grid area
    double south();

    //! Returns the Eastern coordinate of the grid area
    double east();

    //! Returns the Western coordinate of the grid area
    double west();

    //! Returns the grid interval in j direction (North-South)
    double gridNS();

    //! Returns the grid interval in i direction (West-East)
    double gridWE();

    //! Returns \c true if the field is derived, \c false if retrieved
    /*! Metview and MARS mark derived (computed) fields with a special
     *  value in generatingProcess() octet.\n \n
     *  In Metview visualisation derived fields
     *  are indicated by an extra asterisk in the parameter name
     *  in the plot title, and some fields get different unit conversions.
     *  For instance retrieved temperature field values (stored in Kelvin
     *  in GRIB message) are converted (by default) to Celsius for
     *  plotting, but a computed temperature difference field is plotted
     *  (by default) as is, without conversion.
     */
    bool isDerived();

    //! Returns the GRIB Edition number of the current GRIB message
    int edition() { return mvgrid_->getLong("editionNumber"); }

    //! Returns the code number for 'creating centre' (e.g. 98 is ECMWF)
    int centre();

    //! Returns the code number for the parameter; the actual parameter depends also on table()
    double parameter();

    //! Returns the parameter name used by MARS
    std::string parameterName();

    //! Assigns a different parameter code number
    void setParameter(int v) { mvgrid_->setLong("indicatorOfParameter", v); }

    //! Returns (GRIB 1) Table 2 version number
    int table();

    //! Assigns a different Table 2 version number
    void setTable(int v) { mvgrid_->setLong("gribTablesVersionNo", v); }

    //! Returns the code number for the 'generating process'
    /*! This code is also used by Metview/MARS to mark a field
     *  as 'computed', see isDerived().
     */
    int generatingProcess();

    //! Returns the primary level value (octet 11, or combined octets 11&12 in GRIB 1)
    double level();

    //! Returns the second level value (octet 12 in GRIB 1)
    double level_L2();

    //! Returns the type of the level as a string
    std::string levelTypeString();

    //! Returns the code number for the level type
    int levelType();

    //! Returns the code number for the underlying geographical grid
    int dataRepres();

    //! Returns the parameter name used by MARS
    Cached marsName();  //-- from Emoslib Table 2 versions
    // Cached  fullName_DeleteMe();   //-- from Emoslib Table 2 versions

    //! Returns the parameter name used by Magics 6
    Cached magicsName();  //-- from Magics Table 2 versions


    // OBSOLETE: Remove later
    //! Returns the pairing parameter for vector parameters, as a string
    /*! Method searches Magics 6 Extended GRIB Table 2 files for the current
     *  parameter, to see if it is part of a vector pair (u/v, speed/direction,
     *  vorticity/divergence). If this is a part of a vector, then returns
     *  the other parameter code in format \c "ppp.tt" , where \c ppp is the code
     *  for the parameter in Table 2 version \c tt. \n \n
     *  Returns \c "no_pair" if this is a scalar parameter.
     */
    //	Cached  vectorPair(); //-- from Magics Table 2 versions

    //! [Obsolete!]] Returns \c true if the GRIB message has been fully expanded into memory
    /*! Expanded state was a requirement for the obsolete GRIBEX based implementation.
     *  With the current ecCodes implementation messages do not need to be
     *  expanded in order to have access to data values, thus calling this
     *  calling this function is obsolete.
     */
    bool isExpanded() { return mvgrid_->field_->shape == expand_mem; }

    //! This method reads the GRIB message into memory, in case it is not already there
    void ensurePackedMem();  //-- if only on disk, get into memory

    //! This method sets the 'shape' (see libMars) of the field
    void setShape(field_state newState) { set_field_state(mvgrid_->field_, newState); }

    //! Interface to ecCodes to request a \c long valued keyword
    long getGribKeyValueLong(const std::string& key, bool throwOnError = false, bool quiet = false)
    {
        return mvgrid_->getLong(key.c_str(), throwOnError, quiet);
    }

    //! Interface to ecCodes to request a \c double valued keyword
    double getGribKeyValueDouble(const std::string& key, bool throwOnError = false, bool quiet = false)
    {
        return mvgrid_->getDouble(key.c_str(), throwOnError, quiet);
    }

    //! Interface to ecCodes to request a \c string valued keyword
    std::string getGribKeyValueString(const std::string& key, bool throwOnError = false, bool quiet = false)
    {
        return mvgrid_->getString(key.c_str(), throwOnError, quiet);
    }

    //! Interface to ecCodes to request a \c long valued array keyword
    long getGribKeyArrayLong(const std::string& key, long** lvals, bool throwOnError = false, bool quiet = false)
    {
        return mvgrid_->getLongArray(key.c_str(), lvals, throwOnError, quiet);
    }

    //! Interface to ecCodes to request a \c double valued array keyword
    long getGribKeyArrayDouble(const std::string& key, double** dvals, bool throwOnError = false, bool quiet = false)
    {
        return mvgrid_->getDoubleArray(key.c_str(), dvals, throwOnError, quiet);
    }

    //! Interface to ecCodes to set a \c long valued keyword
    bool setGribKeyValueLong(const std::string& key, long value)
    {
        return mvgrid_->setLong(key.c_str(), value);
    }

    //! Interface to ecCodes to set a \c double valued keyword
    bool setGribKeyValueDouble(const std::string& key, double value)
    {
        return mvgrid_->setDouble(key.c_str(), value);
    }

    //! Interface to ecCodes to set a \c string valued keyword
    bool setGribKeyValueString(const std::string& key, std::string& value)
    {
        return mvgrid_->setString(key.c_str(), value);
    }

    //! Returns \c true if the level type is model level
    bool isModelLevel();

    //! Returns \c true if the level type is surface level
    bool isSurface();

    //! Returns \c true if the level type is pressure level
    bool isPressureLevel();

    //! Returns \c true if the level type is depth level (e.g. ocean, soil)
    bool isDepth();

    // Compare with nullptr
    //! Operator for testing the validity of MvField object
    /*! This operator returns the pointer of the internal libMars
     *  GRIB structure as a \c void* .
     *  It is useful to compare the contents of a MvField to nullptr
     *  in a \c while or \c if statements.\n \n
     *  Usage:
     * <PRE>
     *      MvField f;
     *      if ( f ) ...
     * </PRE>
     */
    operator void*();

    //! Operator for testing the validity of MvField object
    /*! This operator compares the pointer of the internal libMars
     *  GRIB structure to nullptr and returns the result.\n \n
     *  Usage:
     * <PRE>
     *       MvField f;
     *       if ( !f ) ...
     * </PRE>
     */
    int operator!();

    // Clone the data...

    //	void clone(); //-- 080724/vk: this method was never implemented?


    //	fortfloat* coordinateLevelsA();
    //		{ return &Grib->rsec2[10]; }
    //
    //	fortfloat* coordinateLevelsB()
    //		{ return coordinateLevelsA() + coordinateLevelsCount(); }

    //	int coordinateLevelsCount();
    //		{ return Grib->ksec2[11]/2;}

    //! Returns the location of the nearest grid point for the given geographical location
    /*! Use method nearestGridpoint() to get the value of the grid point
     */
    MvLocation nearestGridPointLocation(const MvLocation& l);

    //! Returns the data date-time as a double number
    /*! Integer part shows \c yyyymmdd and the decimal part shows the hours
     *  as a fraction of a day. For instance 20080724.5 represents 12:00 UTC
     *  on 24 July 2008, and 20070620.75 represents 18:00 UTC on 20 June 2007.
     */
    double yyyymmddFoh();  // return date: yyyymmdd.f, f=fraction of hour

    //! Returns the validity date-time as a double number
    /*! Integer part shows yyyymmdd and the decimal part shows the hours
     *  as a fraction of a day. For instance 20080724.5 represents 12:00 UTC
     *  on 24 July 2008, and 20070620.75 represents 18:00 UTC on 20 June 2007.*/
    double validityDateTime();

    //! Returns the forecast step in fractions of a day.
    /*! For instance 0.25 represents a forecast of 6 hours,
     *  and 2.5 represents a forecast of 60 hours.
     */
    double stepFoh();  // return step in fraction of hour

    //! Returns the step number from a step string.
    /*! Normally the two are basically the same (e.g. '72' -> 72), but
     *  in some cases the step is a range (e.g. '96-120').
     *  When we used grib_api <= 1.7.0, mars.step (string) was returning the 'endStep'
     *  parameter (in this case 120). However, with grib_api 1.8.0, mars.step
     *  returns the whole range. In this case, Metview was 'unthinkingly' converting
     *  to a number, and the result was the first in the range (in this case 96).
     *  The solution, it would appear, is to scan for a range and if so, then take
     * the second number. This will at least give the same results as previous versions.
     */
    static int stepRangeDecoder(const char* stepString);


    //! Returns the embedded grib_handle
    /*! This method is for "lower" level GRIB access, for cases
     *  not available in MvField class
     */
    grib_handle* getGribHandle() { return mvgrid_->field_->handle; }

    //! Returns a list of locations (lat/long pairs), where the values of the input field
    //! are within the interval [a, b]. This function is similar to Macro function Find.
    int find(double min, double max, std::vector<double>& lat, std::vector<double>& lon);

    void setValueAtToMissing(int index) { mvgrid_->setValueAtToMissing(index); }
    bool isSpectral();
    long pressureLevelInPa();
};

// Stack based objects to change the state of gribs ....
// This is a meta-class
//! Base class for MvFieldLoader and MvFieldExpander
/*! These classes are now obsolete!
 *  They were needed when GRIB handling was done with the
 *  obsolete \c GRIBEX routine. The porting of Metview code
 *  to use \c ecCodes has removed the requirement
 *  to expand GRIB messages as \c ecCodes provides
 *  access to data and meta-data without the need to
 *  explicitly expand the message.
 */
class MvFieldState
{
protected:
    MvFieldState(const MvField&, field_state, field_state state_to_revert_to = unknown, bool readonlyflag = true);
    virtual ~MvFieldState();

private:
    MvField* Field{nullptr};
    field_state OldState{unknown};
    bool readonly{true};
};

// ... this one load a field from file into memory, still in grib

//! Loads a field (a GRIB message) from file to memory
/*! This class is now semi-obsolete (see MvFieldState).
 *  It is currently (July 2008) used in one Metview module
 */
class MvFieldLoader : public MvFieldState
{
public:
    MvFieldLoader(const MvField& f, bool readonlyflag = true) :
        MvFieldState(f, packed_mem, unknown, readonlyflag) {}
};

// ... this one expand the grib

//! Expands (decodes) a field (a GRIB message)
/*! This class is now semi-obsolete (see MvFieldState), although
 *  it is still (July 2008) used in several Metview modules
 */
class MvFieldExpander : public MvFieldState
{
public:
    MvFieldExpander(const MvField& f, bool readonlyflag = true) :
        MvFieldState(f, expand_mem, unknown, readonlyflag) {}
};


// ... this one expands the grib and then frees it when destroyed

//! Expands (decodes) a field (a GRIB message) and frees is when this pbject is destroyed
/*! This class is now semi-obsolete (see MvFieldState), although
 *  it is still (July 2008) used in several Metview modules
 */
class MvFieldExpandThenFree : public MvFieldState
{
public:
    MvFieldExpandThenFree(const MvField& f, bool readonlyflag = true) :
        MvFieldState(f, expand_mem, packed_file, readonlyflag) {}
};

//===========================================================================

//! MvFieldSet contains a set of MvField objects
/*! This class is based on libMars structure \c fieldset, found
 *  in files \c field.h and \c field.c in directory src/libMars.
 */
class MvFieldSet
{
    bool Writable;
    fieldset* Fs;
    MvField** Fields;

    // Private stuff

    void _init();
    void _clean();
    void _copy(const MvFieldSet&);

    // No copy allowed

    void operator=(const MvFieldSet&);
    MvFieldSet(const MvFieldSet&);

public:
    //! Constructor, creates an empty fieldset
    MvFieldSet();  // Create an empty fs

    //! Constructor, reads fields from a file
    /*! Reads the GRIB data file pointed by argument \c filename
     *  and builds the fieldset that contains all the fields in the file.
     */
    MvFieldSet(const char* filename);  // Contruction from a file name

    //! Constructor, creates a fieldset from a request
    /*! Builds a fieldset from a GRIB command.
     *  This command specifies the file name in parameter PATH.
     *  The order of the fields within the file may be specified
     *  by parameter INDEX or by parameters OFFSET and LENGTH.
     */
    MvFieldSet(const MvRequest& req);  // Contruction from a request

    //! Destructor
    ~MvFieldSet();


    // Utility methods

    //! Returns the number of fields in the fieldset
    int countFields(void);  // number of fields

    //! Returns one field from the fieldset
    /*! Argument \c n defines which field to return.
     *  First field is returned when \c n = 0 (\c n = 0, 1, 2,...).
     */
    MvField& operator[](int);  // Return a single field

    //! Returns a request describing the fieldset
    /*! Returns a GRIB command that describes the position of the fields
     *  within the file. The GRIB command contains the parameters:
     * <PRE>
     *      PATH - file name.
     *      TEMPORARY - 0 or 1. When 1, file may be deleted
     *      OFFSET - byte offset of field within file.
     *      LENGTH - size of field in bytes.
     * </PRE>
     */
    MvRequest getRequest();  // Create a MvRequest
                             //	MvRequest getRequest(bool clone=false); // original had an argument which is never used

    //! Appends field \c f to the fieldset
    void operator+=(const MvField& f);  // Append a field
};

//=============================================================================

//! Allows changing the accessing order of MvField objects in MvFieldSet
/*! This class can be used to change the accessing order of fields in MvFieldSet.
 *  Fields can be sorted and - using one MvFilter object - filtered.\n \n
 *  Sorting and filtering are done inside this class (MvFieldSet object
 *  itself is not changed). Fields are accessed using the iterator operator()().
 */
class MvFieldSetIterator
{
    int Count;
    int Current;
    int* Order;

    MvFieldSet* FieldSet;
    MvFilter Filter;

public:
    //! Constructor that associates fieldset \c fs to the iterator
    MvFieldSetIterator(MvFieldSet& fs);

    //! Destructor
    ~MvFieldSetIterator();

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

    //! Method that associates MvFilter \c f to the iterator.
    /*! Only fields that satisfy the filter conditions will be
     *  returned by the iterator
     */
    void setFilter(const MvFilter& f);

    //! This method specifies the sorting criteria to be used when accessing the fieldset
    /*! Argument \c name defines which parameter of the field description command
     *  will be used as a sorting key.
     *  Argument \c ord specifies if fields will be sorted in
     *  ascending ('<') or descending ('>') order.
     *  When more than one parameter are defined as sorting key,
     *  the first one varies more quickly
     */
    void sort(const char* name, char ord = '<');  //  '<' = ascending, '>' = descending

    //! Returns the next iterated field
    /*! This operator sequentially returns a MvField from a MvFieldSet
     *  in the order specified by method sort() and - if used - MvFilter object.\n \n
     *  Usage:\n
     * <PRE>
     *  MvRequest  grib;             // a GRIB command
     *  MvFieldSet fs(grib);         // construct field set
     *
     *  MvFieldSetIterator iter(fs); // define an iterator
     *  iter.sort("STEP,'>'");       // sort STEP in descending order
     *  iter.sort("DATE");           // sort DATE in ascending order
     *
     *  MvFilter level("LEVEL");
     *  MvFilter param("PARAM");
     *
     *  iter.setFilter(param==129 && level>5 && level<=20);
     *
     *  MvField field;
     *
     *  while(field = iter())
     *  {
     *  	// fields that have PARAM=129 and LEVEL in
     *  	// the range [6...20] may be accessed here
     *  }
     * </PRE>
     */
    MvField& operator()();  // returns NULL at end of list

    //! Rewinds the iterator
    void rewind() { Current = 0; }
};
