cimport numpy as cnp

from libcpp.memory cimport shared_ptr
from libcpp.vector cimport vector
from cython cimport final

from ...utils._typedefs cimport intp_t, float64_t

cnp.import_array()

######################
## std::vector to np.ndarray coercion
# As type covariance is not supported for C++ containers via Cython,
# we need to redefine fused types.
ctypedef fused vector_double_intp_t:
    vector[intp_t]
    vector[float64_t]


ctypedef fused vector_vector_double_intp_t:
    vector[vector[intp_t]]
    vector[vector[float64_t]]

cdef cnp.ndarray[object, ndim=1] coerce_vectors_to_nd_arrays(
    shared_ptr[vector_vector_double_intp_t] vecs
)

#####################
{{for name_suffix in ['64', '32']}}

from ._base cimport BaseDistancesReduction{{name_suffix}}
from ._middle_term_computer cimport MiddleTermComputer{{name_suffix}}

cdef class RadiusNeighbors{{name_suffix}}(BaseDistancesReduction{{name_suffix}}):
    """float{{name_suffix}} implementation of the RadiusNeighbors."""

    cdef:
        float64_t radius

        # DistanceMetric{{name_suffix}} compute rank-preserving surrogate distance via rdist
        # which are proxies necessitating less computations.
        # We get the equivalent for the radius to be able to compare it against
        # vectors' rank-preserving surrogate distances.
        float64_t r_radius

        # Neighbors indices and distances are returned as np.ndarrays of np.ndarrays.
        #
        # For this implementation, we want resizable buffers which we will wrap
        # into numpy arrays at the end. std::vector comes as a handy container
        # for interacting efficiently with resizable buffers.
        #
        # Though it is possible to access their buffer address with
        # std::vector::data, they can't be stolen: buffers lifetime
        # is tied to their std::vector and are deallocated when
        # std::vectors are.
        #
        # To solve this, we dynamically allocate std::vectors and then
        # encapsulate them in a StdVectorSentinel responsible for
        # freeing them when the associated np.ndarray is freed.
        #
        # Shared pointers (defined via shared_ptr) are use for safer memory management.
        # Unique pointers (defined via unique_ptr) can't be used as datastructures
        # are shared across threads for parallel_on_X; see _parallel_on_X_init_chunk.
        shared_ptr[vector[vector[intp_t]]] neigh_indices
        shared_ptr[vector[vector[float64_t]]] neigh_distances

        # Used as array of pointers to private datastructures used in threads.
        vector[shared_ptr[vector[vector[intp_t]]]] neigh_indices_chunks
        vector[shared_ptr[vector[vector[float64_t]]]] neigh_distances_chunks

        bint sort_results

    @final
    cdef void _merge_vectors(
        self,
        intp_t idx,
        intp_t num_threads,
    ) noexcept nogil


cdef class EuclideanRadiusNeighbors{{name_suffix}}(RadiusNeighbors{{name_suffix}}):
    """EuclideanDistance-specialisation of RadiusNeighbors{{name_suffix}}."""
    cdef:
        MiddleTermComputer{{name_suffix}} middle_term_computer
        const float64_t[::1] X_norm_squared
        const float64_t[::1] Y_norm_squared

        bint use_squared_distances

{{endfor}}
