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

// MvGeoPoints.h,   apr03/vk


#include <string>
#include "inc_iostream.h"
#include <map>
#include <vector>
#include "MvVariant.h"

using namespace std;

//! Geopoint missing value indicator
/*! Background: the choice of value depended on three things:
 * <UL>
 *  <LI> it should be large enough to be outwith the theoretical
 *     range of any meteorological parameter
 *  <LI> it should be small enough to fit into a standard \c float
 *     variable (courtesy to users who may write their own programs
 *     that load geopoints files using single-precision floating point)
 *  <LI> it should have a minimum number of digits in its printed value
 *     so that it is immune to any change to the printed precision
 *     of geopoints values (there may, in the future, be a user-callable
 *     function to set this parameter).
 * </UL>
 */
#define GEOPOINTS_MISSING_VALUE     3.0E+38


//const double cIsStringValue = 0.5e37;

//! \enum eGeoFormat Enum for different geopoints file types
enum eGeoFormat
{
   eGeoTraditional    /**< - lat_y / lon_x / level / date / time / value */
  ,eGeoString         /**< - lat_y / lon_x / level / date / time / stringValue */
  ,eGeoXYV            /**< - lon_x / lat_y / value */
  ,eGeoVectorPolar    /**< - lat_y / lon_x / level / date / time / speed / direction */
  ,eGeoVectorXY       /**< - lat_y / lon_x / level / date / time / u-comp / v-comp */
};

//! \enum eGeoValueType Enum for different geopoints data types
enum eGeoValueType { eGeoVString, eGeoVDouble, eGeoVLong };

//_____________________________________________________________________
//! A class for a single geopoint, normally stored in MvGeoPoints file
/*! Geopoints is the format used by Metview to handle spatially irregular
 *  data (e.g. observations) in a non-BUFR format.\n \n
 *  Metview geopoints format has several flavours, see enum ::eGeoFormat.
 */
class MvGeoP1
{
    //! Friend function to write one point (one line) into a MvGeoPoints file
    friend ostream& operator<< ( ostream& aStream, const MvGeoP1& gp );

 public:
    //! Constructor
    MvGeoP1();

    //! Copy constructor
    MvGeoP1( const MvGeoP1& in );

    //! Destructor
    ~MvGeoP1(){}

    //! Assignment operator
    MvGeoP1& operator =  ( const MvGeoP1& gp1 );

    //! Equality operator returns \c true when all data values are the same
    /*! Note that the longitude values are compared as such, i.e.
     *  they are not normalised for comparison. Thus points that otherwise
     *  are equal and which represent the same geographical latitude point
     *  but with different sign (e.g. -60E and 270E), are NOT considered equal.
     */
    bool     operator == ( const MvGeoP1& gp1 );

    //! Less-than operator, used for sorting MvGeoPoints objects
    /*! Compares the values of latitude, longitude, height and value.
     *  When deciding which geopoint is "less-than", latitude is
     *  is considered as the most significant value and the height
     *  is considered the least significant value. \n \n
     *  Note that longitude values are compared as such, i.e.
     *  they are not normalised for comparison. Thus the following
     *  three points - all located on the same latitude circle and
     *  have the same height - have the following relation (values
     *  inside square brackets are [latitude,longitude,height]):
     * <PRE>
     *  [45,-60,1000] < [45,0,1000] < [45,270,1000]
     * </PRE>
     *  although in the real world the first point <TT>[45,-60,1000]</TT>
     *  and the third point <TT>[45,270,1000]</TT> refer to the same
     *  geographical location.
     */
    bool     operator <  ( const MvGeoP1& gp1 ) const;

    //! latLonHeightBefore, cut-down version of less-than operator
    bool     latLonHeightBefore ( const MvGeoP1& gp1 ) const;


    //! Extracts data values from a line from a geopoints file
    /*! Used mainly by class MvGeoPoints to access geopoints files.
     *  geoFmt is updated if the value turns out to be a string.
     */
    void   extract( const char* line, eGeoFormat &geoFmt );

    //! Returns the column value
    string column ( int, int& );

    //! Returns the latitude value (alias \c Y value)
    double lat_y()   const { return latitude_;  }

    //! Returns the longitude value (alias \c X value)
    double lon_x()   const { return longitude_; }

    //! Returns the height value
    double height()  const { return height_;    }

    //! Returns the date value
    long   date()    const { return date_;      }

    //! Returns the time value
    long   time()    const { return time_;      }

    //! Returns the string value
    /*! Returns an empty string if the point has a numerical value.
     */
    string strValue()const { return strValue_;  }

    //! Returns the (first) value
    double value()   const { return value_;     }

    //! Alias for \c value(), returns the first value (wind speed)
    double speed()   const { return value_;     }  //-- alias for value()

    //! Returns the second value
    double value2()  const { return value2_;    }

    //! Alias for \c value2(), returns the second value (wind direction)
    double direc()   const { return value2_;    }  //-- alias for value2()

    //! Change the latitude value. No checks on the value are done.
    void   lat_y( double lat ) { latitude_ = lat; }

    //! Change the longitude value. No checks on the value are done.
    void   lon_x( double lon ) { longitude_ = lon; }

    //! Change the height value. No checks on the value are done.
    void   height( double h ) { height_ = h; }

    //! Change the date value. No checks on the value are done.
    void   date( long d ) { date_ = d; }

    //! Change the time value. No checks on the value are done.
    void   time( long t ) { time_ = t; }

    //! Change the (first) value
    void   value( double v ) { value_ = v;  }      //-- set value

    //! Change the second value
    void   value2( double v ){ value2_ = v; }      //-- set value2

    //! Change the wind speed (the second) value
    void   direc( double v ) { value2_ = v; }      //-- set direction, alias for value2

    //! Change the point format
    void   format( eGeoFormat fmt ) { gfmt_ = fmt; }

    //! Returns \c true when latitude, longitude and height values are equal
    bool   sameLocation( const MvGeoP1& gp1 );

    //! Assigns new latitude/longitude values to the point
    /*! The given values are checked for validity:
     *  the latitude value is forced between [-90...90] and the
     *  longitude value is "semi-normalised" to fall between [-180...360].
     */
    void   location( double lat, double lon ); //{ latitude_=lat; longitude_=lon; }

    //! Returns the enum value of the geopoint format of the point
    eGeoFormat format() const { return gfmt_; }

    //! Returns \c true if the point has a string value
    bool       hasStringValue() const { return gfmt_ == eGeoString; }

    //! Returns \c true if the point has two values, i.e. it represents a vector
    bool       hasVector() const
	               { return gfmt_ == eGeoVectorPolar || gfmt_ == eGeoVectorXY; }

    //! Returns \c true if the (first) value is missing
    bool    value_missing() const {return  (value_  == GEOPOINTS_MISSING_VALUE);}

    //! Returns \c true if the second value is missing
    bool    direc_missing() const {return  (value2_ == GEOPOINTS_MISSING_VALUE);}

    //! Returns \c true if either of the values is missing
    bool    any_missing()   const {return ((value_  == GEOPOINTS_MISSING_VALUE) || (value2_ == GEOPOINTS_MISSING_VALUE));}

    //! Sets the (first) value missing
    void    set_value_missing()   {value_  = GEOPOINTS_MISSING_VALUE;}  // set to missing

    //! Sets the second value missing
    void    set_direc_missing()   {value2_ = GEOPOINTS_MISSING_VALUE;}  // set to missing

// protected:
 private:
    void   _copy( const MvGeoP1& gp1 );
    void   _stringOrNumber( char* buf );
    int    _countDigits( char*& p );

 protected:
    eGeoFormat gfmt_;
    double     latitude_;
    double     longitude_;
    double     height_;
    long       date_;
    long       time_;
    double     value_;             //-- value can be either numeric...
    string     strValue_;          //-- ...or a string
    double     value2_;            //-- for vector data (direction)
};

//_____________________________________________________________________
//! A class for handling geopoints files
/*! Geopoints is the format used by Metview to handle spatially irregular
 *  data (e.g. observations) in a non-BUFR format.\n \n
 *  Metview geopoints format has several flavours, see enum ::eGeoFormat.\n \n
 *  Individual points are stored in an array of MvGeoP1 objects.
 */
class MvGeoPoints
{

 public:
	typedef map<string,MvVariant> metadata_t;


	//! Constructor
	MvGeoPoints();

	//! Copy constructor
	MvGeoPoints( const MvGeoPoints& );

	//! Constructor with a name of a geopoints file as the argument
	/*! Loads geopoints from the file into memory. If nmax is given, only
       nmax geopoints are loaded into memory.
	 */
	MvGeoPoints( const char *name, const int nmax=0 );

	//! Constructor to create a geopoints object with \c count empty points
	MvGeoPoints( long  count );
	//	MvGeoPoints(fieldset*,int);
	//	MvGeoPoints(MvGeoPoints *,fieldset*,int);

	//! Destructor
	~MvGeoPoints();

	//! Assigment operator
	MvGeoPoints& operator = ( const MvGeoPoints& gp );

	//! Access operator to extract \c n'th point
	/*! Index \c n starts from zero, i.e. n=0,1,2,3...
	 */
	MvGeoP1& operator [] ( long n ) const { return pts_[ n ]; }

	//! Returns an element given row and column
	string value (long, int, int&);

	//! Returns the format of points in MvGeoPoints object
	eGeoFormat format() const { return gfmt_; }
	string sFormat() const { return sgfmt_; }

	//! Sets the format for points in MvGeoPoints object
	void format( eGeoFormat fmt );

	//! Returns the column information
	int ncols() const { return ncols_; }
	vector<string> scols() const { return scols_; }
	string scol( int index ) const { return scols_[index]; }

	//! Set geopoints format info
	void setFormat();

	//! Returns \c true if MvGeoPoints object contains geovectors
	bool       hasVectors() const
	               { return gfmt_ == eGeoVectorPolar || gfmt_ == eGeoVectorXY; }

	//! Resets the size of MvGeoPoints object to be \c n points
	/*! Old points are deleted and \c n new empty points are created.
	 */
	void     newReservedSize( long n );

	//! Returns the number of points in MvGeoPoints object
	long     count()   const { return count_; }

	//! Resets the number of points to be \c n
	/*! Note that this method does not change point values and that
	 *  it is meant to be used only to decrease the number of active
	 *  points in the file.\n \n
	 *  Use method newReservedSize() to increase the number of available points.
	 */
	void     count( long n ) { count_ = n; }    //-- make smaller only!!

	//! Returns the geopoint closest to the given latitude-longitude location
	MvGeoP1  nearestPoint( double lat_y, double lon_x ) const;

	//! Returns the index of the first point not having a missing value
	/*! Note: index starts from zero.
	 */
	long     indexOfFirstValidPoint() const;

	//! Loads points data from file \c filename to memory
	bool     load( const char* filename );

	//! Loads points data from ifstream \c filename to memory
	bool     load( ifstream &fstr, const int nmax = 0 );

	//! Releases points data from memory
	void     unload();

	//! Writes geopoints to file \c filename
	bool     write( const char* filename );

	//! Sorts geopoints using MvGeoP1::operator<
	void     sort();

	//! Removes duplicate geopoints
	/*! First all points are sorted using sort() and then all duplicate
	 *  points are removed. Operator MvGeoP1::operator== is used to
	 *  test the equality.
	 */
	void     removeDuplicates();

	//! Offsets the latitude/longitude values
	/*! This method can be used to print values from two or more
	 *  MvGeoPoints objects containing same locations, to prevent
	 *  the values to be printed on top of each other.\n \n
	 *  Note that the offset values are given in degrees and thus
	 *  the visual offset on the plot depends on the scale of
	 *  the plot (which also depends on the level of zooming).
	 */
	void     offset( double latOffset, double lonOffset );

	//! Return a map of metadata items
	metadata_t &metadata()                  {return metadata_;}
	metadata_t const &metadataConst() const {return metadata_;}

	//! Information about the database, query etc. that generated the geopoints data
	string dbSystem() const {return dbSystem_;}
	const map<string,string>& dbColumn() const {return dbColumn_;}
 	string dbColumn(string col)
		{return (dbColumn_.find(col) != dbColumn_.end()) ? dbColumn_[col]:"";}
	const map<string,string>& dbColumnAlias() const {return dbColumnAlias_;}
	string dbColumnAlias(string col)
		{return (dbColumnAlias_.find(col) != dbColumnAlias_.end()) ? dbColumnAlias_[col]:"";}
	string dbPath() const {return dbPath_;}
	const vector<string>& dbQuery() const {return dbQuery_;}

	//! Members
	//! Returns filename
	string path() const { return path_; }

 private:
	void _copy( const MvGeoPoints& gp );
	bool load( const int nmax=0 );

 protected:
	eGeoFormat      gfmt_;
	MvGeoP1*        pts_;
	long            count_;
	string          path_;
	string          sgfmt_;
	vector<string>  scols_;
	int				ncols_;

	metadata_t      metadata_;
	string          dbSystem_;
	map<string,string>      dbColumn_;
	map<string,string>      dbColumnAlias_;
	string          dbPath_;
	vector<string>  dbQuery_;
};
