"""
This is an example on how to document Python modules using doc
strings and the sphinx tool. The doc strings can make use of
the reStructuredText format, see
`<http://docutils.sourceforge.net/docs/user/rst/quickstart.html>`_.
It is recommended to document Python modules according to
the `rules <https://github.com/numpy/numpy/blob/master/doc/example.py>`_
of the ``numpy`` package. See also
`A Guide to NumPy/SciPy Documentation <https://github.com/numpy/numpy/blob/master/doc/HOWTO_DOCUMENT.rst.txt>`_.

"""
__all__ = ['roots', 'Quadratic', 'Cubic']

from numpy.lib.scimath import sqrt  # handles real and complex args

def roots(a, b, c, verbose=False):
    """
    Return the two roots in the quadratic equation::

      a*x**2 + b*x + c = 0

    or written with math typesetting

    .. math:: ax^2 + bx + c = 0

    The returned roots are real or complex numbers,
    depending on the values of the arguments `a`, `b`,
    and `c`.

    Parameters
    ----------
    a: int, real, complex
       coefficient of the quadratic term
    b: int, real, complex
       coefficient of the linear term
    c: int, real, complex
       coefficient of the constant term
    verbose: bool, optional
       prints the quantity ``b**2 - 4*a*c`` and if the
       roots are real or complex

    Returns
    -------
    root1, root2: real, complex
        the roots of the quadratic polynomial.

    Raises
    ------
    ValueError:
        when `a` is zero

    See Also
    --------
    :class:`Quadratic`: which is a class for quadratic polynomials
        that also has a :func:`Quadratic.roots` method for computing
        the roots of a quadratic polynomial. There is also a class
        :class:`linear.Linear` in the module :mod:`linear`
        (i.e., :class:`linear.Linear`).

    Notes
    -----
    The algorithm is a straightforward implementation of
    a very well known formula [1]_.

    References
    ----------
    .. [1] Any textbook on mathematics or
           `Wikipedia <http://en.wikipedia.org/wiki/Quadratic_equation>`_.

    Examples
    --------
    >>> roots(-1, 2, 10)
    (-5.3166247903553998, 1.3166247903553998)
    >>> roots(-1, 2, -10)
    ((-2-3j), (-2+3j))

    Alternatively, we can in a doc string list the arguments and
    return values in a table

    ==========   =============   ================================
    Parameter    Type            Description
    ==========   =============   ================================
    a            float/complex   coefficient for quadratic term
    b            float/complex   coefficient for linear term
    c            float/complex   coefficient for constant term
    r1, r2       float/complex   return: the two roots of
                                 the quadratic polynomial
    ==========   =============   ================================
    """
    if abs(a) < 1E-14:
        raise ValueError('a=%g is too close to zero' % a)

    q = b**2 - 4*a*c
    if verbose:
        print 'q=%g: %s roots' % (q, 'real' if q>0 else 'complex')

    root1 = -b + sqrt(q)/float(2*a)
    root2 = -b - sqrt(q)/float(2*a)
    return root1, root2

class Quadratic:
    """
    Representation of a quadratic polynomial:

    .. math::

       ax^2 + bx + c

    Example:

    >>> q = Quadratic(a=2, b=4, c=-16)
    >>> print q
    2*x**2 + 4*x - 16
    >>> r1, r2 = q.roots()
    >>> r1
    2.0
    >>> r2
    -4.0
    >>> q(r1), q(r2)  # check
    (0.0, 0.0)
    >>> repr(q)
    'Quadratic(a=2, b=4, c=-16)'
    """

    def __init__(self, a, b, c):
        """
        The arguments `a`, `b`, and `c` are coefficients in
        the quadratic polynomial::

          a*x**2 + b*x + c

        or

        .. math::

           ax^2 + bx + c

        ==========   =============   ================================
        Argument     Type            Description
        ==========   =============   ================================
        a            float/complex   coefficient for quadratic term
        b            float/complex   coefficient for linear term
        c            float/complex   coefficient for constant term
        ==========   =============   ================================

        Raises
        ------
        ValueError:
            When `a` is too close to zero.
        """
        self.a, self.b, self.c = a, b, c
        if abs(a) < 1E-14:
            raise ValueError('a=%g is too close to zero' % a)

    def __call__(self, x):
        return self.value(x)

    def value(self, x):
        """
        Return the value of the quadratic polynomial for `x`.

        Example:

        >>> q = Quadratic(a=2, b=4, c=-16)
        >>> print q
        2*x**2 + 4*x - 16
        >>> q(1)
        -10
        >>> q(-2.5)
        -13.5
        """
        return self.a*x**2 + self.b*x + self.c

    def roots(self):
        """
        Return the two roots of the quadratic polynomial.
        The roots are real or complex, depending on the
        coefficients in the polynomial.

        Let us define a quadratic polynomial:

        >>> q = Quadratic(a=2, b=4, c=-16)
        >>> print q
        2*x**2 + 4*x - 16

        The roots are then found by

        >>> r1, r2 = q.roots()
        >>> r1
        2.0
        >>> r2
        -4.0
        """
        a, b, c = self.a, self.b, self.c  # short names
        q = b**2 - 4*a*c
        root1 = (-b + sqrt(q))/float(2*a)
        root2 = (-b - sqrt(q))/float(2*a)
        return root1, root2


    def __str__(self):
        # no doc
        return '%s*x**2 %s %s*x %s %s' % \
               (self.a, _sign(self.b), abs(self.b),
                _sign(self.c), abs(self.c))

    def __repr__(self):
        return 'Quadratic(a=%s, b=%s, c=%s)' % \
               (self.a, self.b, self.c)

def _sign(v):
    """Return '+' if `v` >= 0, otherwise '-'."""
    return '+' if v >= 0 else '-'

class Cubic(Quadratic):
    """Evaluation of a cubic polynomial :math:`ax^3 + bx^2 + cx + d`."""
    def __init__(self, a, b, c, d):
        self.cubic = self.a
        Quadratic.__init__(self, b, c, d)

    def value(self, x):
        return Quadratic.value(self, x) + self.cubic*x**3

    def roots(self):
        raise NotImplementedError('roots for cubic polynomial not impl.')

    def __str__(self):
        return '%s*x**3 +' % (self.cubic) + Quadratic.__str__(self)