/**********************************************************************
zyGrib: meteorological GRIB file viewer
Copyright (C) 2008 - Jacques Zaninetti - http://www.zygrib.org

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
***********************************************************************/

/******************************************
Elément de base d'un fichier GRIB
******************************************/

#ifndef GRIBRECORD_H
#define GRIBRECORD_H

#include <iostream>
#include <cmath>

#define DEBUG_INFO false
#define DEBUG_ERROR true
#define grib_debug(format, ...)             \
  {                                         \
    if (DEBUG_INFO) {                       \
      fprintf(stderr, format, __VA_ARGS__); \
      fprintf(stderr, "\n");                \
    }                                       \
  }
#define erreur(format, ...)                 \
  {                                         \
    if (DEBUG_ERROR) {                      \
      fprintf(stderr, "Grib ERROR: ");      \
      fprintf(stderr, format, __VA_ARGS__); \
      fprintf(stderr, "\n");                \
    }                                       \
  }

#define zuint unsigned int
#define zuchar unsigned char

#define GRIB_NOTDEF -999999999

//--------------------------------------------------------
// dataTypes    Cf function translateDataType()
//--------------------------------------------------------
#define GRB_PRESSURE 2   /* Pa     */
#define GRB_GEOPOT_HGT 7 /* gpm    */
#define GRB_TEMP 11      /* K      */
#define GRB_TPOT 13      /* K      */
#define GRB_TMAX 15      /* K      */
#define GRB_TMIN 16      /* K      */
#define GRB_DEWPOINT 17  /* K      */

#define GRB_WIND_DIR 31   /* Deg. Wind Direction */
#define GRB_WIND_SPEED 32 /* m/s  Wind Speed     */
#define GRB_WIND_VX 33    /* m/s U  */
#define GRB_WIND_VY 34    /* m/s V  */

#define GRB_CUR_DIR 47   /* Deg. Direction of current  */
#define GRB_CUR_SPEED 48 /* m/s Speed of current       */
#define GRB_UOGRD 49     /*"u-component of current", "m/s" */
#define GRB_VOGRD 50     /*"v-component of current", "m/s" */

#define GRB_HUMID_SPEC 51  /* kg/kg  */
#define GRB_HUMID_REL 52   /* %      */
#define GRB_PRECIP_RATE 59 /* l/m2/s */
#define GRB_PRECIP_TOT 61  /* l/m2   */
#define GRB_SNOW_DEPTH 66  /* m      */
#define GRB_CLOUD_TOT 71   /* %      */
#define GRB_HTSGW 100      /* m      */
#define GRB_WTMP 80        /* "Water Temperature", "K" */
#define GRB_COMP_REFL 212  /* dBZ */

#define GRB_WVDIR 101
#define GRB_WVHGT 102
#define GRB_WVPER 103
#define GRB_SWDIR 104
#define GRB_SWELL 105
#define GRB_SWPER 106
#define GRB_DIRPW 107
#define GRB_PERPW 108
#define GRB_DIRSW 109
#define GRB_PERSW 110
#define GRB_PER 209
#define GRB_DIR 210

#define GRB_CRAIN 140         /* "Categorical rain", "yes=1;no=0" */
#define GRB_FRZRAIN_CATEG 141 /* 1=yes 0=no */
#define GRB_SNOW_CATEG 143    /* 1=yes 0=no */
#define GRB_CAPE 157          /* J/kg   */

#define GRB_TSEC \
  171 /* "Seconds prior to initial reference time (defined in bytes 18-20)" */
#define GRB_WIND_GUST 180    /* m/s "wind gust */
#define GRB_WIND_GUST_VX 181 /* m/s */
#define GRB_WIND_GUST_VY 182 /* m/s */

#define GRB_USCT 190 /* Scatterometer estimated U Wind, NCEP Center 7  */
#define GRB_VSCT 191 /* Scatterometer estimated V Wind, NCEP Center 7  */

#define GRB_WIND_XY2D 250    /* private : GRB_WIND_VX+GRB_WIND_VX */
#define GRB_DIFF_TEMPDEW 251 /* private : GRB_TEMP-GRB_DEWPOINT */

//--------------------------------------------------------
// Levels types (altitude reference)
//--------------------------------------------------------
#define LV_GND_SURF 1
#define LV_ISOTHERM0 4
#define LV_ISOBARIC 100
#define LV_MSL 102
#define LV_ABOV_MSL 103
#define LV_ABOV_GND 105
#define LV_SIGMA 107
#define LV_ATMOS_ENT 10
#define LV_ATMOS_ALL 200
//---------------------------------------------------------
enum DataCenterModel {
  NOAA_GFS,
  NOAA_NCEP_WW3,
  NOAA_NCEP_SST,
  NOAA_RTOFS,
  FNMOC_WW3_GLB,
  FNMOC_WW3_MED,
  NORWAY_METNO,
  ECMWF_ERA5,
  KNMI_HIRLAM,
  KNMI_HARMONIE_AROME,
  OTHER_DATA_CENTER
};

//----------------------------------------------
class GribCode {
public:
  static zuint makeCode(zuchar dataType, zuchar levelType, zuint levelValue) {
    return ((levelValue & 0xFFFF) << 16) + ((levelType & 0xFF) << 8) + dataType;
  }
  static zuchar getDataType(zuint code) { return code & 0xFF; }
  static zuchar getLevelType(zuint code) { return (code >> 8) & 0xFF; }
  static zuint getLevelValue(zuint code) { return (code >> 16) & 0xFFFF; }
};

//----------------------------------------------
class GribRecord {
public:
  GribRecord(const GribRecord &rec);
  GribRecord() { m_bfilled = false; }

  virtual ~GribRecord();

  static GribRecord *InterpolatedRecord(const GribRecord &rec1,
                                        const GribRecord &rec2, double d,
                                        bool dir = false);
  static GribRecord *Interpolated2DRecord(GribRecord *&rety,
                                          const GribRecord &rec1x,
                                          const GribRecord &rec1y,
                                          const GribRecord &rec2x,
                                          const GribRecord &rec2y, double d);

  static GribRecord *MagnitudeRecord(const GribRecord &rec1,
                                     const GribRecord &rec2);

  static void Polar2UV(GribRecord *pDIR, GribRecord *pSPEED);

  void multiplyAllData(double k);
  void Substract(const GribRecord &rec, bool positive = true);
  void Average(const GribRecord &rec);

  bool isOk() const { return ok; };
  bool isDataKnown() const { return knownData; };
  bool isEof() const { return eof; };
  bool isDuplicated() const { return IsDuplicated; };
  //-----------------------------------------
  zuchar getDataType() const { return dataType; }
  void setDataType(const zuchar t);

  zuchar getLevelType() const { return levelType; }
  zuint getLevelValue() const { return levelValue; }
  zuint getDataCenterModel() const { return dataCenterModel; }
  //-----------------------------------------

  zuchar getIdCenter() const { return idCenter; }
  zuchar getIdModel() const { return idModel; }
  zuchar getIdGrid() const { return idGrid; }

  //-----------------------------------------
  std::string getKey() const { return dataKey; }
  static std::string makeKey(int dataType, int levelType, int levelValue);

  //-----------------------------------------
  int getPeriodP1() const { return periodP1; }
  int getPeriodP2() const { return periodP2; }
  zuint getPeriodSec() const { return periodsec; }
  zuchar getTimeRange() const { return timeRange; }

  // Number of points in the grid
  int getNi() const { return Ni; }
  int getNj() const { return Nj; }
  double getDi() const { return Di; }
  double getDj() const { return Dj; }

  // Value at one point of the grid
  double getValue(int i, int j) const { return data[j * Ni + i]; }

  void setValue(zuint i, zuint j, double v) {
    if (i < Ni && j < Nj) data[j * Ni + i] = v;
  }

  // Value for one point interpolated
  double getInterpolatedValue(double px, double py,
                              bool numericalInterpolation = true,
                              bool dir = false) const;

  // Value for polar interpolation of vectors
  static bool getInterpolatedValues(double &M, double &A, const GribRecord *GRX,
                                    const GribRecord *GRY, double px, double py,
                                    bool numericalInterpolation = true);

  // coordiantes of grid point
  inline double getX(int i) const { return Lo1 + i * Di; }
  inline double getY(int j) const { return La1 + j * Dj; }
  void getXY(int i, int j, double *x, double *y) const {
    *x = getX(i);
    *y = getY(j);
  };

  double getLatMin() const { return latMin; }
  double getLonMin() const { return lonMin; }
  double getLatMax() const { return latMax; }
  double getLonMax() const { return lonMax; }

  // Is there a value at a particular grid point ?
  inline bool hasValue(int i, int j) const;
  // Is there a value that is not GRIB_NOTDEF ?
  inline bool isDefined(int i, int j) const {
    return hasValue(i, j) && getValue(i, j) != GRIB_NOTDEF;
  }

  // Reference date Date (file creation date)
  time_t getRecordRefDate() const { return refDate; }
  const char *getStrRecordRefDate() const { return strRefDate; }

  // Date courante des prévisions
  time_t getRecordCurrentDate() const { return curDate; }
  const char *getStrRecordCurDate() const { return strCurDate; }
  void setRecordCurrentDate(time_t t);
  void print();
  bool isFilled() { return m_bfilled; }
  void setFilled(bool val = true) { m_bfilled = val; }

private:
  // Is a point within the extent of the grid?
  inline bool isPointInMap(double x, double y) const;
  inline bool isXInMap(double x) const;
  inline bool isYInMap(double y) const;

protected:
  // private:
  static bool GetInterpolatedParameters(const GribRecord &rec1,
                                        const GribRecord &rec2, double &La1,
                                        double &Lo1, double &La2, double &Lo2,
                                        double &Di, double &Dj, int &im1,
                                        int &jm1, int &im2, int &jm2, int &Ni,
                                        int &Nj, int &rec1offi, int &rec1offj,
                                        int &rec2offi, int &rec2offj);

  int id;          // unique identifiant
  bool ok;         // valid?
  bool knownData;  // type de donnée connu
  bool waveData;
  bool IsDuplicated;
  bool eof;
  std::string dataKey;
  char strRefDate[32];
  char strCurDate[32];
  int dataCenterModel;
  bool m_bfilled;

  //---------------------------------------------
  // SECTION 0: THE INDICATOR SECTION (IS)
  //---------------------------------------------
  zuchar editionNumber;

  // SECTION 1: THE PRODUCT DEFINITION SECTION (PDS)
  zuchar idCenter;
  zuchar idModel;
  zuchar idGrid;
  zuchar dataType;  // octet 9 = parameters and units
  zuchar levelType;
  zuint levelValue;

  bool hasBMS;
  zuint refyear, refmonth, refday, refhour, refminute;
  // zuchar periodP1, periodP2;
  zuint periodP1, periodP2;
  zuchar timeRange;
  zuint periodsec;  // period in seconds
  time_t refDate;   // C reference date
  time_t curDate;   // C current date
  // SECTION 2: THE GRID DESCRIPTION SECTION (GDS)
  zuchar NV, PV;
  zuchar gridType;
  zuint Ni, Nj;
  double La1, Lo1, La2, Lo2;
  double latMin, lonMin, latMax, lonMax;
  double Di, Dj;
  zuchar resolFlags, scanFlags;
  bool hasDiDj;
  bool isEarthSpheric;
  bool isUeastVnorth;
  bool isScanIpositive;
  bool isScanJpositive;
  bool isAdjacentI;
  // SECTION 3: BIT MAP SECTION (BMS)
  zuint BMSsize;
  zuchar *BMSbits;
  // SECTION 4: BINARY DATA SECTION (BDS)
  double *data;
  // SECTION 5: END SECTION (ES)

  time_t makeDate(zuint year, zuint month, zuint day, zuint hour, zuint min,
                  zuint sec);

  //        void   print();
};

//==========================================================================
inline bool GribRecord::hasValue(int i, int j) const {
  // is data present in BMS ?
  if (!hasBMS) {
    return true;
  }
  int bit;
  if (isAdjacentI) {
    bit = j * Ni + i;
  } else {
    bit = i * Nj + j;
  }
  zuchar c = BMSbits[bit / 8];
  zuchar m = (zuchar)128 >> (bit % 8);
  return (m & c) != 0;
}

//-----------------------------------------------------------------
inline bool GribRecord::isPointInMap(double x, double y) const {
  return isXInMap(x) && isYInMap(y);
  /*    if (Dj < 0)
          return x>=Lo1 && y<=La1 && x<=Lo1+(Ni-1)*Di && y>=La1+(Nj-1)*Dj;
      else
          return x>=Lo1 && y>=La1 && x<=Lo1+(Ni-1)*Di && y<=La1+(Nj-1)*Dj;*/
}
//-----------------------------------------------------------------
inline bool GribRecord::isXInMap(double x) const {
  //    return x>=Lo1 && x<=Lo1+(Ni-1)*Di;
  // printf ("%f %f %f\n", Lo1, Lo2, x);
  if (Di > 0) {
    double maxLo = Lo2;
    if (Lo2 + Di >= 360) /* grib that covers the whole world */
      maxLo += Di;
    return x >= Lo1 && x <= maxLo;
  } else {
    double maxLo = Lo1;
    if (Lo2 + Di >= 360) /* grib that covers the whole world */
      maxLo += Di;
    return x >= Lo2 && x <= maxLo;
  }
}
//-----------------------------------------------------------------
inline bool GribRecord::isYInMap(double y) const {
  if (Dj < 0)
    return y <= La1 && y >= La2;
  else
    return y >= La1 && y <= La2;
}

#endif
