"""
.. topic:: Tools module

    .. autosummary::

        db2mag
        db2pow
        mag2db
        nextpow2
        pow2db
        onesided_2_twosided
        twosided_2_onesided
        centerdc_2_twosided
        twosided_2_centerdc

    .. codeauthor:: Thomas Cokelaer, 2011
"""
import numpy as np
from numpy import ceil, log2
from collections import deque


#__all__ = ["cshift", "pow2dB", "nextpow2", "twosided", "twosided_zerolag",
#           "fftshift"]


def fftshift(x):
    """wrapper to numpy.fft.fftshift

    .. doctest::

        >>> from spectrum import fftshift
        >>> x = [100, 2, 3, 4, 5]
        >>> fftshift(x)
        array([  4,   5, 100,   2,   3])
    """
    return np.fft.fftshift(x)


def _swapsides(data):
    """todo is it really useful ?

    Swap  sides

    .. doctest::

        >>> from spectrum import swapsides
        >>> x = [-2, -1, 1, 2]
        >>> swapsides(x)
        array([ 2, -2, -1])

    """
    N = len(data)
    return np.concatenate((data[N//2+1:], data[0:N//2]))


def twosided_2_onesided(data):
    """Convert a one-sided PSD to a twosided PSD

    In order to keep the power in the onesided PSD the same
    as in the twosided version, the onesided values are twice
    as much as in the input data (except for the zero-lag value).

    ::

        >>> twosided_2_onesided([10, 2,3,3,2,8])
        array([ 10.,   4.,   6.,   8.])

    """
    assert len(data) % 2 == 0
    N = len(data)
    psd = np.array(data[0:N//2+1]) * 2.
    psd[0] /= 2.
    psd[-1] = data[-1]
    return psd


def onesided_2_twosided(data):
    """Convert a two-sided PSD to a one-sided PSD

    In order to keep the power in the twosided PSD the same
    as in the onesided version, the twosided values are 2 times
    lower than the input data (except for the zero-lag and N-lag
    values).

    ::

        >>> twosided_2_onesided([10, 4, 6, 8])
        array([ 10.,   2.,   3.,   3., 2., 8.])

    """
    psd = np.concatenate((data[0:-1], cshift(data[-1:0:-1], -1)))/2.
    psd[0] *= 2.
    psd[-1] *= 2.
    return psd


def twosided_2_centerdc(data):
    """Convert a two-sided PSD to a center-dc PSD"""
    N = len(data)
    # could us int() or // in python 3
    newpsd = np.concatenate((cshift(data[N//2:], 1), data[0:N//2]))
    newpsd[0] = data[-1]
    return newpsd


def centerdc_2_twosided(data):
    """Convert a center-dc PSD to a twosided PSD"""
    N = len(data)
    newpsd = np.concatenate((data[N//2:], (cshift(data[0:N//2], -1))))
    return newpsd


def twosided(data):
    """return a twosided vector with non-duplication of the first element

    .. doctest::

        >>> from spectrum import twosided
        >>> a = [1,2,3]
        >>> twosided(a)
        array([3, 2, 1, 2, 3])

    """
    twosided = np.concatenate((data[::-1], data[1:]))
    return twosided #remove the first element to have a power of 2 and compatiable with pylab.psd


def _twosided_zerolag(data, zerolag):
    """Build a symmetric vector out of stricly positive lag vector and zero-lag

    .. doctest::

        >>> data = [3,2,1]
        >>> zerolag = 4
        >>> twosided_zerolag(data, zerolag)
        array([1, 2, 3, 4, 3, 2, 1])

    .. seealso:: Same behaviour as :func:`twosided_zerolag`
    """
    res = twosided(np.insert(data, 0, zerolag))
    return res


def cshift(data, offset):
    """Circular shift to the right (within an array) by a given offset

    :param array data: input data (list or numpy.array)
    :param int offset: shift the array with the offset

    .. doctest::

        >>> from spectrum import cshift
        >>> cshift([0, 1, 2, 3, -2, -1], 2)
        array([-2, -1,  0,  1,  2,  3])

    """
    # the deque method is suppose to be optimal when using rotate to shift the
    # data that playing with the data to build a new list.
    if isinstance(offset, float):
        offset = int(offset)
    a = deque(data)
    a.rotate(offset)
    return np.array(a)  #convert back to an array. Is it necessary?


def pow2db(x):
    """returns the corresponding decibel (dB) value for a power value x.

    The relationship between power and decibels is:

    .. math::    X_{dB} = 10 * \log_{10}(x)

    .. doctest::

        >>> from spectrum import pow2db
        >>> x = pow2db(0.1)
        >>> x
        -10.0
    """
    return 10 * log10(x)


def db2pow(xdb):
    """Convert decibels (dB) to power

    .. doctest::

        >>> from spectrum import db2pow
        >>> p = db2pow(-10)
        >>> p
        0.1

    .. seealso:: :func:`pow2db`
    """
    return 10.**(xdb/10.)


def nextpow2(x):
    """returns the smallest power of two that is greater than or equal to the
    absolute value of x.

    This function is useful for optimizing FFT operations, which are
    most efficient when sequence length is an exact power of two.

    :Example:

    .. doctest::

        >>> from spectrum import nextpow2
        >>> x = [255, 256, 257]
        >>> nextpow2(x)
        array([8, 8, 9])

    """
    res = ceil(log2(x))
    return res.astype('int')  #we want integer values only but ceil gives float


def db2mag(xdb):
    """Convert decibels (dB) to magnitude

    .. doctest::

        >>> from spectrum import db2mag
        >>> db2mag(-20)
        0.1

    .. seealso:: :func:`pow2db`
    """
    return 10.**(xdb/20.)


def mag2db(x):
    """Convert magnitude to decibels (dB)

    The relationship between magnitude and decibels is:

    .. math::    X_{dB} = 20 * \log_{10}(x)

    .. doctest::

        >>> from spectrum import mag2db
        >>> mag2db(0.1)
        -20.0

    .. seealso:: :func:`db2mag`
    """
    return 20. * log10(x)


def log10(data):
    np.seterr(divide='ignore')
    data = np.log10(data)
    np.seterr(divide='warn')
    return data


