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

  Author: Uwe Schulzweida

*/

#include <numbers>
#include "cellsearch_reg2d.h"

constexpr auto twoPi = 2.0 * std::numbers::pi;

void
grid_boundbox_reg2d(size_t nx, size_t ny, Varray<double> const &cornerLonsReg2d, Varray<double> const &cornerLatsReg2d,
                    double *gridBoundBox)
{
  gridBoundBox[0] = cornerLatsReg2d[0];
  gridBoundBox[1] = cornerLatsReg2d[ny];
  if (gridBoundBox[0] > gridBoundBox[1])
  {
    gridBoundBox[0] = cornerLatsReg2d[ny];
    gridBoundBox[1] = cornerLatsReg2d[0];
  }
  gridBoundBox[2] = cornerLonsReg2d[0];
  gridBoundBox[3] = cornerLonsReg2d[nx];
}

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

static long
set_srch_indices(size_t numSearchCells, long nx, long imin, long imax, long jmin, long jmax, Varray<size_t> &srchIndices)
{
  size_t maxSize = numSearchCells + (jmax - jmin + 1) * (imax - imin + 1);
  if (srchIndices.size() < maxSize) srchIndices.resize(maxSize);
  for (long jm = jmin; jm <= jmax; ++jm)
    for (long im = imin; im <= imax; ++im) srchIndices[numSearchCells++] = jm * nx + im;

  return numSearchCells;
}

static void
check_lon_bounds(double offset, double lonMin, double lonMax, double &boundLon1, double &boundLon2)
{
  boundLon1 += offset;
  boundLon2 += offset;
  if (boundLon1 < lonMin && boundLon2 > lonMin) boundLon1 = lonMin;
  if (boundLon2 > lonMax && boundLon1 < lonMax) boundLon2 = lonMax;
}
/*
static void
debug_message(const char *txt, long imin, long imax, long jmin, long jmax, Varray<double> const &srcCornersLons)
{
  printf("%s:  lonMin=%g lonMax=%g  iMin=%ld iMax=%ld  jMin=%ld jMax %ld  numCells=%ld\n", txt, RAD2DEG * srcCornersLons[imin],
         RAD2DEG * srcCornersLons[imax + 1], imin, imax, jmin, jmax, (jmax - jmin + 1) * (imax - imin + 1));
}
*/
static size_t
get_search_cells_reg2d(long nx, long ny, Varray<double> const &srcCornersLats, Varray<double> const &srcCornersLons,
                       std::array<double, 4> const &tgtCellBoundBox, Varray<size_t> &srchIndices)
{
  auto debug{ false };
  size_t numSearchCells = 0;  // num cells in restricted search arrays

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

  auto imin = nxp1, imax = -1L, jmin = nyp1, jmax = -1L;

  auto lfound = rect_grid_search2(jmin, jmax, tgtCellBoundBox[0], tgtCellBoundBox[1], nyp1, srcCornersLats);
  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 srcLonMin = srcCornersLons[0];
  auto srcLonMax = srcCornersLons[nx];

  auto boundLon1 = tgtCellBoundBox[2];
  auto boundLon2 = tgtCellBoundBox[3];
  // debug = (bound_lon1 <= 0 && bound_lon2 >= 0);

  if (boundLon1 <= srcLonMax && boundLon2 >= srcLonMin)
  {
    check_lon_bounds(0.0, srcLonMin, srcLonMax, boundLon1, boundLon2);
    lfound = rect_grid_search2(imin, imax, boundLon1, boundLon2, nxp1, srcCornersLons);
    if (lfound)
    {
      // if (debug) debug_message("1", imin, imax, jmin, jmax, srcCornersLons);
      numSearchCells = set_srch_indices(numSearchCells, nx, imin, imax, jmin, jmax, srchIndices);
    }
  }

  boundLon1 = tgtCellBoundBox[2];
  boundLon2 = tgtCellBoundBox[3];
  if (boundLon1 < srcLonMin || boundLon2 > srcLonMax)
  {
    if (boundLon1 <= srcLonMin && boundLon2 >= srcLonMin)
    {
      check_lon_bounds(twoPi, srcLonMin, srcLonMax, boundLon1, boundLon2);
      long imin2 = nxp1, imax2 = -1;
      lfound = rect_grid_search2(imin2, imax2, boundLon1, boundLon2, nxp1, srcCornersLons);
      if (lfound)
      {
        if (imax != -1 && imin2 <= imax) imin2 = imax + 1;
        if (imax != -1 && imax2 <= imax) imax2 = imax + 1;
        if (imin2 >= 0 && imax2 < nx)
        {
          // if (debug) debug_message("2", imin2, imax2, jmin, jmax, srcCornersLons);
          numSearchCells = set_srch_indices(numSearchCells, nx, imin2, imax2, jmin, jmax, srchIndices);
        }
      }
    }

    boundLon1 = tgtCellBoundBox[2];
    boundLon2 = tgtCellBoundBox[3];
    if (boundLon1 <= srcLonMax && boundLon2 >= srcLonMax)
    {
      check_lon_bounds(-twoPi, srcLonMin, srcLonMax, boundLon1, boundLon2);
      long imin3 = nxp1, imax3 = -1;
      lfound = rect_grid_search2(imin3, imax3, boundLon1, boundLon2, nxp1, srcCornersLons);
      if (lfound)
      {
        if (imin != nxp1 && imin3 >= imin) imin3 = imin - 1;
        if (imax != nxp1 && imax3 >= imin) imax3 = imin - 1;
        if (imin3 >= 0 && imin3 < nx)
        {
          // if (debug) debug_message("3", imin3, imax3, jmin, jmax, srcCornersLons);
          numSearchCells = set_srch_indices(numSearchCells, nx, imin3, imax3, jmin, jmax, srchIndices);
        }
      }
    }
  }

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

  return numSearchCells;
}

static void
restrict_boundbox(double const *gridBoundBox, std::array<double, 4> &boundBox)
{
  if (boundBox[0] < gridBoundBox[0] && boundBox[1] > gridBoundBox[0]) boundBox[0] = gridBoundBox[0];
  if (boundBox[1] > gridBoundBox[1] && boundBox[0] < gridBoundBox[1]) boundBox[1] = gridBoundBox[1];

  if (boundBox[2] >= gridBoundBox[3] && (boundBox[3] - twoPi) > gridBoundBox[2])
  {
    boundBox[2] -= twoPi;
    boundBox[3] -= twoPi;
  }
  if (boundBox[3] <= gridBoundBox[2] && (boundBox[2] - twoPi) < gridBoundBox[3])
  {
    boundBox[2] += twoPi;
    boundBox[3] += twoPi;
  }
}

static void
boundbox_from_corners_reg2d(GridCell const &gridCell, std::array<double, 4> &boundBox)
{
  auto clat1 = gridCell.coordinatesY[0];
  auto clat2 = gridCell.coordinatesY[2];

  boundBox[0] = (clat2 > clat1) ? clat1 : clat2;
  boundBox[1] = (clat2 > clat1) ? clat2 : clat1;
  boundBox[2] = gridCell.coordinatesX[0];
  boundBox[3] = gridCell.coordinatesX[1];
}

static void
boundbox_from_corners_unstruct(GridCell const &gridCell, std::array<double, 4> &boundBox)
{
  auto clon = gridCell.coordinatesX[0];
  auto clat = gridCell.coordinatesY[0];

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

  auto nc = gridCell.yacGridCell.num_corners;
  for (size_t j = 1; j < nc; ++j)
  {
    clon = gridCell.coordinatesX[j];
    clat = gridCell.coordinatesY[j];

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

  if (std::abs(boundBox[3] - boundBox[2]) > std::numbers::pi)
  {
    boundBox[2] = 0;
    boundBox[3] = twoPi;
  }
}

size_t
CellsearchReg2d::do_cellsearch(bool isReg2dCell, const GridCell &gridCell, Varray<size_t> &searchIndices)
{
  std::array<double, 4> tgtCellBoundBox;
  if (isReg2dCell)
    boundbox_from_corners_reg2d(gridCell, tgtCellBoundBox);
  else
    boundbox_from_corners_unstruct(gridCell, tgtCellBoundBox);

  restrict_boundbox(m_gridBoundbox, tgtCellBoundBox);

  auto numSearchCells = get_search_cells_reg2d(m_nx, m_ny, m_cornerLatsReg2d, m_cornerLonsReg2d, tgtCellBoundBox, searchIndices);

  if (numSearchCells == 1 && m_nx == 1 && m_ny == 1 && is_equal(m_cornerLatsReg2d[0], m_cornerLatsReg2d[1])
      && is_equal(m_cornerLonsReg2d[0], m_cornerLonsReg2d[1]))
    numSearchCells = 0;

  return numSearchCells;
}
