/*
  This file is part of CDO. CDO is a collection of Operators to
  manipulate and analyse Climate model Data.

  Copyright (C) 2003-2021 Uwe Schulzweida, <uwe.schulzweida AT mpimet.mpg.de>
  See COPYING file for copying and redistribution conditions.

  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; version 2 of the License.

  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.
*/
#ifndef REMAP_H
#define REMAP_H

#include <cstdint>
#include <cmath>

#include "varray.h"
#include "remap_vars.h"
#include "remap_grid_cell_search.h"
#include "grid_point_search.h"

#ifndef M_PI
#define M_PI 3.14159265358979323846264338327950288
#endif

constexpr double PI = M_PI;
constexpr double PI2 = (2.0 * PI);
constexpr double PIH = (0.5 * PI);
constexpr float PI_f = PI;
constexpr float PI2_f = PI2;
constexpr float PIH_f = PIH;

constexpr double TINY = 1.e-14;

enum class RemapGridType
{
  Undefined,
  Reg2D,
  Curve2D,
  Unstruct
};

#define REMAP_GRID_BASIS_SRC 1
#define REMAP_GRID_BASIS_TGT 2

enum class SubmapType
{
  NONE,
  LAF,
  SUM,
  AVG
};

struct LonLatPoint
{
  double lon, lat;
  LonLatPoint() {};
  LonLatPoint(double _lon, double _lat) : lon(_lon), lat(_lat) {};
};

struct // RemapGrid
#ifdef WARN_UNUSED
[[gnu::warn_unused]]
#endif
RemapGrid
{
  int gridID;
  int tmpgridID;
  RemapGridType type;
  int rank;                 // rank of the grid
  size_t size;              // total points on the grid
  size_t num_cell_corners;  // number of corners for each grid cell

  bool lneed_cell_corners;
  bool luse_cell_corners;   // use corners for bounding boxes

  bool lextrapolate;
  bool non_global;
  bool is_cyclic;

  size_t dims[2];           // size of grid dimension

  int nvgp;                 // size of vgpm
  Varray<int> vgpm;         // flag which cells are valid
  Varray<short> mask;       // flag which cells participate

  Varray<double> reg2d_center_lon;  // reg2d lon/lat coordinates for
  Varray<double> reg2d_center_lat;  // each grid center in radians
  Varray<double> reg2d_corner_lon;  // reg2d lon/lat coordinates for
  Varray<double> reg2d_corner_lat;  // each grid corner in radians

  Varray<double> cell_center_lon;  // lon/lat coordinates for
  Varray<double> cell_center_lat;  // each grid center in radians
  Varray<double> cell_corner_lon;  // lon/lat coordinates for
  Varray<double> cell_corner_lat;  // each grid corner in radians

  Varray<double> cell_area;        // total area of each grid cell
  Varray<double> cell_frac;        // fractional area of grid cells participating in remapping
};

struct // GridSearchBins
#ifdef WARN_UNUSED
[[gnu::warn_unused]]
#endif
GridSearchBins
{
  unsigned nbins;                // num of bins for restricted search
  size_t ncells;                 // total number of grid cells (cell_bound_box)
  Varray<size_t> bin_addr;       // min,max adds for grid cells in this lat bin
  Varray<float> bin_lats;        // min,max latitude for each search bin
  Varray<float> cell_bound_box;  // lon/lat bounding box for use
};

struct // RemapSearch
#ifdef WARN_UNUSED
[[gnu::warn_unused]]
#endif
RemapSearch
{
  RemapGrid *srcGrid;
  RemapGrid *tgtGrid;

  GridSearchBins srcBins;
  GridSearchBins tgtBins;

  GridPointSearch gps;
  GridCellSearch gcs;
};

struct // RemapType
#ifdef WARN_UNUSED
[[gnu::warn_unused]]
#endif
RemapType
{
  int nused;
  int gridID;
  size_t gridsize;
  size_t nmiss;
  RemapGrid src_grid;
  RemapGrid tgt_grid;
  RemapVars vars;
  RemapSearch search;
};

#define REMAP_WRITE_REMAP 2
#define REMAP_MAX_ITER 3
#define REMAP_NUM_SRCH_BINS 4
#define REMAP_GENWEIGHTS 5

void remap_set_threshhold(double threshhold);
void remap_set_int(int remapvar, int value);

void remap_init_grids(RemapMethod mapType, bool lextrapolate, int gridID1, RemapGrid &src_grid, int gridID2, RemapGrid &tgt_grid);

void remap_grid_init(RemapGrid &grid);
void remap_grid_free(RemapGrid &grid);
void remap_grid_alloc(RemapMethod mapType, RemapGrid &grid);
void remap_search_init(RemapMethod mapType, RemapSearch &search, RemapGrid &src_grid, RemapGrid &tgt_grid);
void remap_search_free(RemapSearch &search);

void remap_search_points(RemapSearch &rsearch, const LonLatPoint &llp, knnWeightsType &knnWeights);
int remap_search_square(RemapSearch &rsearch, const LonLatPoint &llp, size_t (&src_add)[4], double (&src_lats)[4], double (&src_lons)[4]);
size_t remap_search_cells(RemapSearch &rsearch, bool isReg2dCell, grid_cell &gridCell, size_t *srchAddr);

void remap_bilinear_weights(RemapSearch &rsearch, RemapVars &rv);
void remap_bicubic_weights(RemapSearch &rsearch, RemapVars &rv);
void remap_distwgt_weights(size_t numNeighbors, RemapSearch &rsearch, RemapVars &rv);
void remap_conserv_weights(RemapSearch &rsearch, RemapVars &rv);
void remap_conserv_weights_scrip(RemapSearch &rsearch, RemapVars &rv);

void remap_bilinear(RemapSearch &rsearch, const Field &field1, Field &field2);
void remap_bicubic(RemapSearch &rsearch, const Field &field1, Field &field2);
void remap_dist_wgt(size_t numNeighbors, RemapSearch &rsearch, const Field &field1, Field &field2);
void remap_conserv(NormOpt normOpt, RemapSearch &rsearch, const Field &field1, Field &field2);

void remap_stat(int remapOrder, RemapGrid &src_grid, RemapGrid &tgt_grid, RemapVars &rv, const Field &field1, const Field &field2);

template <typename T>
void remap_gradients(RemapGrid &grid, const Varray<short> &mask, const Varray<T> &array, RemapGradients &gradients);
void remap_gradients(RemapGrid &grid, const Field &field, RemapGradients &gradients);

void sort_add(size_t num_links, size_t num_wts, size_t *add1, size_t *add2, double *weights);
void sort_iter(size_t num_links, size_t num_wts, size_t *add1, size_t *add2, double *weights, int parent);

void remap_write_data_scrip(const char *interp_file, RemapMethod mapType, SubmapType submapType, int numNeighbors, int remapOrder,
                         RemapGrid &src_grid, RemapGrid &tgt_grid, RemapVars &rv);
void remap_read_data_scrip(const char *interp_file, int gridID1, int gridID2, RemapMethod &mapType, SubmapType &submapType,
                        int &numNeighbors, int &remapOrder, RemapGrid &src_grid, RemapGrid &tgt_grid, RemapVars &rv);

void calc_lat_bins(GridSearchBins &searchBins);
size_t get_srch_cells(size_t tgt_cell_addr, GridSearchBins &tgtBins, GridSearchBins &srcBins, float *tgt_cell_bound_box,
                      size_t *srch_add);

int grid_search_square_reg_2d_NN (size_t nx, size_t ny, size_t *nbr_add, double *nbr_dist, double plat, double plon,
                            const Varray<double> &src_center_lat, const Varray<double> &src_center_lon);

int grid_search_square_reg_2d(RemapGrid *src_grid, size_t (&src_add)[4], double (&src_lats)[4], double (&src_lons)[4], double plat, double plon);

bool point_in_quad(bool isCyclic, size_t nx, size_t ny, size_t i, size_t j, size_t adds[4], double lons[4], double lats[4],
                 double plon, double plat, const double *centerLon, const double *centerLat);

int grid_search_square_curv_2d_scrip(RemapGrid *src_grid, size_t (&src_add)[4], double (&src_lats)[4], double (&src_lons)[4], double plat, double plon,
                                GridSearchBins &srcBins);

bool remap_find_weights(const LonLatPoint &llpoint, const double (&src_lons)[4], const double (&src_lats)[4], double *ig, double *jg);
int rect_grid_search(size_t &ii, size_t &jj, double x, double y, size_t nxm, size_t nym, const Varray<double> &xm, const Varray<double> &ym);

LonLatPoint remapgrid_get_lonlat(RemapGrid *grid, size_t cell_add);

void remap_check_area(size_t grid_size, double *cell_area, const char *name);

#endif /* REMAP_H */
