import numpy as np
import pandas as pd

from .type_converters import as_vector


def find_closest(
    closest_to, list_to_search_in, direction="both", strictly=False, return_index=False
):
    """**Find the closest number in the array from a given number x**

    Parameters
    ----------
    closest_to : float
        The target number(s) to find the closest of.
    list_to_search_in : list
        The list of values to look in.
    direction : str
        ``"both"`` for smaller or greater, ``"greater"`` for only greater numbers and ``"smaller"`` for the closest smaller.
    strictly : bool
        ``False`` for strictly superior or inferior or ``True`` for including equal.
    return_index : bool
        If ``True``, will return the index of the closest value in the list.

    Returns
    ----------
    closest : int
        The closest number in the array.

    Example
    ----------
    .. ipython:: python

      import neurokit2 as nk

      # Single number
      x = nk.find_closest(1.8, [3, 5, 6, 1, 2])
      x

      y = nk.find_closest(1.8, [3, 5, 6, 1, 2], return_index=True)
      y

      # Vectorized version
      x = nk.find_closest([1.8, 3.6], [3, 5, 6, 1, 2])
      x

    """

    # Transform to arrays
    closest_to = as_vector(closest_to)
    list_to_search_in = pd.Series(as_vector(list_to_search_in))

    out = [
        _find_closest(i, list_to_search_in, direction, strictly, return_index) for i in closest_to
    ]

    if len(out) == 1:
        return out[0]
    else:
        return np.array(out)


# =============================================================================
# Internal
# =============================================================================
def _find_closest(
    closest_to, list_to_search_in, direction="both", strictly=False, return_index=False
):

    try:
        index, closest = _find_closest_single_pandas(
            closest_to, list_to_search_in, direction, strictly
        )
    except ValueError:
        index, closest = np.nan, np.nan

    if return_index is True:
        return index
    else:
        return closest


# =============================================================================
# Methods
# =============================================================================


def _findclosest_base(x, vals, direction="both", strictly=False):
    if direction == "both":
        closest = min(vals, key=lambda y: np.abs(y - x))
    if direction == "smaller":
        if strictly is True:
            closest = max(y for y in vals if y < x)
        else:
            closest = max(y for y in vals if y <= x)
    if direction == "greater":
        if strictly is True:
            closest = min(filter(lambda y: y > x, vals))
        else:
            closest = min(filter(lambda y: y >= x, vals))

    return closest


def _find_closest_single_pandas(x, vals, direction="both", strictly=False):

    if direction in ["both", "all"]:
        index = (np.abs(vals - x)).idxmin()

    if direction in ["smaller", "below"]:
        if strictly is True:
            index = (np.abs(vals[vals < x] - x)).idxmin()
        else:
            index = (np.abs(vals[vals <= x] - x)).idxmin()

    if direction in ["greater", "above"]:
        if strictly is True:
            index = (vals[vals > x] - x).idxmin()
        else:
            index = (vals[vals >= x] - x).idxmin()

    closest = vals[index]

    return index, closest
