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

  Author: Uwe Schulzweida

*/

#ifndef POINTSEARCH_HEALPIX_H
#define POINTSEARCH_HEALPIX_H

#include <array>

#include "cdo_math.h"
#include "point.h"
#include "knndata.h"
#include "grid_convert.h"
#include "grid_healpix.h"

class PointsearchHealpix
{
public:
  explicit PointsearchHealpix(const HpParams &hpParams) : m_hpParams(hpParams) {}
  ~PointsearchHealpix() {}

  /* not used because result differ from search() and unstruct::search()
  void
  search_1nn(PointLonLat const &pointLL, knnDataType &knnData, double searchRadius)
  {
    size_t index = (size_t) hp_lonlat_to_index(m_hpParams, pointLL.get_lon(), pointLL.get_lat());

    size_t numIndices = 1;

    double lon, lat;
    hp_index_to_lonlat(m_hpParams, index, &lon, &lat);

    double distance;
    store_distance_healpix(searchRadius, pointLL, knnData, numIndices, &index, &distance, &lon, &lat);
   }
   */

  void
  search(PointLonLat const &pointLL, KnnData &knnData, double searchRadius)
  {
    auto index = hp_lonlat_to_index(m_hpParams, pointLL.lon(), pointLL.lat());

    int64_t neighbours[8];
    hp_get_neighbours(m_hpParams, index, neighbours);

    std::array<size_t, 9> indices;
    indices[0] = index;
    size_t numIndices = 1;
    for (int i = 0; i < 8; ++i)
      if (neighbours[i] >= 0) indices[numIndices++] = neighbours[i];

    std::array<double, 9> lons{}, lats{};
    for (size_t i = 0; i < numIndices; ++i) hp_index_to_lonlat(m_hpParams, indices[i], &lons[i], &lats[i]);

    store_distance_healpix(searchRadius, pointLL, knnData, numIndices, indices, lons, lats);

    if (knnData.m_needCoords)
    {
      lonlat_to_xyz(pointLL.lon(), pointLL.lat(), knnData.m_tgtCoord);
      auto numNeighbors = knnData.m_numNeighbors;
      for (size_t i = 0; i < numNeighbors; ++i)
      {
        double lon, lat;
        hp_index_to_lonlat(m_hpParams, knnData.m_indices[i], &lon, &lat);
        lonlat_to_xyz(lon, lat, knnData.m_srcCoords[i]);
      }
    }
  }

private:
  HpParams m_hpParams;

  static void
  store_distance_healpix(double searchRadius, PointLonLat const &pointLL, KnnData &knnData, size_t numIndices,
                         std::array<size_t, 9> &indices, std::array<double, 9> const &lons, std::array<double, 9> const &lats)
  {
    std::array<double, 3> tgtCoord;
    lonlat_to_xyz(pointLL.lon(), pointLL.lat(), tgtCoord);

    auto sqrSearchRadius = cdo::sqr(searchRadius);

    std::array<double, 9> distances;
    std::array<double, 3> srcCoord;
    size_t numWeights = 0;
    for (size_t i = 0; i < numIndices; ++i)
    {
      lonlat_to_xyz(lons[i], lats[i], srcCoord);
      // Find distance to this point
      double sqrDistance = static_cast<float>(cdo::sqr_distance(tgtCoord, srcCoord));
      // Store the index and distance if this is one of the smallest so far
      if (sqrDistance <= sqrSearchRadius)
      {
        indices[numWeights] = indices[i];
        distances[numWeights] = std::sqrt(sqrDistance);
        numWeights++;
      }
    }

    auto maxNeighbors = std::min(numWeights, knnData.maxNeighbors());
    for (size_t i = 0; i < numWeights; ++i) { knnData.store_distance(indices[i], distances[i], maxNeighbors); }

    knnData.check_distance();
  }
};

#endif
