/**
 * @file grid_search_utils.c
 *
 * @copyright Copyright  (C)  2014 Moritz Hanke <hanke@dkrz.de>
 *
 * @version 1.0
 * @author Moritz Hanke <hanke@dkrz.de>
 */
/*
 * Keywords:
 * Maintainer: Moritz Hanke <hanke@dkrz.de>
 * URL: https://doc.redmine.dkrz.de/YAC/html/index.html
 *
 * This file is part of YAC.
 *
 * YAC 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, either version 3 of the License, or
 * (at your option) any later version.
 *
 * YAC 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with YAC.  If not, see <http://www.gnu.org/licenses/gpl.txt>.
 */

#include <stdlib.h>

#include "grid_search_utils.h"
#include "ensure_array_size.h"
#include "utils.h"
#include "string.h"

struct dep_list *
yac_grid_search_utils_do_point_search_p(struct grid_search * search,
                                        struct grid * search_grid_data,
                                        struct grid * grid_data) {
 
   // determine the number of target points
   size_t num_target_corners = (size_t)yac_get_num_grid_corners(grid_data);

   // do the actual point search
   struct dep_list * tgt_corner_to_src_cell =
      yac_do_point_search_c(search, grid_data);

   // compute the number of src corners per cell and the total number of
   // "target corner to source corner"-dependencies
   size_t * num_src_per_tgt =
      calloc(num_target_corners, sizeof(*num_src_per_tgt));
   size_t num_total_src_deps = 0;

   for (size_t i = 0; i < num_target_corners; ++i) {
      if (yac_get_num_dependencies_of_element(tgt_corner_to_src_cell, i) == 1) {

         unsigned curr_src_cell =
            yac_get_dependencies_of_element(tgt_corner_to_src_cell, i)[0];
         num_src_per_tgt[i] =
            (size_t)yac_get_num_cell_corners(search_grid_data, curr_src_cell);
         num_total_src_deps += num_src_per_tgt[i];
      } else {
         num_src_per_tgt[i] = 0;
      }
   }

   unsigned * tgt_to_src;

   // generate dependency array
   tgt_to_src = malloc(num_total_src_deps * sizeof (tgt_to_src[0]));
   num_total_src_deps = 0;

   for (size_t i = 0; i < num_target_corners; ++i) {

      if (yac_get_num_dependencies_of_element(tgt_corner_to_src_cell, i) == 1) {

         unsigned curr_src_cell =
            yac_get_dependencies_of_element(tgt_corner_to_src_cell, i)[0];
         unsigned const * curr_src_corners =
            yac_get_cell_corner_indices (search_grid_data, curr_src_cell);
         for (size_t j = 0; j < num_src_per_tgt[i]; ++j)
            tgt_to_src[num_total_src_deps++] = curr_src_corners[j];
      }
   }

   yac_dep_list_delete(tgt_corner_to_src_cell);

   struct dep_list * target_to_src_points =
      yac_dep_list_new(num_target_corners, num_src_per_tgt, tgt_to_src);
   return target_to_src_points;
}

struct dep_list *
yac_grid_search_utils_do_point_search_p2 (struct grid_search * search,
                                          struct grid * search_grid_data,
                                          double (*coordinates_xyz)[3],
                                          size_t num_points) {

   // do the actual point search
   struct dep_list * tgt_point_to_src_cell =
      yac_do_point_search_c2(search, coordinates_xyz, num_points);

   // compute the number of src corners per cell and the total number of
   // "target corner to source corner"-dependencies
   size_t * num_src_per_tgt = calloc (num_points, sizeof(*num_src_per_tgt));
   size_t num_total_src_deps = 0;

   unsigned * src_cells = malloc (num_points * sizeof(*src_cells));

   for (size_t i = 0; i < num_points; ++i) {

      size_t num_cells =
         yac_get_num_dependencies_of_element(tgt_point_to_src_cell, i);

      if (num_cells > 0) {

         unsigned const * dependencies =
            yac_get_dependencies_of_element(tgt_point_to_src_cell, i);

         unsigned src_cell = dependencies[0];

         for (size_t j = 1; j < num_cells; ++j)
            if (src_cell > dependencies[j])
               src_cell = dependencies[j];

         src_cells[i] = src_cell;

         num_src_per_tgt[i] =
            (size_t)yac_get_num_cell_corners(search_grid_data, src_cell);
         num_total_src_deps += num_src_per_tgt[i];
      } else {
         src_cells[i] = -1;
         num_src_per_tgt[i] = 0;
      }
   }

   unsigned * tgt_to_src;
   unsigned const * curr_src_corners;

   // generate dependency array
   tgt_to_src = malloc (num_total_src_deps * sizeof (tgt_to_src[0]));
   num_total_src_deps = 0;

   for (size_t i = 0; i < num_points; ++i) {

      if (src_cells[i] != -1) {

         curr_src_corners = yac_get_cell_corner_indices (search_grid_data,
                                                     src_cells[i]);
         for (size_t j = 0; j < num_src_per_tgt[i]; ++j) {

            tgt_to_src[num_total_src_deps++] = curr_src_corners[j];
         }
      }
   }

   free(src_cells);

   yac_dep_list_delete(tgt_point_to_src_cell);

   struct dep_list * target_to_src_points =
      yac_dep_list_new(num_points, num_src_per_tgt, tgt_to_src);
   return target_to_src_points;
}

static unsigned check_grid_cells(
  double point_coords[3], struct grid * grid,
  unsigned const * cell_indices, size_t num_cells,
  struct grid_cell * tmp_cell) {

  struct grid_cell cell = *tmp_cell;

  for (size_t i = 0; i < num_cells; ++i) {

    struct bounding_circle bnd_circle;
    yac_get_grid_cell2(grid, cell_indices[i], &cell, &bnd_circle);

    if (yac_point_in_cell2(point_coords, cell, bnd_circle)) {

      *tmp_cell = cell;
      return cell_indices[i];
    }
  }

  *tmp_cell = cell;
  return -1;
}

static unsigned check_point_grid_cells_CELL_(
  double point_coords[3], unsigned base_cell_index, struct grid * base_grid,
  struct grid * point_grid, struct grid_cell * tmp_cell) {

  UNUSED(base_grid);

  // the base cell index is a corner of the point grid
  size_t num_point_cells =
    (size_t)yac_get_num_corner_cells(point_grid, base_cell_index);
  unsigned const * point_cell_indices =
    yac_get_corner_cell_indices(point_grid, base_cell_index);

  return check_grid_cells(point_coords, point_grid, point_cell_indices,
                          num_point_cells, tmp_cell);
}

static unsigned check_point_grid_cells_CORNER_(
  double point_coords[3], unsigned base_cell_index, struct grid * base_grid,
  struct grid * point_grid, struct grid_cell * tmp_cell) {

  struct bounding_circle bnd_circle;
  yac_get_grid_cell2(point_grid, base_cell_index, tmp_cell, &bnd_circle);

  if (yac_point_in_cell2(point_coords, *tmp_cell, bnd_circle))
    return base_cell_index;

  // check the neighbour cells
  struct dep_list * cell_neigh_deps = yac_get_cell_neigh_dep_list(base_grid);
  size_t num_neigh_cells =
    yac_get_num_dependencies_of_element(cell_neigh_deps, (size_t)base_cell_index);
  unsigned const * neigh_cells =
    yac_get_dependencies_of_element(cell_neigh_deps, (size_t)base_cell_index);

  return check_grid_cells(point_coords, point_grid, neigh_cells,
                          num_neigh_cells, tmp_cell);
}

static unsigned check_point_grid_cells_EDGE_(
  double point_coords[3], unsigned base_cell_index, struct grid * base_grid,
  struct grid * point_grid, struct grid_cell * tmp_cell) {

  unsigned const * base_cell_edges =
    yac_get_cell_edge_indices(base_grid, base_cell_index);
  size_t num_base_cell_edges =
    (size_t)yac_get_num_cell_edges(base_grid, base_cell_index);

  // with this algorithm many cell are checked twice...

  for (size_t i = 0; i < num_base_cell_edges; ++i) {

    unsigned const * point_cell_indices =
      yac_get_corner_cell_indices(point_grid, base_cell_edges[i]);
    size_t num_point_cells =
      (size_t)yac_get_num_corner_cells(point_grid, base_cell_edges[i]);

    unsigned ret_val =
      check_grid_cells(point_coords, point_grid, point_cell_indices,
                       num_point_cells, tmp_cell);

    if (ret_val != -1) return ret_val;
  }

  return -1;
}

static int check_point_grid_cells(
  double coordinate_xyz[3], unsigned base_cell_index,
  struct grid * base_grid, struct grid * point_grid,
  unsigned (*check_point_grid_cells_)(
    double point_coords[3], unsigned base_cell_index,
    struct grid * base_grid, struct grid * point_grid,
    struct grid_cell * tmp_cell), struct grid_cell * tmp_cell,
  size_t * num_matching_cell_corners, unsigned ** matching_cell_corners,
  unsigned * matching_cell_corners_array_size, size_t offset) {

  unsigned point_cell_index =
    check_point_grid_cells_(
      coordinate_xyz, base_cell_index, base_grid, point_grid, tmp_cell);

  // if a matching point cell was found
  if (point_cell_index != -1) {

    size_t num_point_cell_corners =
      (size_t)yac_get_num_cell_corners(point_grid, point_cell_index);

    unsigned const * corner_indices =
      yac_get_cell_corner_indices(point_grid, point_cell_index);

    ENSURE_ARRAY_SIZE(*matching_cell_corners, *matching_cell_corners_array_size,
                      num_point_cell_corners + offset);
    *num_matching_cell_corners = num_point_cell_corners;

    for (size_t i = 0; i < num_point_cell_corners; ++i)
      (*matching_cell_corners)[offset+i] = corner_indices[i];
  } else return 0;

  return 1;
}

struct dep_list *
yac_grid_search_utils_do_point_search_p3(struct grid_search * search,
                                         double (*coordinates_xyz)[3],
                                         size_t num_points,
                                         struct points * points) {


  // start with a cell search
  struct dep_list * tgt_point_to_src_cell =
    yac_do_point_search_c2(search, coordinates_xyz, num_points);

  unsigned * tgt_point_to_src_points_dep = NULL;
  unsigned tgt_point_to_src_points_dep_array_size = 0;
  size_t * num_dep_per_tgt_point =
    calloc(num_points, sizeof(*num_dep_per_tgt_point));
  size_t total_num_deps = 0;

  unsigned (*check_point_grid_cells_)(
    double point_coords[3], unsigned base_cell_index, struct grid * base_grid,
    struct grid * point_grid, struct grid_cell * tmp_cell);
  struct grid_cell tmp_cell;
  struct grid * base_grid = yac_get_base_grid(points);
  struct grid * point_grid = yac_get_point_grid(points);

  switch (points->location) {
    case (CORNER):
      check_point_grid_cells_ = check_point_grid_cells_CORNER_;
      break;
    case (CELL):
      check_point_grid_cells_ = check_point_grid_cells_CELL_;
      break;
    case (EDGE):
      check_point_grid_cells_ = check_point_grid_cells_EDGE_;
      break;
    default:
      yac_internal_abort_message(
        "ERROR: unknown point location type in do_point_search_p3",
        __FILE__, __LINE__);
  }

  yac_init_grid_cell(&tmp_cell);

  // for all points to be search
  for (size_t i = 0; i < num_points; ++i) {

    size_t num_matching_cells =
      yac_get_num_dependencies_of_element(tgt_point_to_src_cell, i);
    unsigned const * matching_cells =
      yac_get_dependencies_of_element(tgt_point_to_src_cell, i);

    for (size_t j = 0; j < num_matching_cells; ++j) {

      // check method point cells of current cell
      if (check_point_grid_cells(
            coordinates_xyz[i], matching_cells[j], base_grid, point_grid,
            check_point_grid_cells_, &tmp_cell, num_dep_per_tgt_point+i,
            &tgt_point_to_src_points_dep,
            &tgt_point_to_src_points_dep_array_size, total_num_deps)) {

        total_num_deps += num_dep_per_tgt_point[i];
        break;
      }
    }
  }

  yac_free_grid_cell(&tmp_cell);
  yac_dep_list_delete(tgt_point_to_src_cell);

  tgt_point_to_src_points_dep =
    realloc(tgt_point_to_src_points_dep,
            total_num_deps * sizeof(*tgt_point_to_src_points_dep));

  struct dep_list * target_to_src_points =
    yac_dep_list_new(
      num_points, num_dep_per_tgt_point, tgt_point_to_src_points_dep);
  return target_to_src_points;
}

void yac_grid_search_utils_do_point_search_p4 (struct grid_search * search,
                                               struct grid * search_grid_data,
                                               double coordinate_xyz[3],
                                               size_t * n_points,
                                               size_t * points_size,
                                               size_t ** points) {


   // do the actual point search
   struct dep_list * tgt_point_to_src_cell =
      yac_do_point_search_c2(search, (double(*)[3])&(coordinate_xyz[0]), 1);

   size_t num_cells =
      yac_get_num_dependencies_of_element(tgt_point_to_src_cell, 0);

   if (num_cells > 0) {

      unsigned const * dependencies =
         yac_get_dependencies_of_element(tgt_point_to_src_cell, 0);

      unsigned src_cell = dependencies[0];

      for (size_t i = 1; i < num_cells; ++i)
         if (src_cell > dependencies[i])
            src_cell = dependencies[i];

      *n_points = (size_t)yac_get_num_cell_corners(search_grid_data, src_cell);
      ENSURE_ARRAY_SIZE(*points, *points_size, *n_points);

      unsigned const * src_corners =
         yac_get_cell_corner_indices(search_grid_data, src_cell);
      for (size_t i = 0; i < *n_points; ++i)
         (*points)[i] = (size_t)(src_corners[i]);
   } else {
      *n_points = 0;
   }

   yac_dep_list_delete(tgt_point_to_src_cell);
}

static size_t get_num_point_grid_cells_CELL(
  struct grid * point_grid, struct grid * base_grid, size_t num_cells,
  unsigned const * base_grid_cell_indices) {

  UNUSED(base_grid);

  size_t num_point_grid_cells = 0;

  // the base cell index is a corner of the point grid
  for (size_t i = 0; i < num_cells; ++i)
    num_point_grid_cells +=
      (size_t)yac_get_num_corner_cells(point_grid, base_grid_cell_indices[i]);

  return num_point_grid_cells;
}

static size_t get_num_point_grid_cells_CORNER(
  struct grid * point_grid, struct grid * base_grid, size_t num_cells,
  unsigned const * base_grid_cell_indices) {

  UNUSED(point_grid);

  size_t num_point_grid_cells = num_cells;

  // the neighbour cells
  struct dep_list * cell_neigh_deps = yac_get_cell_neigh_dep_list(base_grid);
  for (size_t i = 0; i < num_cells; ++i)
    num_point_grid_cells +=
      yac_get_num_dependencies_of_element(
         cell_neigh_deps, (size_t)base_grid_cell_indices[i]);

  return num_point_grid_cells;
}

static size_t get_num_point_grid_cells_EDGE(
  struct grid * point_grid, struct grid * base_grid, size_t num_cells,
  unsigned const * base_grid_cell_indices) {

  size_t num_point_grid_cells = 0;

  for (size_t i = 0; i < num_cells; ++i) {

    unsigned const * base_cell_edges =
      yac_get_cell_edge_indices(base_grid, base_grid_cell_indices[i]);
    size_t num_base_cell_edges =
      (size_t)yac_get_num_cell_edges(base_grid, base_grid_cell_indices[i]);

    // with this algorithm many cell are checked twice...

    for (size_t j = 0; j < num_base_cell_edges; ++j)
      num_point_grid_cells +=
        (size_t)yac_get_num_corner_cells(point_grid, base_cell_edges[j]);
  }

  return num_point_grid_cells;
}

static void get_point_grid_cell_indices_CELL(
  struct grid * point_grid, struct grid * base_grid, size_t num_cells,
  unsigned const * base_grid_cell_indices, unsigned * point_grid_cell_indices) {

  UNUSED(base_grid);

  size_t num_point_grid_cells = 0;

  // the base cell index is a corner of the point grid
  for (size_t i = 0; i < num_cells; ++i) {

    // the base cell index is a corner of the point grid
    size_t curr_num_point_cells =
      (size_t)yac_get_num_corner_cells(point_grid, base_grid_cell_indices[i]);

    memcpy(
      point_grid_cell_indices + num_point_grid_cells,
      yac_get_corner_cell_indices(point_grid, base_grid_cell_indices[i]),
      curr_num_point_cells * sizeof(*point_grid_cell_indices));

    num_point_grid_cells += curr_num_point_cells;
  }
}

static void get_point_grid_cell_indices_CORNER(
  struct grid * point_grid, struct grid * base_grid, size_t num_cells,
  unsigned const * base_grid_cell_indices, unsigned * point_grid_cell_indices) {

  UNUSED(point_grid);

  size_t num_point_grid_cells;

  for (num_point_grid_cells = 0; num_point_grid_cells < num_cells;
       ++num_point_grid_cells)
    point_grid_cell_indices[num_point_grid_cells] =
      base_grid_cell_indices[num_point_grid_cells];

  // the neighbour cells
  struct dep_list * cell_neigh_deps = yac_get_cell_neigh_dep_list(base_grid);
  for (size_t i = 0; i < num_cells; ++i) {

    unsigned const * curr_point_cells =
      yac_get_dependencies_of_element(
        cell_neigh_deps, (size_t)base_grid_cell_indices[i]);
    size_t curr_num_point_cells =
      yac_get_num_dependencies_of_element(
         cell_neigh_deps, (size_t)base_grid_cell_indices[i]);

    memcpy(
      point_grid_cell_indices + num_point_grid_cells, curr_point_cells,
      curr_num_point_cells * sizeof(*point_grid_cell_indices));

    num_point_grid_cells += curr_num_point_cells;
  }
}

static void get_point_grid_cell_indices_EDGE(
  struct grid * point_grid, struct grid * base_grid, size_t num_cells,
  unsigned const * base_grid_cell_indices, unsigned * point_grid_cell_indices) {

  size_t num_point_grid_cells = 0;

  for (size_t i = 0; i < num_cells; ++i) {

    unsigned const * base_cell_edges =
      yac_get_cell_edge_indices(base_grid, base_grid_cell_indices[i]);
    size_t num_base_cell_edges =
      (size_t)yac_get_num_cell_edges(base_grid, base_grid_cell_indices[i]);

    // with this algorithm many cell are checked twice...

    for (size_t j = 0; j < num_base_cell_edges; ++j) {

      size_t curr_num_point_cells =
        (size_t)yac_get_num_corner_cells(point_grid, base_cell_edges[j]);

      memcpy(
        point_grid_cell_indices + num_point_grid_cells,
        yac_get_corner_cell_indices(point_grid, base_cell_edges[j]),
        curr_num_point_cells * sizeof(*point_grid_cell_indices));

      num_point_grid_cells += curr_num_point_cells;
    }
  }
}

static int compare_uint (const void * a, const void * b) {
  return ( *(unsigned*)a - *(unsigned*)b );
}

struct dep_list *
yac_grid_search_utils_do_point_search_c3 (struct grid_search * search,
                                          double (*coordinates_xyz)[3],
                                          size_t num_points,
                                          struct points * points) {


  // start with a cell search
  struct dep_list * tgt_point_to_src_cell =
    yac_do_point_search_c2(search, coordinates_xyz, num_points);

  size_t (*get_num_point_grid_cells)(
    struct grid * point_grid, struct grid * base_grid, size_t num_cells,
    unsigned const * base_grid_cell_indices);
  void (*get_point_grid_cell_indices)(
    struct grid * point_grid, struct grid * base_grid, size_t num_cells,
    unsigned const * base_grid_cell_indices, unsigned * point_grid_cell_indices);
  struct grid * point_grid = yac_get_point_grid(points);
  struct grid * base_grid = yac_get_base_grid(points);

  switch (points->location) {
    case (CORNER):
      get_num_point_grid_cells = get_num_point_grid_cells_CORNER;
      get_point_grid_cell_indices = get_point_grid_cell_indices_CORNER;
      break;
    case (CELL):
      get_num_point_grid_cells = get_num_point_grid_cells_CELL;
      get_point_grid_cell_indices = get_point_grid_cell_indices_CELL;
      break;
    case (EDGE):
      get_num_point_grid_cells = get_num_point_grid_cells_EDGE;
      get_point_grid_cell_indices = get_point_grid_cell_indices_EDGE;
      break;
    default:
      yac_internal_abort_message(
        "ERROR: unknown point location type in do_point_search_p3",
        __FILE__, __LINE__);
  }

  unsigned * tgt_point_to_src_cell_dep = NULL;
  size_t tgt_point_to_src_points_dep_array_size = 0;
  size_t * num_dep_per_tgt_point =
    malloc(num_points * sizeof(*num_dep_per_tgt_point));
  size_t total_num_deps = 0;

  size_t max_num_point_grid_cells = 0;
  {
    // for all points to be search
    for (size_t i = 0; i < num_points; ++i) {

      // get number of potentially matching point grid cells
      size_t num_cells =
        yac_get_num_dependencies_of_element(tgt_point_to_src_cell, i);
      unsigned const * cell_indices =
        yac_get_dependencies_of_element(tgt_point_to_src_cell, i);
      size_t num_point_grid_cells =
        get_num_point_grid_cells(
          point_grid, base_grid, num_cells, cell_indices);

      if (num_point_grid_cells > max_num_point_grid_cells)
        max_num_point_grid_cells = num_point_grid_cells;

      num_dep_per_tgt_point[i] = num_point_grid_cells;
    }
  }

  unsigned * tmp_point_grid_cell_indices =
    malloc(max_num_point_grid_cells * sizeof(*tmp_point_grid_cell_indices));
  struct grid_cell tmp_cell;
  yac_init_grid_cell(&tmp_cell);

  // for all points to be search
  for (size_t i = 0; i < num_points; ++i) {

    // get point grid cell indices
    size_t num_cells =
      yac_get_num_dependencies_of_element(tgt_point_to_src_cell, i);
    unsigned const * cell_indices =
      yac_get_dependencies_of_element(tgt_point_to_src_cell, i);
    get_point_grid_cell_indices(point_grid, base_grid, num_cells, cell_indices,
                                tmp_point_grid_cell_indices);

    size_t num_point_grid_cells = num_dep_per_tgt_point[i];

    // sort point grid cell indices and remove duplicates
    qsort(tmp_point_grid_cell_indices, num_point_grid_cells,
          sizeof(*tmp_point_grid_cell_indices), compare_uint);
    yac_remove_duplicates_uint(
      tmp_point_grid_cell_indices, &num_point_grid_cells);

    ENSURE_ARRAY_SIZE(
      tgt_point_to_src_cell_dep, tgt_point_to_src_points_dep_array_size,
      total_num_deps + num_point_grid_cells);

    size_t matching_point_grid_cells = 0;

    // check point grid cells
    for (size_t j = 0; j < num_point_grid_cells; ++j) {

      struct bounding_circle bnd_circle;
      yac_get_grid_cell2(
        point_grid, tmp_point_grid_cell_indices[j], &tmp_cell, &bnd_circle);

      if (yac_point_in_cell2(coordinates_xyz[i], tmp_cell, bnd_circle)) {

        tgt_point_to_src_cell_dep[total_num_deps++] =
          tmp_point_grid_cell_indices[j];
        ++matching_point_grid_cells;
      }
    }

    num_dep_per_tgt_point[i] = matching_point_grid_cells;
  }

  yac_free_grid_cell(&tmp_cell);
  free(tmp_point_grid_cell_indices);
  yac_dep_list_delete(tgt_point_to_src_cell);

  tgt_point_to_src_cell_dep =
    realloc(tgt_point_to_src_cell_dep,
            total_num_deps * sizeof(*tgt_point_to_src_cell_dep));

  struct dep_list * tgt_to_src_cells =
    yac_dep_list_new(
      num_points, num_dep_per_tgt_point, tgt_point_to_src_cell_dep);
  return tgt_to_src_cells;

}

