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

  Copyright (C) 2003-2018 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 "cdo_int.h"
#include "grid_cell_search.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      (STR_IS_EQ(methodstr, "spherepart")) cellSearchMethod = CellSearchMethod::spherepart;
  else if (STR_IS_EQ(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 double *reg2d_corner_lon, const 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];
}

GridCellSearch *
gridCellSearchCreateReg2d(size_t dims[2], const double *reg2d_corner_lon, const double *reg2d_corner_lat)
{
  GridCellSearch *gcs = (GridCellSearch *) Calloc(1, sizeof(GridCellSearch));

  gcs->is_reg2d = true;
  gcs->dims[0] = dims[0];
  gcs->dims[1] = dims[1];
  size_t nx = dims[0];
  size_t ny = dims[1];
  size_t nxp1 = nx + 1;
  size_t nyp1 = ny + 1;

  gcs->reg2d_corner_lon = (double *) Malloc(nxp1 * sizeof(double));
  gcs->reg2d_corner_lat = (double *) Malloc(nyp1 * sizeof(double));

  arrayCopy(nxp1, reg2d_corner_lon, gcs->reg2d_corner_lon);
  arrayCopy(nyp1, reg2d_corner_lat, gcs->reg2d_corner_lat);

  gridBoundboxReg2d(nx, ny, gcs->reg2d_corner_lon, gcs->reg2d_corner_lat, gcs->gridBoundboxReg2d);

  return gcs;
}

GridCellSearch *
gridCellSearchCreate(size_t numCells, size_t numCellCorners, double *cellCornerLon, double *cellCornerLat)
{
  GridCellSearch *gcs = (GridCellSearch *) Calloc(1, sizeof(GridCellSearch));

  gcs->method = cellSearchMethod;

  grid *yacSrcGrid = yac_scrip_grid_new(cellCornerLon, cellCornerLat, numCells, numCellCorners);
  gcs->yacSearch = yac_sphere_part_search_new(yacSrcGrid);
  gcs->yacSrcGrid = yacSrcGrid;

  return gcs;
}

void
gridCellSearchDelete(GridCellSearch *gcs)
{
  if (gcs)
    {
      if (gcs->reg2d_corner_lon) Free(gcs->reg2d_corner_lon);
      if (gcs->reg2d_corner_lat) Free(gcs->reg2d_corner_lat);

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

      Free(gcs);
    }
}

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 = gridCell.coordinates_xyz;
  if (numCellCorners == 4 && isReg2dCell)
    yac_get_cell_bounding_circle_reg_quad(xyz, xyz + 3, xyz + 6, xyz + 9, &bndCircle);
  else if (numCellCorners == 3)
    yac_get_cell_bounding_circle_unstruct_triangle(xyz, xyz + 3, xyz + 6, &bndCircle);
  else
    yac_get_cell_bounding_circle(gridCell, &bndCircle);

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

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

  yac_free_dep_list(&resultList);

  return numSrchCells;
}

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

      return nadds;
    }

  return 0;
}
