import operator
from collections.abc import Mapping

import numpy
import pandas
from sklearn.preprocessing import OneHotEncoder


def one_hot_encode(vector, dtype="float32", categories=None, index=None):
    if isinstance(vector, pandas.Series):
        index = vector.index
        vector = vector.values

    encoder = OneHotEncoder(
        categories="auto"
        if categories is None
        else [
            categories,
        ],
        sparse=False,
        dtype=dtype,
    ).fit(vector.reshape(-1, 1))

    return pandas.DataFrame(
        data=encoder.transform(vector.reshape(-1, 1)),
        columns=encoder.categories_,
        index=index,
    )


def periodize(
    values,
    mapping=None,
    default=None,
    right=True,
    left=True,
    **kwargs,
):
    """
    Label sections of a continuous variable.

    This function contrasts with `pandas.cut` in that
    there can be multiple non-contiguous sections of the
    underlying continuous interval that obtain the same
    categorical value.

    Parameters
    ----------
    values : array-like
            The values to label. If given as a pandas.Series,
            the returned values will also be a Series,
            with a categorical dtype.
    mapping : Collection or Mapping
            A mapping, or a collection of 2-tuples giving
            key-value pairs (not necessarily unique keys).
            The keys (or first values) will be the new values,
            and the values (or second values) are 2-tuples
            giving upper and lower bounds.
    default : any, default None
            Keys not inside any labeled interval will get
            this value instead.
    right : bool, default True
            Whether to include the upper bound[s] in the
            intervals for labeling.
    left : bool, default True
            Whether to include the lower bound[s] in the
            intervals for labeling.
    **kwargs :
            Are added to `mapping`.

    Returns
    -------
    array-like

    Example
    -------
    >>> import pandas
    >>> h = pandas.Series(range(1,24))
    >>> periodize(h, default='OP', AM=(6.5, 9), PM=(16, 19))
    0     OP
    1     OP
    2     OP
    3     OP
    4     OP
    5     OP
    6     AM
    7     AM
    8     AM
    9     OP
    10    OP
    11    OP
    12    OP
    13    OP
    14    OP
    15    PM
    16    PM
    17    PM
    18    PM
    19    OP
    20    OP
    21    OP
    22    OP
    dtype: category
    Categories (3, object): ['AM', 'OP', 'PM']
    """
    if mapping is None:
        mapping = []

    if isinstance(mapping, Mapping):
        mapping = list(mapping.items())

    mapping.extend(kwargs.items())

    if isinstance(values, pandas.Series):
        x = pandas.Series(index=values.index, data=default)
    else:
        x = numpy.full(values.shape, default)

    if right:
        rop = operator.le
    else:
        rop = operator.lt

    if left:
        lop = operator.ge
    else:
        lop = operator.gt

    for k, (lowerbound, upperbound) in mapping:
        if lowerbound is None:
            lowerbound = -numpy.inf
        if upperbound is None:
            upperbound = numpy.inf
        x[lop(values, lowerbound) & rop(values, upperbound)] = k

    if isinstance(x, pandas.Series):
        x = x.astype("category")

    return x
