"""
.. Copyright (c) 2014, 2015 Marshall Farrier
   license http://opensource.org/licenses/MIT

Common - generic functions (:mod:`pynance.common`)
==================================================

.. currentmodule:: pynance.common
"""

import pandas as pd

def featurize(equity_data, n_sessions, **kwargs):
    """
    Generate a raw (unnormalized) feature set from the input data.
    The value at `column` on the given date is taken
    as a feature, and each row contains values for n_sessions

    Parameters
    -----------
    equity_data : DataFrame
        data from which to generate features

    n_sessions : int
        number of sessions to use as features

    selection : str, default: 'Adj Close'
        column of `equity_data` from which to generate features.

    columns : list, default: ``map(str, range((-n_sessions + 1), 1))``
        column names for output DataFrame. Default will look like:
        ['-5', '-4', '-3', '-2', '-1', '0'].

    Returns
    ----------
    out : DataFrame
        Each row is a sequence of `n_sessions` session values where
        the last column matches the value on the date specified by
        the DataFrame index.

    Examples
    --------
    >>> pn.featurize(equity_data, n_sessions, **kwargs)
    """
    #Benchmarking
    #>>> s = 'from __main__ import data\nimport datetime as dt\n'
    #>>> timeit.timeit('data.featurize(data.get("ge", dt.date(1960, 1, 1), 
    #        dt.date(2014, 12, 31)), 256)', setup=s, number=1)
    #1.6771750450134277
    columns = kwargs.get('columns', map(str, range(-n_sessions + 1, 1)))
    selection = kwargs.get('selection', 'Adj Close')
    # empty DataFrame with desired index and column labels
    features = pd.DataFrame(index=equity_data.index[(n_sessions - 1):],
            columns=columns, dtype='float64')
    values = equity_data[selection].values
    for i in range(n_sessions - 1):
        features.iloc[:, i] = values[i:(-n_sessions + i + 1)]
    features.iloc[:, n_sessions - 1] = values[(n_sessions - 1):]
    return features

def decorate(fn, *args, **kwargs):
    """
    Return a new function that replicates the behavior of the input
    but also returns an additional value. Used for creating functions
    of the proper type to pass to `labeledfeatures()`.

    Parameters
    ----------
    fn : function

    *args : any
        Additional parameters that the returned function will return

    **kwargs : dict
        Each element in `kwargs` will become an attribute of the output
        function.

    Returns
    ----------
    wrapped : function
        New function that acts like `fn` except that it also returns
        an additional value.

    Examples
    ----------
    >>> from functools import partial
    >>> forecast_interval = 32
    >>> features, labels = pn.data.labeledfeatures(eqdata, 256, featurefn,
    ...        decorate(partial(pn.data.lab.growth, forecast_interval, 'Adj Close'), forecast_interval))
    >>> def f():
    ...    return 0, 1 
    ...
    >>> pn.decorate(f, 3, 4, 5)()
    (0, 1, 3, 4, 5)
    >>> pn.decorate(lambda x: x * .5, 3, 4, 5)(1.)
    (1., 3, 4, 5)
    >>> pn.decorate(lambda x: x, 1 2)('foo')
    ('foo', 1, 2)
    >>> pn.decorate(f, 'foo'):
    (0, 1, 'foo')
    pn.decorate(f, 0, foo='bar').foo
    >>> 'bar'

    Notes
    ----------
    If `fn` returns multiple values, these will be returned in sequence
    as the first values returned by `add_rets(fn, arg0, arg1, arg2)`. See example
    above.
    """
    def _wrapper(*_args, **kwargs):
        _ret = fn(*_args, **kwargs)
        if isinstance(_ret, tuple):
            return _ret + args
        if len(args) == 0:
            return _ret
        return (_ret,) + args
    for key, value in kwargs.items():
        _wrapper.__dict__[key] = value
    return _wrapper

def expand(fn, col, inputtype=pd.DataFrame):
    """
    Wrap a function applying to a single column to make a function
    applying to a multi-dimensional dataframe or ndarray

    Parameters
    ----------
    fn : function
        Function that applies to a series or vector.

    col : str or int
        Index of column to which to apply `fn`.

    inputtype : class or type
        Type of input to be expected by the wrapped function.
        Normally pd.DataFrame or np.ndarray. Defaults to pd.DataFrame.

    Returns
    ----------
    wrapped : function
        Function that takes an input of type `inputtype` and applies
        `fn` to the specified `col`.
    """
    if inputtype == pd.DataFrame:
        if isinstance(col, int):
            def _wrapper(*args, **kwargs):
                return fn(args[0].iloc[:, col], *args[1:], **kwargs)
            return _wrapper
        def _wrapper(*args, **kwargs):
            return fn(args[0].loc[:, col], *args[1:], **kwargs)
        return _wrapper
    elif inputtype == np.ndarray:
        def _wrapper(*args, **kwargs):
            return fn(args[0][:, col], *args[1:], **kwargs)
        return _wrapper
    raise TypeError("invalid input type")

def has_na(eqdata):
    """
    Return false if `eqdata` contains no missing values.

    Parameters
    ----------
    eqdata : DataFrame or ndarray
        Data to check for missing values (NaN, None)

    Returns
    ----------
    answer : bool
        False iff `eqdata` contains no missing values.
    """
    if isinstance(eqdata, pd.DataFrame):
        _values = eqdata.values
    else:
        _values = eqdata
    return len(_values[pd.isnull(_values)]) > 0
