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

// MvGrid.h,    vk Aug-98

#ifndef MvGrid_DEFINED
#define MvGrid_DEFINED

#include <float.h>

#include "mars.h"
#include <string>
using std::string;

#include "MvLocation.h"
#include "MvMatrix.h"

const string cLatLonGrid         ("regular_ll");
const string cLatLonRotatedGrid  ("rotated_ll");
const string cLatLonReducedGrid  ("reduced_ll");
const string cGaussianGrid       ("regular_gg");
const string cGaussianReducedGrid("reduced_gg");
const string cPolarStereoGrid    ("polar_stereographic");
const string cLambertGrid        ("lambert");
const string cLambertAzEqGrid    ("lambert_azimuthal_equal_area");
const string cMercatorGrid       ("mercator");
const string cSatelliteImage     ("space_view");

const double cGridScaling      = 1000.0;
//const int    cLevelNotGiven    = -999;


//-- temporary functions:
void grib_api_port_missing( const char* msg );
void nontested_grib_api_port( const char* msg );


class        MvGridBase;
//! Function to create an instance of a suitable MvGrid object.
MvGridBase*  MvGridFactory( field* myfield,
                            bool memoryToBeReleased = true,
                            bool expandGrid         = true);


//===================================================================
//
// MvGridPoint:
// -----------
//! \brief Utility class (struct) for a single grid point
//!
//! A utility class to hold the value and the coordinates of
//! a single grid point (or a location)
//!

struct MvGridPoint
{
    MvGridPoint( double value, double lat_y, double lon_x, long index=-1 )
               : value_( value ), loc_( lat_y, lon_x ), index_(index) { }
    MvGridPoint( double value, const MvLocation& loc, long index=-1 )
               : value_( value ), loc_( loc ), index_(index) { }
    MvGridPoint()
               : value_( mars.grib_missing_value ), loc_( 0, 0 ), index_(-1) { }

    double     value_;
    MvLocation loc_;
    long       index_;
};

const double      cMvGridMissingValue = DBL_MAX; // mars.grib_missing_value;
const double      cMvlatLonMissingValue = -999.9; // mars.grib_missing_value;
const MvGridPoint cMissingPoint( cMvGridMissingValue, cMvlatLonMissingValue, cMvlatLonMissingValue );

//! Class to handle rectangular areas
/*! Metview has "an Area Problem" i.e. areas are defined differently
 *  for PlotWindow and for Macros. This class accepts geographical
 *  areas defined in either form:
 * <PRE>
 *     (1) [upper left corner]-[lower right corner] i.e. [n,w,s,e] (Macro)
 *  or:
 *     (2) [lower left corner]-[upper right corner] i.e. [s,w,n,e] (Window)
 * </PRE>
 *  where one corner value is a [latitude,longitude] pair.
 *
 *  Class asserts that internally:
 * <PRE>
 *     o  North >= South (North and South values swapped if (2) used)
 *     o   East >= West  (never swapped, only normalized)
 * </PRE>
 *  "East >= West" relation is guaranteed by normalising the values:
 * <PRE>
 *     o     0 <  East <= 360
 *     o  -360 <= West <= East
 *     o   (East-West) <= 360
 * </PRE>
 *  Prerequisite:\n
 *  Class is designed to work properly with input argument ranges:
 * <PRE>
 *     o  -360 <= longitude <= +360
 *     o   -90 <= latitude  <=  +90.
 * </PRE>
 */
class MvGeoBox
{
 public:
	//! Constructor with area fully defined
	MvGeoBox( double north, double west, double south, double east )
	      { set(north,west,south,east); }

	//! Constructor that defines a belt between the given latitudes
	MvGeoBox( double north, double south ){ set(north,0,south,360); }

	//! Default constructor, area covers the whole globe
	MvGeoBox(){ set(90,0,-90,360); }

	//! Sets a new area
	void set( double north, double west, double south, double east );

	//! Tests if the given point is inside the area
	/*! The longitude argument \c lon_x is normalised before testing,
	 *  trying to fit it between the normalised East-West values.
	 */
	int  isInside( double lat_y, double lon_x ) const;

 private:
	double n, w, s, e;
};

// Backwards compatibility: MvGeoBox was previously called just GeoBox
typedef MvGeoBox GeoBox;


//===================================================================
//
// MvGridBase:
// ----------
//! Abstract base class for different grid representations
/*! Classes have methods to traverse grid points consequtively.
 *  For each valid grid point its value, location and non-normalised
 *  weight can be requested.
 */

class MvGridBase
{
	friend class MvField;

 public:
	MvGridBase( field* myfield, field_state oldState, bool memoryToBeReleased );
	virtual ~MvGridBase();

	//! Returns the \c index:th value
	double  operator[](int index);

	//! Initialises the iterator
	virtual void    init();

	//! Returns the longitude value of the first grid point
	virtual double  firstLonX() = 0;

	//! Returns the latitude value of the first grid point
	virtual double  firstLatY() = 0;

	//! Advances the iterator to the next grid point
	/*! Returns \c false if no more grid points left
	 */
	virtual bool    advance() = 0;

	//! Returns the relative area weight of the grid point [iterator]
	virtual double  weight() const;

	//! Returns the nearest grid point value and location
	virtual MvGridPoint nearestGridpoint( double lat_y, double lon_x );

	//! Returns the value interpolated into the given point
	virtual double      interpolatePoint( double lat_y, double lon_x );

	//! Returns a matrix around the given point
	virtual bool getMatrixNN( double lat_y, double lon_x, MvMatrix& mat);

	//! Returns \c true if the underlying grid is geographical and implemented
	/*! Returns \c false for spectral data and for unimplemented grids
	 */
	virtual bool hasLocationInfo() const { return true; }

	//! Returns \c true if the underlying grid has at least the iterator methods implemented
	/*! Returns \c false for spectral data and for unimplemented grids
	 */
	virtual bool hasIterator() const { return true; }


	//! Returns \c true when the object contains a valid field descriptor
	bool  isValid() const { return field_ != 0; }

	//! Returns \c true when the current grid is similar to \c otherGrib
	/*! Returns \c false if grids contain different number of points
	 *  or grid scanning modes are different
	 */
	bool  isEqual( const MvGridBase* otherGrib ) const;
	//-- isSimilar() to be removed (011025/vk) --//
//	bool   isSimilar( const MvGridBase* g ) const { return isEqual(g); }

	//! Returns the grid type as a string
	string gridType() const { return gridType_; }

   //! Check grid type (implement for the other grids, if it is needed)
   bool isRegularLatLongGrid()
      { return gridType_ == cLatLonGrid; }

	//! Returns the value of the current grid point [iterator]
	virtual double value() const
	         { return valueAt(currentIndex_); }
//	         { return field_ ? (double)(field_->values[ currentIndex_ ]) : mars.grib_missing_value; }

	//! Assigns \c xx to the value of the current grid point [iterator]
	void   value( double xx ) { if ( field_ ) field_->values[ currentIndex_] = xx; }

	//! Returns the value of the \c i:th grid point
	double valueAt( int i ) const;
//	double valueAt( int i ) const
//	         { return field_ ? (double)(field_->values[ i ]) : mars.grib_missing_value; }

	//! Assigns \c xx to the value of the \c i:th grid point
	void   valueAt( int i, double xx ) { if ( field_ ) field_->values[ i ] = xx; }

	//! Returns \c true when the grid point has a value
	/*! Returns \c false when the grid point is missing its value
	 */
	bool   hasValue() const { return value() != mars.grib_missing_value; }

	//! Returns the code used for representing missing values
	double missingValue() const { return mars.grib_missing_value; }

	//! Returns the latitude of the current grid point [iterator]
	virtual double  lat_y() const { return currentLaty_; }

	//! Returns the longitude of the current grid point [iterator]
	virtual double  lon_x() const { return currentLonx_; }

	//! Returns the number of data values in the GRIB message
	/*! This is either the number of grid points (for geographical
	 *  grids) or the number spectral coefficient (spherical harmonics)
	 */
	long        length() const { return field_ ? (long)(field_->value_count) : 0; }

	//! Returns the value and the location of the current grid point [iterator]
	MvGridPoint gridPoint() const { return MvGridPoint( value(), lat_y(), lon_x(), currentIndex_ ); }

	virtual vector<double> averageCalc(bool ew,double N,double W,double S,double E,double gridInterval);

	long   getLong( const char* name, bool throwOnError = false ) const;
	double getDouble( const char* name, bool throwOnError = false ) const;
	string getString( const char* name, bool throwOnError = false ) const;

	long   getLongArray   ( const char* name, long **lvals, bool throwOnError = false ) const;
	long   getDoubleArray ( const char* name, double **lvals, bool throwOnError = false ) const;


	bool   setLong  ( const char* name, long val );
	bool   setDouble( const char* name, double val );
	bool   setString( const char* name, string &val );

	long   vertCoordCoefPairCount();
	bool   vertCoordCoefs( double& C1, double& C2, int level );

	field* fieldPtr(){ return field_; }

 protected:
	bool  ScanModeCheck( double first, double last, double step ) const;

 protected:
	field*   field_;
        double*  vertCoord_;
        long     vertCoordPairCount_;
	string   gridType_;
	long  horisLines_;       //-- points_meridian
	long  horisPoints_;      //-- points_parallel
	int      horisPointCount_;
	long     currentIndex_;
	double   currentLaty_;
	double   currentLonx_;
	double   dx_;
	field_state  oldState_;
	bool         memoryToBeReleased_;
};

//===================================================================
//
// MvUnimplementedGrid:
// -------------------
//
//! \brief MvGrid class for unimplemented grids
//!
//! This class is for unimplemented grids.
//!
//!  Used by MvGridFactory to return a valid ptr, always, i.e.
//!  applications can safely use the ptr. (Method 'isValid()'
//!  can be used to check if the grid behind the ptr has been
//!  properly implemented.)
//!

class MvUnimplementedGrid : public MvGridBase
{
 public:
	MvUnimplementedGrid( field* myfield, field_state oldState );
	~MvUnimplementedGrid(){}

	void    init();
	bool    advance();
	double  firstLonX(){ return cMvlatLonMissingValue;}
	double  firstLatY(){ return cMvlatLonMissingValue;}
	bool    hasLocationInfo() const { return false; }
	bool    hasIterator() const { return false; }
	double  weight() const { return 1;}
	MvGridPoint nearestGridpoint( double, double )
	  { return cMissingPoint; }
	double      interpolatePoint( double, double )
	  { return DBL_MAX; }
	vector<double> averageCalc(bool ew,double N,double W,double S,double E,double grid);
};



//===================================================================
//
// MvGridUsingGribIterator:
// ------------------------
//
//! \brief MvGrid base class for grids that use the ecCodes GRIB iterator
//!
//! This base class is for grids that use the ecCodes GRIB iterator
//!

class MvGridUsingGribIterator : public MvGridBase
{
 public:
	MvGridUsingGribIterator( field* myfield
			  , field_state oldState
			  , bool memoryToBeReleased
	                );

	~MvGridUsingGribIterator();

	bool    advance();

	//! We may/may not have the iterator for this projection
	bool hasIterator() const { return (iter_ != NULL); }

	double value() const;

private:
	grib_iterator*     iter_;
	double             currentVal_;
};


//===================================================================
//
// MvLatLonGrid:
// ------------
//! \brief MvGrid class for fields in latitude/longitude grid
//!
//! This class is for fields in normal latitude/longitude grid
//!

class MvLatLonGrid : public MvGridBase
{
 public:
	MvLatLonGrid( field*      myfield
                    , field_state oldState
		    , bool        memoryToBeReleased
		    , bool        isRotated = false );
	~MvLatLonGrid(){}

	virtual double  firstLonX();
	virtual double  firstLatY();
	virtual double  lastLonX();
	virtual double  lastLatY();
	virtual bool    advance();
	virtual MvGridPoint nearestGridpoint( double lat_y, double lon_x );
	virtual double      interpolatePoint( double lat_y, double lon_x );

 protected:
	//gribsec2_ll* sec2_;

 private:
	double       dy_;
	double       firstLonX_;
	double       firstLatY_;
	double       lastLonX_;
	double       lastLatY_;
};

//===================================================================
//
// MvLatLonRotatedGrid:
// -------------------
//! \brief MvGrid class for fields in rotated latitude/longitude grid
//!
//! This class is for fields in rotated latitude/longitude grid
//!

class MvLatLonRotatedGrid : public MvLatLonGrid
{
 public:
	MvLatLonRotatedGrid( field* myfield, field_state oldState, bool memoryToBeReleased );
	~MvLatLonRotatedGrid(){}

	virtual double  lat_y() const
	 { return unRotate( MvLatLonGrid::lat_y(), MvLatLonGrid::lon_x() ).latitude(); }
	virtual double  lon_x() const
	 { return unRotate( MvLatLonGrid::lat_y(), MvLatLonGrid::lon_x() ).longitude(); }

	virtual MvGridPoint nearestGridpoint( double lat_y, double lon_x );
	virtual double      interpolatePoint( double lat_y, double lon_x );

 protected:
	MvLocation   unRotate() const { return unRotate( currentLaty_, currentLonx_ ); }
	MvLocation   unRotate( double laty, double lonx) const;
	MvLocation   rotate( double laty, double lonx) const;

 private:
	double       southPoleLaty_;
	double       southPoleLonx_;
	// double       southPoleRotation_;
};

//===================================================================
//
// MvLambertGrid:
// -------------
//! \brief MvGrid class for fields in Lambert grid
//!
//! This class is for fields in Lambert grid. Member functions nearestGridpoint()
//! and interpolatePoint() have not been implemented for this class and
//! thus the methods in the base class are used (these methods do not try
//! to calculate which grid point is the right one, but instead go through
//! all points in order to find the closest one).
//!

class MvLambertGrid : public MvGridBase
{
 public:
	MvLambertGrid( field*      myfield
                     , field_state oldState
		     , bool        memoryToBeReleased );
	~MvLambertGrid(){}

	virtual double  firstLonX();
	virtual double  firstLatY();
	virtual bool    advance();
//	virtual MvGridPoint nearestGridpoint( double lat_y, double lon_x );
//	virtual double      interpolatePoint( double lat_y, double lon_x );
	virtual double  weight() const;

 protected:
	bool  computeLatLon( int ip, int jp );

 protected:
//	fortint*     sec2_;

 private:
	//double       dy_;
	double       gridLat1_;
	double       gridLon1_;
	double       gridVertLon_;
	double       gridTanLat_;
	double       sinLatTan_;
	double       sinLatTanInvPer2_;
	double       cosLatTan_;
	double       hemiSphere_;
	double       poleI_;
	double       poleJ_;
	double       earthRadius_;
	double       earthRadiusPerDx_;
	double       theThing_;
	int          currentI_;
	int          currentJ_;
	bool         jPositive_;
};


//===================================================================
//
// MvLambertAzimuthalEqualAreaGrid:
// -------------
//! \brief MvGrid class for fields in Lambert Azimuthal Equal Area grid
//!
//! This class is for fields in Lambert Azimuthal Equal Area grid.
//! It pretty much just does what the MvGridUsingGribIterator class does.

class MvLambertAzimuthalEqualAreaGrid : public MvGridUsingGribIterator
{
 public:
	MvLambertAzimuthalEqualAreaGrid( field*      myfield
                                   , field_state oldState
                                   , bool        memoryToBeReleased );
	~MvLambertAzimuthalEqualAreaGrid(){}

	//! Dummy function. Returns always missing_value.
	double  firstLonX(){ return cMvlatLonMissingValue;}

	//! Dummy function. Returns always missing_value.
	double  firstLatY(){ return cMvlatLonMissingValue;}
};



//===================================================================
//
// MvIrregularGrid:
// ------------------
//! \brief MvGrid parent class for fields in Gaussian and reduced_latlong grid 
//!
//! An abstract parent class for fields in Gaussian and reduced_latlong grid.
//!

class MvIrregularGrid : public MvGridBase
{
 public:
	MvIrregularGrid( field* myfield, field_state oldState, bool memoryToBeReleased );
	~MvIrregularGrid();

	virtual void    init();
	virtual double  firstLonX();
	virtual double  firstLatY();
	virtual double  firstLonXInArea(int row);
	virtual double  lastLonXInArea(int row);
	virtual MvGridPoint nearestGridpoint( double lat_y, double lon_x );
	virtual double      interpolatePoint( double lat_y, double lon_x );
	virtual int     pointsInRow( int row ) = 0;
	bool            globalNS_;
	bool            globalEW_;
	virtual double  extrapolatePoint( double lat_y, double lon_x ) = 0;
	double          lastLonX();

 protected:
	void         checkAreaLimits();
	int          findLatIndex( double latitude );

 protected:
//	gribsec2_gg* sec2_;
	long         numGlobalParallels_;
	double*      latitudes_;
	int          firstLatIndex_;
	int          lastLatIndex_;
	int          currentLatIndex_;
	bool         isSouthToNorthScanning_;
	double       firstLonX_;
	double       lastLonX_;
};



//===================================================================
//
// MvReducedLatLongGrid:
// ---------------------
//! \brief MvGrid class for fields in reduced lat/long grid
//!
//! The number of points on each parallel is taken from
//! the GRIB message.
//!

class MvReducedLatLongGrid : public MvIrregularGrid
{
 public:
	MvReducedLatLongGrid( field* myfield, field_state oldState, bool memoryToBeReleased );
	~MvReducedLatLongGrid();

	virtual bool    advance();
	virtual int     pointsInRow( int row );
	virtual double  extrapolatePoint( double, double )
		{ return DBL_MAX; }

 protected:
	long*           pointsInRow_;
};



//===================================================================
//
// GaussianLatitudes:
// ------------------
//! \brief Retrieves, caches and returns the results of grib_get_gaussian_latitudes()


class GaussianLatitudes
{
 public:
 	GaussianLatitudes() {};
	~GaussianLatitudes() {};
	int latitudes(long trunc, double *v, unsigned int numLatitudes);

 private:
	map<long, vector<double> > cachedLatitudes_;
 
};



//===================================================================
//
// MvGaussianGridBase:
// ------------------
//! \brief MvGrid parent class for fields in Gaussian grid
//!
//! An abstract parent class for fields in Gaussian grid.
//!

class MvGaussianGridBase : public MvIrregularGrid
{
 public:
	MvGaussianGridBase( field* myfield, field_state oldState, bool memoryToBeReleased );
	~MvGaussianGridBase() { };

	virtual double extrapolatePoint( double lat_y, double lon_x );
	static GaussianLatitudes gLatitudes_;
};

//===================================================================
//
// MvGaussianGrid:
// --------------
//! \brief MvGrid class for fields in Gaussian grid
//!
//! This class is for fields in non-reduced Gaussian grid.
//!

class MvGaussianGrid : public MvGaussianGridBase
{
 public:
    MvGaussianGrid( field* myfield, field_state oldState, bool memoryToBeReleased );
    ~MvGaussianGrid(){}

	virtual bool    advance();
	virtual int     pointsInRow( int row );
};

//===================================================================
//
// MvReducedGaussianGrid:
// ---------------------
//! \brief MvGrid class for fields in reduced Gaussian grid
//!
//! The number of points on each parallel is taken from
//! the GRIB message.
//!

class MvReducedGaussianGrid : public MvGaussianGridBase
{
 public:
	MvReducedGaussianGrid( field* myfield, field_state oldState, bool memoryToBeReleased );
	~MvReducedGaussianGrid(){ delete [] pointsInRow_; }

	virtual double  firstLonXInArea(int row);
	virtual double  lastLonXInArea(int row);
	virtual bool    advance();
	virtual int     pointsInRow( int row );

 protected:
	long*           pointsInRow_;
};

//===================================================================
//
// MvSatelliteImage:
// -------------------
//! \brief MvGrid class for Satellite images
//!
//! Note that some member functions have not been implemented.
//!

class MvSatelliteImage : public MvGridUsingGribIterator
{
 public:
	MvSatelliteImage( field* myfield
			  , field_state oldState
			  , bool memoryToBeReleased
	                );

	~MvSatelliteImage();

	//! Dummy function. Returns always missing_value.
	double  firstLonX(){ return cMvlatLonMissingValue;}

	//! Dummy function. Returns always missing_value.
	double  firstLatY(){ return cMvlatLonMissingValue;}

	//! Dummy function. Returns always 1.
	double  weight() const { return 1; }

	MvGridPoint nearestGridpoint( double, double );
	double      interpolatePoint( double, double );
	bool getMatrixNN( double lat_y, double lon_x, MvMatrix& mat);

	//! Returns always false (location info not available).
	bool hasLocationInfo() const { return false; }

	double GetValue( int lin, int col );

 protected:
//	gribsec2_sv*       sec2_;
	unsigned char*     Sbuf_;
};


#endif
// MvGrid_DEFINED
