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

#include "process_int.h"
#include <mpim_grid.h>
#include "remap.h"

int rect_grid_search2(long &imin, long &imax, double xmin, double xmax, long nxm, const Varray<double> &xm);

static size_t
getSrchCellsReg2d(const size_t *src_grid_dims, const Varray<double> &src_corner_lat, const Varray<double> &src_corner_lon,
                  const double *tgt_cell_bound_box, size_t *srch_add)
{
  bool debug = false;
  long nx = src_grid_dims[0];
  long ny = src_grid_dims[1];
  size_t num_srch_cells = 0;  // num cells in restricted search arrays

  long nxp1 = nx + 1;
  long nyp1 = ny + 1;

  auto src_lon_min = src_corner_lon[0];
  auto src_lon_max = src_corner_lon[nx];

  long imin = nxp1, imax = -1, jmin = nyp1, jmax = -1;

  int lfound = rect_grid_search2(jmin, jmax, tgt_cell_bound_box[0], tgt_cell_bound_box[1], nyp1, src_corner_lat);
  if (!lfound) return 0;
  // printf("lfound, jmin, jmax %d %ld %ld\n", lfound, jmin, jmax);
  // if ( jmin > 0 ) jmin--;
  // if ( jmax < (ny-2) ) jmax++;

  auto bound_lon1 = tgt_cell_bound_box[2];
  auto bound_lon2 = tgt_cell_bound_box[3];
  if (bound_lon1 <= src_lon_max && bound_lon2 >= src_lon_min)
    {
      if (debug) printf("  b1 %g %g\n", bound_lon1 * RAD2DEG, bound_lon2 * RAD2DEG);
      if (bound_lon1 < src_lon_min && bound_lon2 > src_lon_min) bound_lon1 = src_lon_min;
      if (bound_lon2 > src_lon_max && bound_lon1 < src_lon_max) bound_lon2 = src_lon_max;
      lfound = rect_grid_search2(imin, imax, bound_lon1, bound_lon2, nxp1, src_corner_lon);
      if (lfound)
        {
          if (debug)
            printf("   %g %g imin %ld  imax %ld  jmin %ld jmax %ld\n", RAD2DEG * src_corner_lon[imin],
                   RAD2DEG * src_corner_lon[imax + 1], imin, imax, jmin, jmax);
          for (long jm = jmin; jm <= jmax; ++jm)
            for (long im = imin; im <= imax; ++im) srch_add[num_srch_cells++] = jm * nx + im;
        }
    }

  bound_lon1 = tgt_cell_bound_box[2];
  bound_lon2 = tgt_cell_bound_box[3];
  if (bound_lon1 <= src_lon_min && bound_lon2 >= src_lon_min)
    {
      long imin2 = nxp1, imax2 = -1;
      bound_lon1 += 2 * M_PI;
      bound_lon2 += 2 * M_PI;
      if (debug) printf("  b2 %g %g\n", bound_lon1 * RAD2DEG, bound_lon2 * RAD2DEG);
      if (bound_lon1 < src_lon_min && bound_lon2 > src_lon_min) bound_lon1 = src_lon_min;
      if (bound_lon2 > src_lon_max && bound_lon1 < src_lon_max) bound_lon2 = src_lon_max;
      lfound = rect_grid_search2(imin2, imax2, bound_lon1, bound_lon2, nxp1, src_corner_lon);
      if (lfound)
        {
          if (imax != -1 && imin2 <= imax) imin2 = imax + 1;
          if (imax != -1 && imax2 <= imax) imax2 = imax + 1;
          if (imin2 >= 0 && imax2 < nxp1)
            {
              if (debug)
                printf("   %g %g imin %ld  imax %ld  jmin %ld jmax %ld\n", RAD2DEG * src_corner_lon[imin2],
                       RAD2DEG * src_corner_lon[imax2 + 1], imin2, imax2, jmin, jmax);
              for (long jm = jmin; jm <= jmax; ++jm)
                for (long im = imin2; im <= imax2; ++im) srch_add[num_srch_cells++] = jm * nx + im;
            }
        }
    }

  bound_lon1 = tgt_cell_bound_box[2];
  bound_lon2 = tgt_cell_bound_box[3];
  if (bound_lon1 <= src_lon_max && bound_lon2 >= src_lon_max)
    {
      long imin3 = nxp1, imax3 = -1;
      bound_lon1 -= 2 * M_PI;
      bound_lon2 -= 2 * M_PI;
      if (debug) printf("  b3 %g %g\n", bound_lon1 * RAD2DEG, bound_lon2 * RAD2DEG);
      if (bound_lon1 < src_lon_min && bound_lon2 > src_lon_min) bound_lon1 = src_lon_min;
      if (bound_lon2 > src_lon_max && bound_lon1 < src_lon_max) bound_lon2 = src_lon_max;
      lfound = rect_grid_search2(imin3, imax3, bound_lon1, bound_lon2, nxp1, src_corner_lon);
      if (lfound)
        {
          if (imin != nxp1 && imin3 >= imin) imin3 = imin - 1;
          if (imax != nxp1 && imax3 >= imin) imax3 = imin - 1;
          if (imin3 >= 0 && imin3 < nxp1)
            {
              if (debug)
                printf("   %g %g imin %ld  imax %ld  jmin %ld jmax %ld\n", RAD2DEG * src_corner_lon[imin3],
                       RAD2DEG * src_corner_lon[imax3 + 1], imin3, imax3, jmin, jmax);
              for (long jm = jmin; jm <= jmax; ++jm)
                for (long im = imin3; im <= imax3; ++im) srch_add[num_srch_cells++] = jm * nx + im;
            }
        }
    }

  if (debug) printf(" num_srch_cells: %zu\n", num_srch_cells);

  return num_srch_cells;
}

static void
restrict_boundbox(const double *grid_bound_box, double *bound_box)
{
  if (bound_box[0] < grid_bound_box[0] && bound_box[1] > grid_bound_box[0]) bound_box[0] = grid_bound_box[0];
  if (bound_box[1] > grid_bound_box[1] && bound_box[0] < grid_bound_box[1]) bound_box[1] = grid_bound_box[1];

  if (bound_box[2] >= grid_bound_box[3] && (bound_box[3] - 2 * M_PI) > grid_bound_box[2])
    {
      bound_box[2] -= 2 * M_PI;
      bound_box[3] -= 2 * M_PI;
    }
  if (bound_box[3] <= grid_bound_box[2] && (bound_box[2] - 2 * M_PI) < grid_bound_box[3])
    {
      bound_box[2] += 2 * M_PI;
      bound_box[3] += 2 * M_PI;
    }
}

static void
boundboxFromCornersReg2d(const grid_cell &yacGridCell, double *bound_box)
{
  const auto coordinates_x = yacGridCell.coordinates_x;
  const auto coordinates_y = yacGridCell.coordinates_y;

  const auto clat1 = coordinates_y[0];
  const auto clat2 = coordinates_y[2];
  if (clat2 > clat1)
    {
      bound_box[0] = clat1;
      bound_box[1] = clat2;
    }
  else
    {
      bound_box[0] = clat2;
      bound_box[1] = clat1;
    }

  bound_box[2] = coordinates_x[0];
  bound_box[3] = coordinates_x[1];
}

static void
boundboxFromCorners1(const grid_cell &yacGridCell, double *bound_box)
{
  const auto coordinates_x = yacGridCell.coordinates_x;
  const auto coordinates_y = yacGridCell.coordinates_y;

  auto clon = coordinates_x[0];
  auto clat = coordinates_y[0];

  bound_box[0] = clat;
  bound_box[1] = clat;
  bound_box[2] = clon;
  bound_box[3] = clon;

  const auto nc = yacGridCell.num_corners;
  for (size_t j = 1; j < nc; ++j)
    {
      clon = coordinates_x[j];
      clat = coordinates_y[j];

      if (clat < bound_box[0]) bound_box[0] = clat;
      if (clat > bound_box[1]) bound_box[1] = clat;
      if (clon < bound_box[2]) bound_box[2] = clon;
      if (clon > bound_box[3]) bound_box[3] = clon;
    }

  if (std::fabs(bound_box[3] - bound_box[2]) > PI)
    {
      bound_box[2] = 0;
      bound_box[3] = PI2;
    }
}

static size_t
gridSearchCellsReg2d(const GridCellSearch &gcs, bool isReg2dCell, grid_cell &gridCell, size_t *srchAddr)
{
  double tgt_cell_bound_box[4];
  if (isReg2dCell)
    boundboxFromCornersReg2d(gridCell, tgt_cell_bound_box);
  else
    boundboxFromCorners1(gridCell, tgt_cell_bound_box);

  restrict_boundbox(gcs.gridBoundboxReg2d, tgt_cell_bound_box);

  auto numSrchCells = getSrchCellsReg2d(gcs.dims, gcs.reg2d_corner_lat, gcs.reg2d_corner_lon, tgt_cell_bound_box, srchAddr);

  if (numSrchCells == 1 && gcs.dims[0] == 1 && gcs.dims[1] == 1 && IS_EQUAL(gcs.reg2d_corner_lat[0], gcs.reg2d_corner_lat[1])
      && IS_EQUAL(gcs.reg2d_corner_lon[0], gcs.reg2d_corner_lon[1]))
    numSrchCells = 0;

  return numSrchCells;
}

static size_t
gridSearchCells(GridCellSearch &gcs, bool isReg2dCell, grid_cell &gridCell, size_t *srchAddr)
{
  return do_grid_cell_search(gcs, isReg2dCell, gridCell, srchAddr);
}

size_t
remap_search_cells(RemapSearch &rsearch, bool isReg2dCell, grid_cell &gridCell, size_t *srchAddr)
{
  if (rsearch.srcGrid->type == RemapGridType::Reg2D)
    return gridSearchCellsReg2d(rsearch.gcs, isReg2dCell, gridCell, srchAddr);
  else
    return gridSearchCells(rsearch.gcs, isReg2dCell, gridCell, srchAddr);
}
