"""Time series labelling."""
# License : Apache 2.0

import types
import numpy as np
import numbers
from sklearn.base import BaseEstimator
from sklearn.utils.validation import check_is_fitted, column_or_1d
from ..base import TransformerResamplerMixin
from .embedding import SlidingWindow
from ..utils.validation import validate_params

import warnings

warnings.warn(
    "Starting at v0.1.4, this package was renamed as 'giotto-tda'. The "
    "giotto-learn PyPI package will no longer be developed or maintained, and "
    "will remain at the state of v0.1.3. Please visit "
    "https://github.com/giotto-ai/giotto-tda to find installation information "
    "for giotto-tda.")


class Labeller(BaseEstimator, TransformerResamplerMixin):
    """Target creation from sliding windows over a time series.

    Useful to define a time series forecasting task in which labels are
    obtained from future values of the input time series, via the
    application of a function to time windows.

    Parameters
    ----------
    width : int, optional, default: ``10``
        Width of each sliding window. Each window contains ``width + 1``
        objects from the original time series.

    func : callable, optional, default: ``numpy.std``
        Function to be applied to each window.

    func_params : dict, optional, default: ``{}``
        Additional keyword arguments for `func`.

    percentiles : list of ordered floats between 0 and 1 or None, optional, \
                  default: ``None``
        If ``None``, creates a target for a regression task. Otherwise,
        creates a target for an n-class classification task where
        ``n = len(percentiles) + 1``.

    n_steps_future : int, optional, default: ``1``
        Number of steps in the future for the predictive task.

    Attributes
    ----------
    thresholds_ : list of floats or ``None`` if percentiles is ``None``
        Values corresponding to each percentile, based on data seen in
        :meth:`fit`.

    Examples
    --------
    >>> import numpy as np
    >>> from giotto.time_series import Labeller
    >>> # Create a time series
    >>> X = np.arange(10).reshape(-1, 1)
    >>> labeller = Labeller(width=2)
    >>> # Fit and transform X
    >>> X, y = labeller.fit_transform_resample(X, X)
    >>> print(X)
    [[1]
     [2]
     [3]
     [4]
     [5]
     [6]
     [7]
     [8]]
    >>> print(y)
    [0.81649658 0.81649658 0.81649658 0.81649658 0.81649658 0.81649658
     0.81649658 0.81649658]

    """

    _hyperparameters = {
        'func': [types.FunctionType],
        '_percentiles': [list, [numbers.Number, (0., 1.)]],
        'n_steps_future': [int, [1, np.inf]]}

    def __init__(self, width=10, func=np.std,
                 func_params={}, percentiles=None, n_steps_future=1):
        self.width = width
        self.func = func
        self.func_params = func_params
        self.percentiles = percentiles
        self.n_steps_future = n_steps_future

    def fit(self, X, y=None):
        """Compute :attr:`thresholds_` and return the estimator.

        Parameters
        ----------
        X : ndarray, shape (n_samples,) or (n_samples, 1)
            Time series to build a target for.

        y : None
            There is no need for a target in a transformer, yet the pipeline
            API requires this parameter.

        Returns
        -------
        self : object

        """
        if self.percentiles is None:
            _percentiles = [0.]
        else:
            _percentiles = self.percentiles

        validate_params({**self.get_params(), '_percentiles': _percentiles},
                         self._hyperparameters)
        X = column_or_1d(X)

        self._sliding_window = SlidingWindow(width=self.width, stride=1).fit(X)

        _X = self._sliding_window.transform(X)
        _X = self.func(_X, axis=1, **self.func_params).reshape(-1, 1)

        if self.percentiles is not None:
            self.thresholds_ = [np.percentile(np.abs(_X.flatten()), percentile)
                                for percentile in self.percentiles]
        else:
            self.thresholds_ = None
        return self

    def transform(self, X, y=None):
        """Cuts `X` so it is aligned with `y`.

        Parameters
        ----------
        X : ndarray, shape (n_samples,) or (n_samples, 1)
            Time series to build a target for.

        y : None
            There is no need for a target, yet the pipeline API
            requires this parameter.

        Returns
        -------
        Xt : ndarray, shape (n_samples_new, 1)
            The cut input time series.

        """
        # Check is fit had been called
        check_is_fitted(self, ['_sliding_window', 'thresholds_'])
        X = column_or_1d(X)

        Xt = X[:-self.n_steps_future]

        if self.n_steps_future < self.width:
            Xt = Xt[self.width - self.n_steps_future:]
        return Xt.reshape(-1, 1)

    def resample(self, y, X=None):
        """Resample `y`.

        Parameters
        ----------
        y : ndarray, shape (n_samples,)
            Time series to build a target for.

        X : None
            There is no need for `X`, yet the pipeline API requires this
            parameter.

        Returns
        -------
        yr : ndarray, shape (n_samples_new,)
            Target for the prediction task.

        """
        # Check is fit had been called
        check_is_fitted(self, ['_sliding_window', 'thresholds_'])
        y = column_or_1d(y)

        yr = self._sliding_window.transform(y)
        yr = self.func(yr, axis=1, **self.func_params).reshape(-1, 1)

        if self.thresholds_ is not None:
            yr = np.abs(yr)
            yr = np.concatenate(
                [1 * (yr >= 0) * (yr < self.thresholds_[0])] +
                [1 * (yr >= self.thresholds_[i]) *
                 (yr < self.thresholds_[i + 1]) for i in range(
                    len(self.thresholds_) - 1)] +
                [1 * (yr >= self.thresholds_[-1])], axis=1)
            yr = np.nonzero(yr)[1].reshape(yr.shape[0], 1)

        if self.n_steps_future >= self.width:
            yr = yr[self.n_steps_future - self.width + 1:]

        return yr.reshape(-1)
