# -*- coding: utf-8 -*-
import matplotlib.pyplot as plt
import numpy as np

from ..epochs import epochs_create, epochs_to_array
from ..misc import as_vector
from ..signal import signal_findpeaks, signal_fixpeaks
from ..stats import fit_rmse, rescale
from .eog_features import _eog_features_delineate
from .eog_simulate import _eog_simulate_blink


def eog_findpeaks(veog_cleaned, sampling_rate=None, method="mne", **kwargs):
    """**Locate EOG eye blinks**

    Low-level function used by :func:`.eog_peaks` to identify blinks in an EOG signal using a
    different set of algorithms. See :func:`.eog_peaks` for details.

    Parameters
    ----------
    veog_cleaned : Union[list, np.array, pd.Series]
        The cleaned vertical EOG channel. Note that it must be positively oriented, i.e., blinks
        must appear as upward peaks.
    sampling_rate : int
        The signal sampling rate (in Hz, i.e., samples/second). Needed for method ``"blinker"`` or
        ``"jammes2008"``.
    method : str
        The peak detection algorithm. Can be one of ``"neurokit"``, ``"mne"`` (requires the MNE
        package to be installed), or ``"brainstorm"`` or ``"blinker"``.
    sampling_rate : int
        The sampling frequency of the EOG signal (in Hz, i.e., samples/second). Needs to be supplied
        if the method to be used is ``"'blinker"``, otherwise defaults to ``None``.
    **kwargs
        Other arguments passed to functions.

    Returns
    -------
    array
        Vector containing the samples at which EOG-peaks occur,

    See Also
    --------
    eog_peaks

    Examples
    --------
    .. ipython:: python

      import neurokit2 as nk

      # Get data
      eog_signal = nk.data('eog_100hz')
      eog_cleaned = nk.eog_clean(eog_signal, sampling_rate=100)

    * **Example 1:** NeuroKit method

    .. ipython:: python

      neurokit = nk.eog_findpeaks(eog_cleaned,
                                  sampling_rate=100,
                                  method="neurokit",
                                  threshold=0.33,
                                  show=True)
      @savefig p_eog_findpeaks1.png scale=100%
      nk.events_plot(neurokit, eog_cleaned)
      @suppress
      plt.close()

    * **Example 2:** MNE-method

    .. ipython:: python

      mne = nk.eog_findpeaks(eog_cleaned, method="mne")
      @savefig p_eog_findpeaks2.png scale=100%
      nk.events_plot(mne, eog_cleaned)
      @suppress
      plt.close()

    * **Example 3:** brainstorm method

    .. ipython:: python

      brainstorm = nk.eog_findpeaks(eog_cleaned, method="brainstorm")
      @savefig p_eog_findpeaks3.png scale=100%
      nk.events_plot(brainstorm, eog_cleaned)
      @suppress
      plt.close()

    * **Example 4:** blinker method

    .. ipython:: python

      blinker = nk.eog_findpeaks(eog_cleaned, sampling_rate=100, method="blinker")
      @savefig p_eog_findpeaks4.png scale=100%
      nk.events_plot(blinker, eog_cleaned)
      @suppress
      plt.close()

      # Jammes (2008) method
      # jammes2008 = nk.eog_findpeaks(eog_cleaned, sampling_rate=100, method="jammes2008")
      # nk.events_plot(jammes2008, eog_cleaned)

    """
    # Sanitize input
    eog_cleaned = as_vector(veog_cleaned)

    # Apply method
    method = method.lower()
    if method in ["mne"]:
        peaks = _eog_findpeaks_mne(eog_cleaned)
    elif method in ["brainstorm"]:
        peaks = _eog_findpeaks_brainstorm(eog_cleaned)
    elif method in ["blinker"]:
        peaks = _eog_findpeaks_blinker(eog_cleaned, sampling_rate=sampling_rate)
    elif method in ["neurokit", "nk"]:
        peaks = _eog_findpeaks_neurokit(eog_cleaned, sampling_rate=sampling_rate, **kwargs)
    #    elif method in ["jammes2008", "jammes"]:
    #        peaks = _eog_findpeaks_jammes2008(eog_cleaned, sampling_rate=sampling_rate)
    else:
        raise ValueError(
            "NeuroKit error: eog_peaks(): 'method' should be "
            "one of 'mne', 'brainstorm' or 'blinker'."
        )

    return peaks


# =============================================================================
# Method - NeuroKit
# =============================================================================
def _eog_findpeaks_neurokit(eog_cleaned, sampling_rate=1000, threshold=0.33, show=True):
    """In-house EOG blink detection."""
    peaks = signal_findpeaks(eog_cleaned, relative_height_min=1.25)["Peaks"]
    peaks = signal_fixpeaks(
        peaks=peaks, sampling_rate=sampling_rate, interval_min=0.2, method="neurokit"
    )
    peaks = _eog_findpeaks_neurokit_filterblinks(
        eog_cleaned, peaks, sampling_rate=sampling_rate, threshold=threshold, show=show
    )
    return peaks


def _eog_findpeaks_neurokit_filterblinks(
    eog_cleaned, peaks, sampling_rate=1000, threshold=0.5, show=False
):
    """Compare each detected event to blink template and reject it if too different."""
    # Get epoch around each blink
    events = epochs_create(
        eog_cleaned, peaks, sampling_rate=sampling_rate, epochs_start=-0.4, epochs_end=0.6
    )
    events = epochs_to_array(events)  # Convert to 2D array

    # Generate Blink-template
    template = _eog_simulate_blink(sampling_rate=sampling_rate, method="gamma")

    # Get the "distance" (RMSE) between each blink and the template
    rmse = np.full(events.shape[1], np.nan)
    for i in range(events.shape[1]):
        events[:, i] = rescale(events[:, i], to=[0, 1])  # Reshape to 0-1 scale
        rmse[i] = fit_rmse(events[:, i], template)

    # Plot RMSE distribution
    if show is True:
        plt.subplot(1, 2, 1)
        plt.hist(rmse, color="#FF9800")
        plt.axvline(x=threshold, linewidth=4, color="r")
        plt.title("RMSE Distribution (threshold = " + str(threshold) + ")")
        plt.xlabel("RMSE")

        plt.subplot(1, 2, 2)
        plt.plot(events[:, rmse < threshold], linewidth=0.25, color="black")
        plt.plot(events[:, rmse >= threshold], linewidth=0.5, color="red")
        plt.plot(template, linewidth=2, color="#2196F3", label="Blink template")
        plt.title("Accepted and rejected (red) blinks")
        plt.legend(loc="upper right")

    return peaks[rmse < threshold]


# =============================================================================
# Method - Jammes (2008)
# =============================================================================
# def _eog_findpeaks_jammes2008(eog_cleaned, sampling_rate=1000):
#    """Derivative-based method by Jammes (2008)
#
#    https://link.springer.com/article/10.1007/s11818-008-0351-y
#
#    """
#    # Derivative
#    derivative = np.gradient(eog_cleaned)
#
#    # These parameters were set by the authors "empirically". These are values based on
#    # their figure 1.
#    vcl = 0.5 * np.max(derivative)
#    vol = 0.75 * np.min(derivative)
#
#    crosses_vcl = signal_zerocrossings(derivative - vcl, direction="up")
#    crosses_vol = signal_zerocrossings(derivative - vol, direction="down")
#    crosses_vol = nk.find_closest(crosses_vcl, crosses_vol, direction="above")
#
#    nk.events_plot([crosses_vcl, crosses_vol], eog_cleaned)
#    nk.signal_plot([eog_cleaned, derivative, derivative - vol])
#    durations = (crosses_vol - crosses_vcl) / sampling_rate
#    indices = durations < 0.5
#
#    peaks = np.full(np.sum(indices), np.nan)
#    for i in range(np.sum(indices)):
#        segment = eog_cleaned[crosses_vcl[indices][i]:crosses_vol[indices][i]]
#        peaks[i] = crosses_vcl[indices][i] + np.argmax(segment)
#
#    return peaks


# =============================================================================
# Method - MNE
# =============================================================================
def _eog_findpeaks_mne(eog_cleaned):
    """EOG blink detection based on MNE.

    https://github.com/mne-tools/mne-python/blob/master/mne/preprocessing/eog.py

    """
    # Make sure MNE is installed
    try:
        import mne
    except ImportError:
        raise ImportError(
            "NeuroKit error: signal_filter(): the 'mne' module is required for this method to run. ",
            "Please install it first (`pip install mne`).",
        )

    # Find peaks
    eog_events, _ = mne.preprocessing.peak_finder(eog_cleaned, extrema=1, verbose=False)

    return eog_events


# =============================================================================
# Method - Brainstorm
# =============================================================================
def _eog_findpeaks_brainstorm(eog_cleaned):
    """EOG blink detection implemented in brainstorm.

    https://neuroimage.usc.edu/brainstorm/Tutorials/ArtifactsDetect#Detection:_Blinks

    """
    # Brainstorm: "An event of interest is detected if the absolute value of the filtered
    # signal value goes over a given number of times the standard deviation. For EOG: 2xStd."
    # -> Remove all peaks that correspond to regions < 2 SD
    peaks = signal_findpeaks(eog_cleaned, relative_height_min=2)["Peaks"]

    return peaks


# =============================================================================
# Method - blinker
# =============================================================================
def _eog_findpeaks_blinker(eog_cleaned, sampling_rate=1000):
    """EOG blink detection based on BLINKER algorithm.

    Detects only potential blink landmarks and does not separate blinks from other artifacts yet.
    https://www.frontiersin.org/articles/10.3389/fnins.2017.00012/full

    """
    # Establish criterion
    threshold = 1.5 * np.std(eog_cleaned) + eog_cleaned.mean()
    min_blink = 0.05 * sampling_rate  # min blink frames

    potential_blinks = []
    for i, signal in enumerate(eog_cleaned):
        if signal > threshold:
            potential_blinks.append(i)

    # Make sure each blink is 50ms long and separated by 50ms
    indexes = np.where(np.diff(potential_blinks) > min_blink)[0]
    individual_blinks = np.split(np.diff(potential_blinks), indexes)

    blinks = []
    for idx, i in enumerate(individual_blinks):
        if len(i) > min_blink:
            blinks.append(idx)

    candidates = np.array(potential_blinks)[np.append(0, indexes)[blinks]]

    _, peaks, _, _, _, _ = _eog_features_delineate(
        eog_cleaned, candidates, sampling_rate=sampling_rate
    )

    # Blink peak markers
    peaks = np.array(peaks)

    return peaks
