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

  Copyright (C) 2003-2020 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 "remap_grid_cell_search.h"
#include "cdo_output.h"
#include "compare.h"
#include "array.h"

extern "C"
{
#include "lib/yac/grid_scrip.h"
#include "lib/yac/sphere_part.h"
}

CellSearchMethod cellSearchMethod(CellSearchMethod::spherepart);

void
setCellSearchMethod(const char *methodstr)
{
  // clang-format off
  if      (cstrIsEqual(methodstr, "spherepart")) cellSearchMethod = CellSearchMethod::spherepart;
  else if (cstrIsEqual(methodstr, "latbins"))    cellSearchMethod = CellSearchMethod::latbins;
  else cdoAbort("Grid cell search method %s not available!", methodstr);
  // clang-format on
}

static void
gridBoundboxReg2d(size_t nx, size_t ny, const Varray<double> &reg2d_corner_lon, const Varray<double> &reg2d_corner_lat, double *grid_bound_box)
{
  grid_bound_box[0] = reg2d_corner_lat[0];
  grid_bound_box[1] = reg2d_corner_lat[ny];
  if (grid_bound_box[0] > grid_bound_box[1])
    {
      grid_bound_box[0] = reg2d_corner_lat[ny];
      grid_bound_box[1] = reg2d_corner_lat[0];
    }
  grid_bound_box[2] = reg2d_corner_lon[0];
  grid_bound_box[3] = reg2d_corner_lon[nx];
}

void
gridCellSearchCreateReg2d(GridCellSearch &gcs, size_t dims[2], const Varray<double> &reg2d_corner_lon, const Varray<double> &reg2d_corner_lat)
{
  gcs.is_reg2d = true;
  gcs.dims[0] = dims[0];
  gcs.dims[1] = dims[1];
  const auto nx = dims[0];
  const auto ny = dims[1];
  const auto nxp1 = nx + 1;
  const auto nyp1 = ny + 1;

  gcs.reg2d_corner_lon.resize(nxp1);
  gcs.reg2d_corner_lat.resize(nyp1);

  varrayCopy(nxp1, reg2d_corner_lon, gcs.reg2d_corner_lon);
  varrayCopy(nyp1, reg2d_corner_lat, gcs.reg2d_corner_lat);

  gridBoundboxReg2d(nx, ny, gcs.reg2d_corner_lon, gcs.reg2d_corner_lat, gcs.gridBoundboxReg2d);

  gcs.in_use = true;
}

void
gridCellSearchCreate(GridCellSearch &gcs, size_t numCells, size_t numCellCorners, Varray<double> &cellCornerLon, Varray<double> &cellCornerLat)
{
  gcs.method = cellSearchMethod;

  auto yacSrcGrid = yac_scrip_grid_new(cellCornerLon.data(), cellCornerLat.data(), numCells, numCellCorners);
  gcs.yacSearch = yac_sphere_part_search_new(yacSrcGrid);
  gcs.yacSrcGrid = yacSrcGrid;

  gcs.in_use = true;
}

void
gridCellSearchDelete(GridCellSearch &gcs)
{
  if (gcs.in_use)
    {
      varrayFree(gcs.reg2d_corner_lon);
      varrayFree(gcs.reg2d_corner_lat);

      if (gcs.yacSearch) yac_delete_grid_search((grid_search *) gcs.yacSearch);
      if (gcs.yacSrcGrid) yac_delete_grid((grid *) gcs.yacSrcGrid);

      gcs.in_use = false;
    }
}

static size_t
doGridCellSearchYac(grid_search *yacSearch, bool isReg2dCell, grid_cell &gridCell, size_t *srchAddr)
{
  size_t numCellCorners = gridCell.num_corners;
  bounding_circle bndCircle;
  double(*xyz)[3] = gridCell.coordinates_xyz;

  if (numCellCorners == 4 && isReg2dCell)
    yac_get_cell_bounding_circle_reg_quad(xyz[0], xyz[1], xyz[2], &bndCircle);
  else if (numCellCorners == 3)
    yac_get_cell_bounding_circle_unstruct_triangle(xyz[0], xyz[1], xyz[2], &bndCircle);
  else
    yac_get_cell_bounding_circle(gridCell, &bndCircle);

  dep_list *resultList = yac_do_bnd_circle_search(yacSearch, &bndCircle, 1);

  const size_t numSrchCells = yac_get_num_dependencies_of_element(resultList, 0);
  unsigned const *currNeighs = yac_get_dependencies_of_element(resultList, 0);
  for (size_t i = 0; i < numSrchCells; ++i) srchAddr[i] = currNeighs[i];

  yac_dep_list_delete(resultList);

  return numSrchCells;
}

size_t
doGridCellSearch(GridCellSearch &gcs, bool isReg2dCell, grid_cell &gridCell, size_t *srchAddr)
{
  if (gcs.in_use)
    {
      // clang-format off
      if (gcs.method == CellSearchMethod::spherepart)
        return doGridCellSearchYac((grid_search*)gcs.yacSearch, isReg2dCell, gridCell, srchAddr);
      else
        cdoAbort("%s::method undefined!", __func__);
      // clang-format on
    }

  return 0;
}
