#' Find all neighbors in range
#'
#' Find all neighbors within a given distance for each point in a data set.
#' 
#' @inheritParams findKNN-methods
#' @param threshold A numeric scalar or vector specifying the maximum distance for considering neighbors.
#' @param ... Further arguments to pass to specific methods.
#'     This is guaranteed to include \code{subset}, \code{get.index}, \code{get.distance} \code{BPPARAM} and \code{raw.index}.
#'     See \code{?"\link{findNeighbors-functions}"} for more details.
#' 
#' @return
#' A list is returned containing \code{index}, a list of integer vectors specifying the identities of the neighbors of each point;
#' and \code{distance}, a list of numeric vectors containing the distances to those neighbors.
#' See \code{?"\link{findNeighbors-functions}"} for more details.
#' 
#' @details
#' The class of \code{BNINDEX} and \code{BNPARAM} will determine the dispatch to specific functions.
#' Only one of these arguments needs to be defined to resolve dispatch.
#' However, if both are defined, they cannot specify different algorithms.
#' 
#' If \code{BNINDEX} is supplied, \code{X} does not need to be specified.
#' In fact, any value of \code{X} will be ignored as all necessary information for the search is already present in \code{BNINDEX}.
#' Similarly, any parameters in \code{BNPARAM} will be ignored.
#' 
#' If both \code{BNINDEX} and \code{BNPARAM} are missing, the function will default to the KMKNN algorithm by setting \code{BNPARAM=KmknnParam()}.
#' 
#' @author
#' Aaron Lun
#' 
#' @seealso
#' \code{\link{rangeFindKmknn}}
#' and \code{\link{rangeFindVptree}} for specific methods.
#' 
#' @examples
#' Y <- matrix(rnorm(100000), ncol=20)
#' k.out <- findNeighbors(Y, threshold=3)
#' a.out <- findNeighbors(Y, threshold=3, BNPARAM=VptreeParam())
#' 
#' k.dex <- buildKmknn(Y)
#' k.out2 <- findNeighbors(Y, threshold=3, BNINDEX=k.dex)
#' k.out3 <- findNeighbors(Y, threshold=3, BNINDEX=k.dex, BNPARAM=KmknnParam())
#' 
#' v.dex <- buildVptree(Y)
#' v.out2 <- findNeighbors(Y, threshold=3, BNINDEX=v.dex)
#' v.out3 <- findNeighbors(Y, threshold=3, BNINDEX=v.dex, BNPARAM=VptreeParam())
#'
#' @aliases
#' findNeighbors,missing,missing-method
#' 
#' findNeighbors,missing,KmknnParam-method
#' findNeighbors,KmknnIndex,missing-method
#' findNeighbors,KmknnIndex,KmknnParam-method
#' 
#' findNeighbors,missing,VptreeParam-method
#' findNeighbors,VptreeIndex,missing-method
#' findNeighbors,VptreeIndex,VptreeParam-method
#'
#' @docType methods
#' @name findNeighbors-methods
NULL

##############
# S4 Factory #
##############

#' @importFrom BiocParallel SerialParam
.FINDNEIGHBORS_GENERATOR <- function(FUN, ARGS=spill_args) {
    function(X, threshold, ..., BNINDEX, BNPARAM) {
        do.call(FUN, c(list(X=X, threshold=threshold, ...), ARGS(BNPARAM)))
    }
}

#' @importFrom BiocParallel SerialParam
.FINDNEIGHBORS_GENERATOR_NOX <- function(FUN) {
    function(X, threshold, ..., BNINDEX, BNPARAM) {
        FUN(threshold=threshold, ..., precomputed=BNINDEX)
    }
}

####################
# Default dispatch #
####################

#' @export
setMethod("findNeighbors", c("missing", "missing"), .FINDNEIGHBORS_GENERATOR(findNeighbors, .default_param))

####################
# Specific methods #
####################

#' @export
setMethod("findNeighbors", c("missing", "KmknnParam"), .FINDNEIGHBORS_GENERATOR(rangeFindKmknn))

#' @export
setMethod("findNeighbors", c("KmknnIndex", "missing"), .FINDNEIGHBORS_GENERATOR_NOX(rangeFindKmknn))

#' @export
setMethod("findNeighbors", c("KmknnIndex", "KmknnParam"), .FINDNEIGHBORS_GENERATOR_NOX(rangeFindKmknn))

#' @export
setMethod("findNeighbors", c("missing", "VptreeParam"), .FINDNEIGHBORS_GENERATOR(rangeFindVptree))

#' @export
setMethod("findNeighbors", c("VptreeIndex", "missing"), .FINDNEIGHBORS_GENERATOR_NOX(rangeFindVptree))

#' @export
setMethod("findNeighbors", c("VptreeIndex", "VptreeParam"), .FINDNEIGHBORS_GENERATOR_NOX(rangeFindVptree))
