/***************************** 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 */
    ,
    eGeoNCols /**< - flexible structure */
    ,
    eGeoError /**< - return if not found */
};

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


//! \enum eGeoColType Enum for different 'non-value' columns in a geopoints file (used in NCOLS format)
enum eGeoColType
{
    eGeoColStnId,
    eGeoColLat,
    eGeoColLon,
    eGeoColLevel,
    eGeoColDate,
    eGeoColTime,
    eGeoColValue,
    eGeoColValue2,
    eGeoColError
};


enum eGeoCopyMode
{
    eGeoCopyCoordinatesOnly,
    eGeoCopyAll
};

//_____________________________________________________________________
//! Convenience struct to hold values related to column definitions
class MvGeoPointColumnInfo
{
public:

    bool operator==(const MvGeoPointColumnInfo& in);
    void ensureOnlyOneValueColumn();

    vector<string> colNames_;
    int ncols_;
    int ncoordcols_;
    int nvalcols_;
    int nvalcolsforcompute_;
    bool hasStnIds_;
    vector<eGeoColType> colTypes_;
};


//_____________________________________________________________________
//! 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 MvGeoPoints;  // so that MvGeoP1 can take pointers to MvGeoPoints

class MvGeoP1
{
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.
     *  Returns true of ok, false if error
     */
    bool extract(const char* line, eGeoFormat& geoFmt, MvGeoPoints* gpts);

    //! Returns the column value
    string column(size_t, MvGeoPointColumnInfo& colinfo, 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 whether the stnid is empty
    bool isStnIdEmpty() const { static std::string empty("?"); return (strValue_.empty() || strValue_ == empty); }

    //! Returns the (first) value
    double value() const { return values_[vi_]; }

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

    //! Returns the second value
    double value2() const { return values_[1]; }

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

    //! Returns the indexed value
    double ivalue(size_t i) const { return values_[i]; }

    //! Returns a reference to the vector of values
    const vector<double>& values() const { return values_; }

    //! 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) { values_[vi_] = v; }  //-- set value

    //! Change the second value
    void value2(double v) { values_[1] = v; }  //-- set value2

    //! Change the (nth) value
    void value(double v, size_t i) { values_[i] = v; }  //-- set value

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

    //! Change the station id
    void stnid(const char* id) { strValue_ = id; }

    //! Change the point format
    void format(eGeoFormat fmt, size_t numvals);

    //! 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 (values_[vi_] == GEOPOINTS_MISSING_VALUE); }

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

    //! Returns \c true if the (nth) value is missing
    bool value_missing(size_t n) const { return (values_[n] == GEOPOINTS_MISSING_VALUE); }

    //! Returns \c true if either of the values is missing
    bool any_missing() const { return ((values_[vi_] == GEOPOINTS_MISSING_VALUE) || (values_[1] == GEOPOINTS_MISSING_VALUE)); }

    //! Returns \c true if either the latitude or longitude is missing
    bool latlon_missing() const { return ((lat_y() == GEOPOINTS_MISSING_VALUE) || (lon_x() == GEOPOINTS_MISSING_VALUE)); }

    bool isLocationValid() const;

    //! Sets the (first) value missing
    void set_value_missing() { values_[vi_] = GEOPOINTS_MISSING_VALUE; }  // set to missing

    //! Sets the second value missing
    void set_direc_missing() { values_[1] = GEOPOINTS_MISSING_VALUE; }  // set to missing

    //! Sets the (nth) value missing
    void set_value_missing(size_t n) { values_[n] = GEOPOINTS_MISSING_VALUE; }  // set to missing

    void copy(const MvGeoP1& gp1, eGeoCopyMode mode=eGeoCopyAll);


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

protected:
    eGeoFormat gfmt_;
    double latitude_;
    double longitude_;
    double height_;
    long date_;
    long time_;
    vector<double> values_;
    size_t vi_;        //-- index for the currently used value column
    string strValue_;  //-- value can be a string, but only one
};


//_____________________________________________________________________
//! 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(int count = 0, int numvals = 1, eGeoFormat efmt = eGeoTraditional, bool init = true);

    //! Constructor with column info
    MvGeoPoints(int count, const MvGeoPointColumnInfo &colInfo, eGeoFormat efmt, bool init);

    //! 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; init=true means construct 'count' gpts
    MvGeoPoints(long count, bool init = true);
    //	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) { return pts_[n]; }
    const MvGeoP1& const_element_ref(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, size_t numvals);

    //! Returns the column information
    int ncols() const { return colInfo_.ncols_; }
    int totalcols() const { return colInfo_.ncoordcols_ + colInfo_.nvalcols_; }
    vector<string> colNames() const { return colInfo_.colNames_; }
    string colName(size_t i) const
    {
        if (i >= 0 && i < colInfo_.colNames_.size())
            return colInfo_.colNames_[i];
        else
            return "";
    }
    vector<string> valueColNames() const;
    string valueColName(size_t i) const {return colInfo_.colNames_[colInfo_.ncoordcols_+i]; }
    vector<string> usedColNames() const;
    size_t nValCols() { return colInfo_.nvalcols_; }
    size_t nCoordCols() { return colInfo_.ncoordcols_; }
    size_t nValColsForCompute() { return colInfo_.nvalcolsforcompute_; }
    void nValColsForCompute(size_t n) { colInfo_.nvalcolsforcompute_ = n; }
    void nValCols(size_t n)
    {
        colInfo_.nvalcols_           = n;
        colInfo_.nvalcolsforcompute_ = n;
    }
    eGeoColType colType(size_t i) { return colInfo_.colTypes_[i]; }
    void setColType(size_t i, eGeoColType t) { colInfo_.colTypes_[i] = t; }
    bool hasStnIds() const { return colInfo_.hasStnIds_; }
    void hasStnIds(bool b) { colInfo_.hasStnIds_ = b; }
    void clearColNames() { colInfo_.colNames_.clear(); }
    void clearValueColNames() { colInfo_.colNames_.resize(colInfo_.ncoordcols_); }
    void addColName(std::string name, bool markStnIdAsUsed = false, bool addToFront = false);
    void addColType(eGeoColType t, bool addToFront = false);
    void clearColTypes() { colInfo_.colTypes_.clear(); }

    //! Returns index of the named value (e.g. 'temperature'), or -1 if it does not exist for this data
    int indexOfNamedValue(std::string& name);

    //! 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, bool init = true);

    //! 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(size_t c = 0) 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);

    //! Reads and stores column names from the given line
    bool parseColumnNames(char* line);

    //! Counts the number of non-co-ordinate values on the given line
    int countValueColumns(char* line, int numCoordCols);

    //! Pads the list of column names with empty strings
    void fillValueColumnNames();

    //! If the type is NCOLS, ensure that we have stnid as a coordinate column
    void ensureNColsHasStnIds();

    //! Returns whether another geopoints variable is compatible with this one in terms of columns
    bool isCompatible(const MvGeoPoints &in) {return colInfo_ == in.colInfo_;}

    //! Returns a reference to the column info
    MvGeoPointColumnInfo &columnInfo() { return colInfo_; }


    //! 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_; }

    void copy(const MvGeoPoints& gp, eGeoCopyMode mode=eGeoCopyAll);

    //! Returns reference to the map of coordinate types/names
    static const std::map<std::string, eGeoColType>& coordColMap();


    //! Returns the column type given the name (returns eGeoColError if bad name)
    static eGeoColType colTypeFromName(const std::string& name, bool failIfUnknown = false);

    //! Returns whether the given column type is a coordinate(true) or value(false)
    static bool colTypeIsCoord(eGeoColType t);


private:
    bool load(const int nmax = 0);

protected:
    eGeoFormat gfmt_;
    vector<MvGeoP1> pts_;
    long count_;
    string path_;
    string sgfmt_;
    MvGeoPointColumnInfo colInfo_;

    metadata_t metadata_;
    string dbSystem_;
    map<string, string> dbColumn_;
    map<string, string> dbColumnAlias_;
    string dbPath_;
    vector<string> dbQuery_;
    static std::map<std::string, eGeoColType> coordColMap_;
};
