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

 Copyright 2021 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

#pragma once

#include <iosfwd>
#include <limits>
#include <map>

#include "MvVariant.h"

//! 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,
    eGeoColElevation,
    eGeoColValue,
    eGeoColValue2,
    eGeoColError
};


enum eGeoCopyMode
{
    eGeoCopyCoordinatesOnly,
    eGeoCopyAll
};

//_____________________________________________________________________
//! Convenience struct to hold values related to column definitions
class MvGeoPointColumnInfo
{
public:
    void ensureOnlyOneValueColumn();

    std::vector<std::string> colNames_;
    int ncols_;
    int ncoordcols_;
    int nvalcols_;
    int nvalcolsforcompute_;
    bool hasStnIds_;
    bool hasElevations_;
    std::vector<eGeoColType> colTypes_;
    bool isCompatibleForMerging(const MvGeoPointColumnInfo& in);

private:
    std::vector<std::string> usedColNames() const;
    std::vector<eGeoColType> usedColTypes() const;
};


// the intention was to put the column info into each column,
// but this can be a later refactoring
class MvGeoColumn
{
    /*
public:

    void name(std::string &s) { name_ = s; }
    std::string & name() { return name_; }

    void isCoordinate(bool b) { isCoordinate_ = b; }
    bool isCoordinate() { return isCoordinate_; }

    void colType(eGeoColType ct) { colType_ = ct; }
    eGeoColType colType() { return colType_; }


private:
    std::string name_;
    eGeoColType colType_;
    bool isCoordinate_;
*/
};


class MvGeoColumnString : public MvGeoColumn, public std::vector<std::string>
{
public:
    void destroyVals()
    {
        MvGeoColumnString empty;
        this->swap(empty);
    }
};

class MvGeoColumnDouble : public MvGeoColumn, public std::vector<double>
{
public:
    void destroyVals()
    {
        MvGeoColumnDouble empty;
        this->swap(empty);
    }
};

class MvGeoColumnLong : public MvGeoColumn, public std::vector<long>
{
public:
    void destroyVals()
    {
        MvGeoColumnLong empty;
        this->swap(empty);
    }
};


class MvGeoPoints
{
public:
    static const size_t INVALID_INDEX = std::numeric_limits<size_t>::max();

    using metadata_t = std::map<std::string, MvVariant>;


    // constructors
    MvGeoPoints(size_t count = 0, int numvals = 1, eGeoFormat efmt = eGeoTraditional, bool init = true);
    MvGeoPoints(size_t count, const MvGeoPointColumnInfo& colInfo, eGeoFormat efmt, bool init);
    MvGeoPoints(size_t count, bool init = true);
    MvGeoPoints(const char* name, const size_t nmax = 0);
    MvGeoPoints(const MvGeoPoints&);  // copy constructor

    MvGeoPoints& operator=(const MvGeoPoints& gp);  // assigment operator


    // destructor
    ~MvGeoPoints();
    void unload();  // Releases points data from memory


    eGeoFormat format() const { return gfmt_; }
    std::string sFormat() const { return sgfmt_; }

    void set_format(eGeoFormat f) { gfmt_ = f; }
    void set_format(eGeoFormat fmt, size_t numvals);

    std::string path() const { return path_; }
    void set_path(std::string p) { path_ = p; }


    size_t count() const { return count_; }
    void set_count(size_t c) { count_ = c; }

    size_t valueIndex() const { return vi_; }
    void set_valueIndex(size_t vi) { vi_ = vi; }

    size_t rowIndex() const { return r_; }
    void set_rowIndex(size_t r) { r_ = r; }

    double lat_y(size_t r) const { return latitudes_[r]; }
    double lat_y() const { return latitudes_[r_]; }
    void set_lat_y(double d) { latitudes_[r_] = d; }
    void set_lat_y(double d, size_t r) { latitudes_[r] = d; }
    double lon_x(size_t r) const { return longitudes_[r]; }
    double lon_x() const { return longitudes_[r_]; }
    void set_lon_x(double d) { longitudes_[r_] = d; }
    void set_lon_x(double d, size_t r) { longitudes_[r] = d; }


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


    double height(size_t r) const { return heights_[r]; }
    double height() const { return heights_[r_]; }
    void set_height(double d) { heights_[r_] = d; }
    void set_height(double d, size_t r) { heights_[r] = d; }

    double elevation(size_t r) const { return elevations_[r]; }
    double elevation() const { return elevations_[r_]; }
    void set_elevation(double d) { elevations_[r_] = d; }
    void set_elevation(double d, size_t r) { elevations_[r] = d; }

    long date(size_t r) const { return dates_[r]; }
    long date() const { return dates_[r_]; }
    void set_date(long d) { dates_[r_] = d; }
    void set_date(long d, size_t r) { dates_[r] = d; }

    long time(size_t r) const { return times_[r]; }
    long time() const { return times_[r_]; }
    void set_time(long d) { times_[r_] = d; }
    void set_time(long d, size_t r) { times_[r] = d; }

    // possible optimisation - remove const and return a reference
    std::string strValue(size_t r) const { return strings_[r]; }
    std::string strValue() const { return strings_[r_]; }
    void set_strValue(const char* d) { strings_[r_] = d; }
    void set_strValue(const char* d, size_t r) { strings_[r] = d; }
    void set_strValue(const std::string& s) { strings_[r_] = s; }
    void set_strValue(const std::string& s, size_t r) { strings_[r] = s; }
    bool isStnIdEmpty() const
    {
        static std::string empty("?");
        return (strValue().empty() || strValue() == empty);
    }
    bool isStnIdEmpty(size_t r) const
    {
        static std::string empty("?");
        return (strValue(r).empty() || strValue(r) == empty);
    }
    std::string stnid() { return strValue(); }
    std::string stnid(size_t r) { return strValue(r); }
    void set_stnid(const char* id) { set_strValue(id); }


    double value(size_t r) const { return values_[vi_][r]; }
    double value() const { return values_[vi_][r_]; }
    void set_value(double d, size_t r) { values_[vi_][r] = d; }
    void set_value(double d) { values_[vi_][r_] = d; }
    double speed() const { return value(); }      //-- alias for value()
    void set_speed(double d) const { value(d); }  //-- alias for value()

    double value2(size_t r) const { return values_[1][r]; }
    double value2() const { return values_[1][r_]; }
    void set_value2(double d, size_t r) { values_[1][r] = d; }
    void set_value2(double d) { values_[1][r_] = d; }
    double direc() const { return value2(); }           //-- alias for value2()
    double direc(size_t r) const { return value2(r); }  //-- alias for value2()
    void set_direc(double d) { set_value2(d); }         //-- alias for value2()
    bool direc_missing() const { return (values_[1][r_] == GEOPOINTS_MISSING_VALUE); }
    void set_direc_missing(size_t r) { values_[1][r] = GEOPOINTS_MISSING_VALUE; }
    bool have_direc() { return (nValCols() > 1); }

    double ivalue(size_t c) const { return values_[c][r_]; }
    double ivalue(size_t r, size_t c) const { return values_[c][r]; }
    void set_ivalue(double d, size_t c) { values_[c][r_] = d; }
    void set_ivalue(double d, size_t r, size_t c) { values_[c][r] = d; }

    std::string value(size_t row, size_t col, int& type);

    bool value_missing() const { return (values_[vi_][r_] == GEOPOINTS_MISSING_VALUE); }
    bool value_missing(size_t c) const { return (ivalue(r_, c) == GEOPOINTS_MISSING_VALUE); }
    bool value_missing(size_t r, size_t c) const { return (ivalue(r, c) == GEOPOINTS_MISSING_VALUE); }


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


    // void set_value_missing(size_t r, size_t c) { value(c, r, GEOPOINTS_MISSING_VALUE); }
    void set_value_missing() { set_value(GEOPOINTS_MISSING_VALUE); }  // use row r_ and default val col
    void set_value_missing(size_t c) { set_ivalue(GEOPOINTS_MISSING_VALUE, c); }
    void set_value_missing(size_t r, size_t c) { set_ivalue(GEOPOINTS_MISSING_VALUE, r, c); }

    bool hasVectors() const { return gfmt_ == eGeoVectorPolar || gfmt_ == eGeoVectorXY; }


    //! latLonHeightBefore, cut-down version of less-than operator
    bool latLonHeightBefore(const MvGeoPoints& in, size_t r_this, size_t r_in) const;

    //! Returns \c true when latitude, longitude and height values are equal
    bool sameLocation(const MvGeoPoints& in, size_t r_this, size_t r_in);


    //! Resizes the values arrays according to the current ncols settings
    void resizeValueColumns();


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

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

    bool load(const char* filename);
    bool load(std::ifstream& fstr, const size_t nmax = 0);

    bool extract(const char* line, size_t r);
    bool parseColumnNames(char* line);
    int countValueColumns(char* line, int numCoordCols);
    void fillValueColumnNames();
    void ensureNColsHasStnIds();


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


    //! Returns the column information
    size_t ncols() const { return colInfo_.ncols_; }
    size_t totalcols() const { return colInfo_.ncoordcols_ + colInfo_.nvalcols_; }
    std::vector<std::string> colNames() const { return colInfo_.colNames_; }
    std::string colName(size_t i) const
    {
        if (i >= 0 && i < colInfo_.colNames_.size())
            return colInfo_.colNames_[i];
        else
            return "";
    }
    std::vector<std::string> valueColNames() const;
    std::string valueColName(size_t i) const { return colInfo_.colNames_[colInfo_.ncoordcols_ + i]; }
    std::vector<std::string> usedColNames() const;
    size_t nValCols() const { return colInfo_.nvalcols_; }
    size_t nCoordCols() const { return colInfo_.ncoordcols_; }
    size_t nValColsForCompute() const { return colInfo_.nvalcolsforcompute_; }
    void nValColsForCompute(size_t n) { colInfo_.nvalcolsforcompute_ = n; }
    void nValCols(size_t n)
    {
        colInfo_.nvalcols_ = n;
        colInfo_.nvalcolsforcompute_ = n;
        resizeValueColumns();
    }
    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; }
    bool hasElevations() const { return colInfo_.hasElevations_; }
    void hasElevations(bool b) { colInfo_.hasElevations_ = b; }
    void clearColNames() { colInfo_.colNames_.clear(); }
    void clearValueColNames() { colInfo_.colNames_.resize(colInfo_.ncoordcols_); }
    void addColName(std::string name, bool markStnIdAsUsed = false, bool markElevationAsUsed = false, bool addToFront = false);
    void addColType(eGeoColType t, bool addToFront = false);
    void clearColTypes() { colInfo_.colTypes_.clear(); }
    MvGeoPointColumnInfo& columnInfo() { return colInfo_; }


    std::string column(size_t row, size_t col, MvGeoPointColumnInfo& colinfo, int&);


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


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


    void setColumnsForFormat();


    //! 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
    std::string dbSystem() const { return dbSystem_; }
    const std::map<std::string, std::string>& dbColumn() const { return dbColumn_; }
    std::string dbColumn(std::string col)
    {
        return (dbColumn_.find(col) != dbColumn_.end()) ? dbColumn_[col] : "";
    }
    const std::map<std::string, std::string>& dbColumnAlias() const { return dbColumnAlias_; }
    std::string dbColumnAlias(std::string col)
    {
        return (dbColumnAlias_.find(col) != dbColumnAlias_.end()) ? dbColumnAlias_[col] : "";
    }
    std::string dbPath() const { return dbPath_; }
    const std::vector<std::string>& dbQuery() const { return dbQuery_; }


    //! 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);

    void location(double lat, double lon);  //{ latitude_=lat; longitude_=lon; }


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


    void copyRow(const MvGeoPoints& src, size_t srcRow, size_t destRow);
    void copyRows(const MvGeoPoints& src, size_t srcRowFirst, size_t numRows, size_t destRow);

    bool areRowsEqual(size_t r1, size_t r2);


    MvGeoColumnDouble& valuesColumn(size_t c) { return values_[c]; }


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

    static int _countDigits(char*& p);

    //! 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);

    static bool isMissingValue(double d) { return (d == GEOPOINTS_MISSING_VALUE); }


private:
    void _stringOrNumber(char* buf);
    bool load(const size_t nmax = 0);


    eGeoFormat gfmt_;
    size_t count_;
    std::string path_;
    size_t vi_;  //-- index for the currently used value column
    size_t r_;   //-- row to use if not suppied in fn args

    MvGeoPointColumnInfo colInfo_;

    MvGeoColumnString strings_;
    MvGeoColumnDouble latitudes_;
    MvGeoColumnDouble longitudes_;
    MvGeoColumnDouble heights_;
    MvGeoColumnDouble elevations_;
    MvGeoColumnLong dates_;
    MvGeoColumnLong times_;

    std::vector<MvGeoColumnDouble> values_;


    std::string sgfmt_;
    std::string dbSystem_;
    std::map<std::string, std::string> dbColumn_;
    std::map<std::string, std::string> dbColumnAlias_;
    std::string dbPath_;
    std::vector<std::string> dbQuery_;

    metadata_t metadata_;


    static std::map<std::string, eGeoColType> coordColMap_;
};
