# -*- coding: utf-8 -*-
"""
   trueskill.backends
   ~~~~~~~~~~~~~~~~~~

   Provides mathematical statistics backend chooser.

   :copyright: (c) 2012-2016 by Heungsub Lee.
   :license: BSD, see LICENSE for more details.

"""
from __future__ import absolute_import

import math

from six.moves import range


__all__ = ['available_backends', 'choose_backend', 'cdf', 'pdf', 'ppf']


def _gen_erfcinv(erfc, math=math):
    """Generates the inverse function of erfc by the given erfc function and
    math module.
    """
    def erfcinv(y):
        """The inverse function of erfc."""
        if y >= 2:
            return -100.
        elif y <= 0:
            return 100.
        zero_point = y < 1
        if not zero_point:
            y = 2 - y
        t = math.sqrt(-2 * math.log(y / 2.))
        x = -0.70711 * \
            ((2.30753 + t * 0.27061) / (1. + t * (0.99229 + t * 0.04481)) - t)
        for i in range(2):
            err = erfc(x) - y
            x += err / (1.12837916709551257 * math.exp(-(x ** 2)) - x * err)
        return x if zero_point else -x
    return erfcinv


def _gen_ppf(erfc, math=math):
    """ppf is the inverse function of cdf.  This function generates cdf by the
    given erfc and math module.
    """
    erfcinv = _gen_erfcinv(erfc, math)
    def ppf(x, mu=0, sigma=1):
        """The inverse function of cdf."""
        return mu - sigma * math.sqrt(2) * erfcinv(2 * x)
    return ppf


def erfc(x):
    """Complementary error function (via `http://bit.ly/zOLqbc`_)"""
    z = abs(x)
    t = 1. / (1. + z / 2.)
    r = t * math.exp(-z * z - 1.26551223 + t * (1.00002368 + t * (
        0.37409196 + t * (0.09678418 + t * (-0.18628806 + t * (
            0.27886807 + t * (-1.13520398 + t * (1.48851587 + t * (
                -0.82215223 + t * 0.17087277
            )))
        )))
    )))
    return 2. - r if x < 0 else r


def cdf(x, mu=0, sigma=1):
    """Cumulative distribution function"""
    return 0.5 * erfc(-(x - mu) / (sigma * math.sqrt(2)))


def pdf(x, mu=0, sigma=1):
    """Probability density function"""
    return (1 / math.sqrt(2 * math.pi) * abs(sigma) *
            math.exp(-(((x - mu) / abs(sigma)) ** 2 / 2)))


ppf = _gen_ppf(erfc)


def choose_backend(backend):
    """Returns a tuple containing cdf, pdf, ppf from the chosen backend.

    >>> cdf, pdf, ppf = choose_backend(None)
    >>> cdf(-10)
    7.619853263532764e-24
    >>> cdf, pdf, ppf = choose_backend('mpmath')
    >>> cdf(-10)
    mpf('7.6198530241605255e-24')

    .. versionadded:: 0.3

    """
    if backend is None:  # fallback
        return cdf, pdf, ppf
    elif backend == 'mpmath':
        try:
            import mpmath
        except ImportError:
            raise ImportError('Install "mpmath" to use this backend')
        return mpmath.ncdf, mpmath.npdf, _gen_ppf(mpmath.erfc, math=mpmath)
    elif backend == 'scipy':
        try:
            from scipy.stats import norm
        except ImportError:
            raise ImportError('Install "scipy" to use this backend')
        return norm.cdf, norm.pdf, norm.ppf
    raise ValueError('%r backend is not defined' % backend)


def available_backends():
    """Detects list of available backends.  All of defined backends are
    ``None`` -- internal implementation, "mpmath", "scipy".

    You can check if the backend is available in the current environment with
    this function::

       if 'mpmath' in available_backends():
           # mpmath can be used in the current environment
           setup(backend='mpmath')

    .. versionadded:: 0.3

    """
    backends = [None]
    for backend in ['mpmath', 'scipy']:
        try:
            __import__(backend)
        except ImportError:
            continue
        backends.append(backend)
    return backends
