import math
import fractions
import sys

from ..lang import data
from ..lang import d
from ..lang import deriving
from ..lang import H
from ..lang import sig
from ..lang import t
from ..lang import instance
from ..lang import build_instance
from ..lang import Enum
from ..lang import Show
from Eq import Eq
from Ord import Ord


class Num(Show, Eq):
    """
    Basic numeric class.

    Dependencies:
        Show, Eq

    Attributes:
        __add__, __mul__, __abs__, signum, fromInteger, __neg__, __sub__

    Minimal complete definition:
        add, mul, abs, signum, fromInteger, negate
    """
    @classmethod
    def make_instance(typeclass, cls, add, mul, abs, signum, fromInteger,
                      negate, sub=None):

        @sig(H[(Num, "a")]/ "a" >> "a" >> "a")
        def default_sub(a, b):
            return a.__add__(b.__neg__())

        sub = default_sub if sub is None else sub
        attrs = {"add":add, "mul":mul, "abs":abs, "signum":signum,
                 "fromInteger":fromInteger, "negate":negate, "sub":sub}

        build_instance(Num, cls, attrs)
        return


@sig(H[(Num, "a")]/ "a" >> "a")
def negate(a):
    """
    signum :: Num a => a -> a

    Unary negation.
    """
    return Num[a].negate(a)


@sig(H[(Num, "a")]/ "a" >> "a")
def signum(a):
    """
    signum :: Num a => a -> a

    Sign of a number. The functions abs and signum should satisfy the law:
    abs x * signum x == x
    For real numbers, the signum is either -1 (negative), 0 (zero) or 1
    (positive).
    """
    return Num[a].signum(a)


@sig(H[(Num, "a")]/ "a" >> "a")
def abs(a):
    """
    abs :: Num a => a -> a

    Absolute value.
    """
    return Num[a].abs(a)


instance(Num, int).where(
    add = int.__add__,
    mul = int.__mul__,
    abs = int.__abs__,
    signum = lambda x: -1 if x < 0 else (1 if x > 0 else 0),
    fromInteger = int,
    negate = int.__neg__,
    sub = int.__sub__
)

instance(Num, long).where(
    add = long.__add__,
    mul = long.__mul__,
    abs = long.__abs__,
    signum = lambda x: -1L if x < 0L else (1L if x > 0L else 0L),
    fromInteger = long,
    negate = long.__neg__,
    sub = long.__sub__
)

instance(Num, float).where(
    add = float.__add__,
    mul = float.__mul__,
    abs = float.__abs__,
    signum = lambda x: -1.0 if x < 0.0 else (1.0 if x > 0.0 else 0.0),
    fromInteger = float,
    negate = float.__neg__,
    sub = float.__sub__
)

instance(Num, complex).where(
    add = complex.__add__,
    mul = complex.__mul__,
    abs = complex.__abs__,
    signum = lambda x: complex(-1) if x < 0 else complex(1 if x > 0 else 0),
    fromInteger = complex,
    negate = complex.__neg__,
    sub = complex.__sub__
)


class Fractional(Num):
    """
    Fractional numbers, supporting real division.

    Dependencies:
        Num

    Attributes:
        fromRational, recip, __div__

    Minimal complete definition:
        fromRational, div
    """
    @classmethod
    def make_instance(typeclass, cls, fromRational, div, recip=None):
        if recip is None:
            recip = lambda x: div(1, x)
        attrs = {"fromRational":fromRational, "div":div, "recip":recip}
        build_instance(Fractional, cls, attrs)
        return


Ratio, R =\
        data.Ratio("a") == d.R("a", "a") & deriving(Eq)

Rational = t(Ratio, int)


instance(Fractional, float).where(
    fromRational = lambda rat: float(rat[0])/float(rat[1]),
    div = float.__div__,
    recip = lambda x: 1.0/x
)


@sig(H[(Fractional, "a")]/ "a" >> "a")
def recip(a):
    """
    recip :: Fractional a => a -> a

    Reciprocal fraction.
    """
    return Fractional[a].recip(a)


class Floating(Fractional):
    """
    Trigonometric and hyperbolic functions and related functions.

    Dependencies:
        Fractional

    Attributes:
        pi, exp, sqrt, log, pow, logBase, sin, tan, cos, asin, atan, acos,
        sinh, tanh, cosh, asinh, atanh, acosh

    Minimal complete definition:
        pi, exp, sqrt, log, pow, logBase, sin, tan, cos, asin, atan, acos,
        sinh, tanh, cosh, asinh, atanh, acosh
    """
    @classmethod
    def make_instance(typeclass, cls, pi, exp, sqrt, log, pow, logBase, sin,
            tan, cos, asin, atan, acos, sinh, tanh, cosh, asinh, atanh, acosh):
        attrs = {"pi":pi, "exp":exp, "sqrt":sqrt, "log":log, "pow":pow,
                "logBase":logBase, "sin":sin, "tan":tan, "cos":cos,
                "asin":asin, "atan":atan, "acos":acos, "sinh":sinh,
                "tanh":tanh, "cosh":cosh, "asinh":asinh, "atanh":atanh,
                "acosh":acosh}
        build_instance(Floating, cls, attrs)
        return


@sig(H[(Floating, "a")]/ "a" >> "a")
def exp(x):
    """
    exp :: Floating a => a -> a
    """
    return Floating[x].exp(x)


@sig(H[(Floating, "a")]/ "a" >> "a")
def sqrt(x):
    """
    sqrt :: Floating a => a -> a
    """
    return Floating[x].sqrt(x)


@sig(H[(Floating, "a")]/ "a" >> "a")
def log(x):
    """
    log:: Floating a => a -> a
    """
    return Floating[x].log(x)


@sig(H[(Floating, "a")]/ "a" >> "a" >> "a")
def pow(x, y):
    """
    pow :: Floating a => a -> a -> a
    """
    return Floating[x].pow(x, y)


@sig(H[(Floating, "a")]/ "a" >> "a" >> "a")
def logBase(x, b):
    """
    logBase :: Floating a => a -> a -> a
    """
    return Floating[x].logBase(x, b)


@sig(H[(Floating, "a")]/ "a" >> "a")
def sin(x):
    """
    sin :: Floating a => a -> a
    """
    return Floating[x].sin(x)


@sig(H[(Floating, "a")]/ "a" >> "a")
def cos(x):
    """
    cos :: Floating a => a -> a
    """
    return Floating[x].cos(x)


@sig(H[(Floating, "a")]/ "a" >> "a")
def tan(x):
    """
    tan :: Floating a => a -> a
    """
    return Floating[x].tan(x)


@sig(H[(Floating, "a")]/ "a" >> "a")
def asin(x):
    """
    asin :: Floating a => a -> a
    """
    return Floating[x].asin(x)


@sig(H[(Floating, "a")]/ "a" >> "a")
def atan(x):
    """
    atan :: Floating a => a -> a
    """
    return Floating[x].atan(x)


@sig(H[(Floating, "a")]/ "a" >> "a")
def acos(x):
    """
    acos :: Floating a => a -> a
    """
    return Floating[x].acos(x)


@sig(H[(Floating, "a")]/ "a" >> "a")
def sinh(x):
    """
    sinh :: Floating a => a -> a
    """
    return Floating[x].sinh(x)


@sig(H[(Floating, "a")]/ "a" >> "a")
def tanh(x):
    """
    tanh :: Floating a => a -> a
    """
    return Floating[x].tanh(x)


@sig(H[(Floating, "a")]/ "a" >> "a")
def cosh(x):
    """
    cosh :: Floating a => a -> a
    """
    return Floating[x].cosh(x)


@sig(H[(Floating, "a")]/ "a" >> "a")
def asinh(x):
    """
    asinh :: Floating a => a -> a
    """
    return Floating[x].asinh(x)


@sig(H[(Floating, "a")]/ "a" >> "a")
def atanh(x):
    """
    atanh :: Floating a => a -> a
    """
    return Floating[x].atanh(x)


@sig(H[(Floating, "a")]/ "a" >> "a")
def acosh(x):
    """
    acosh :: Floating a => a -> a
    """
    return Floating[x].acosh(x)


instance(Floating, float).where(
    pi = math.pi,
    exp = math.exp,
    sqrt = math.sqrt,
    log = math.log,
    pow = pow,
    logBase = math.log,
    sin = math.sin,
    tan = math.tan,
    cos = math.cos,
    asin = math.asin,
    atan = math.atan,
    acos = math.acos,
    sinh = math.sinh,
    tanh = math.tanh,
    cosh = math.cosh,
    asinh = math.asinh,
    atanh = math.atanh,
    acosh = math.acosh
)


class Real(Num, Ord):
    """
    Real numbers.

    Dependencies:
        Num, Ord

    Attributes:
        toRational

    Minimal complete definition:
        toRational
    """
    @classmethod
    def make_instance(typeclass, cls, toRational):
        build_instance(Real, cls, {})
        return


@sig(H[(Real, "a")]/ "a" >> Rational)
def toRational(x):
    """
    toRational :: Real a => a -> Rational

    Conversion to Rational.
    """
    return Real[x].toRational(x)


class Integral(Real, Enum):
    """
    Integral numbers, supporting integer division.

    Dependencies:
        Real, Enum

    Attributes:
        quotRem, toInteger, quot, rem, div, mod

    Minimal complete definition:
        quotRem, toInteger, quot, rem, div, mod
    """
    @classmethod
    def make_instance(typeclass, cls, quotRem, divMod, toInteger, quot=None,
            rem=None, div=None, mod=None):

        quot = lambda x: quotRem(x)[0] if quot is None else quot
        rem = lambda x: quotRem(x)[1] if rem is None else rem
        div = lambda x: divMod(x)[0] if div is None else div
        mod = lambda x: divMod(x)[1] if mod is None else mod

        attrs = {"quotRem":quotRem, "toInteger":toInteger, "quot":quot,
                 "rem":rem, "div":div, "mod":mod, "divMod":divMod}
        build_instance(Integral, cls, attrs)
        return


@sig(H[(Integral, "a")]/ "a" >> "a" >> t(Ratio, "a"))
def toRatio(num, denom):
    """
    toRatio :: Integral a => a -> a -> Ratio a

    Conversion to Ratio.
    """
    frac = fractions.Fraction(num, denom)
    return R(frac.numerator, frac.denominator)


instance(Real, int).where(
    toRational = lambda x: toRatio(x, 1)
)

instance(Real, long).where(
    toRational = lambda x: toRatio(x, 1)
)

instance(Real, float).where(
    toRational = lambda x: toRatio(round(x), 1) # obviously incorrect
)

instance(Integral, int).where(
    quotRem = lambda x, y: (x / y, x % y),
    toInteger = int,
    quot = int.__div__,
    rem = int.__mod__,
    div = int.__div__,
    mod = int.__mod__,
    divMod = divmod
)

instance(Integral, long).where(
    quotRem = lambda x, y: (x / y, x % y),
    toInteger = int,
    quot = long.__div__,
    rem = long.__mod__,
    div = long.__div__,
    mod = long.__mod__,
    divMod = divmod
)


class RealFrac(Real, Fractional):
    """
    Extracting components of fractions.

    Dependencies:
        Real, Fractional

    Attributes:
        properFraction, truncate, round, ceiling, floor

    Minimal complete definition:
        properFraction, truncate, round, ceiling, floor
    """
    @classmethod
    def make_instance(typeclass, cls, properFraction, truncate, round, ceiling,
            floor):
        attrs = {"properFraction":properFraction, "truncate":truncate,
                "round":round, "ceiling":ceiling, "floor":floor}
        build_instance(RealFrac, cls, attrs)
        return


@sig(H[(RealFrac, "a"), (Integral, "b")]/ "a" >> ("b", "a"))
def properFraction(x):
    """
    properFraction :: RealFrac a, Integral b => a -> (b, a)

    The function properFraction takes a real fractional number x and returns a
    pair (n,f) such that x = n+f, and:

        n is an integral number with the same sign as x; and
        f is a fraction with the same type and sign as x, and with absolute
        value less than 1.
    """
    return RealFrac[x].properFraction(x)


@sig(H[(RealFrac, "a"), (Integral, "b")]/ "a" >> "b")
def truncate(x):
    """
    truncate :: RealFrac a, Integral b => a -> b

    truncate(x) returns the integer nearest x between zero and x
    """
    return RealFrac[x].truncate(x)


@sig(H[(RealFrac, "a"), (Integral, "b")]/ "a" >> "b")
def round(x):
    """
    round :: RealFrac a, Integral b => a -> b

    round(x) returns the nearest integer to x; the even integer if x is
    equidistant between two integers
    """
    return RealFrac[x].round(x)


@sig(H[(RealFrac, "a"), (Integral, "b")]/ "a" >> "b")
def ceiling(x):
    """
    ceiling :: RealFrac a, Integral b => a -> b

    ceiling(x) returns the least integer not less than x
    """
    return RealFrac[x].ceiling(x)


@sig(H[(RealFrac, "a"), (Integral, "b")]/ "a" >> "b")
def floor(x):
    """
    floor :: RealFrac a, Integral b => a -> b

    floor(x) returns the greatest integer not greater than x
    """
    return RealFrac[x].floor(x)


instance(RealFrac, float).where(
    properFraction = lambda x: (int(math.floor(x)), x - math.floor(x)),
    truncate = lambda x: int(math.floor(x) if x > 0 else math.floor(x+1)),
    round = lambda x: int(round(x, 0)),
    ceiling = math.ceil,
    floor = math.floor
)


class RealFloat(Floating, RealFrac):
    """
    Efficient, machine-independent access to the components of a floating-point
    number.

    Dependencies:
        Floating, RealFrac

    Attributes:
        floatRange, isNan, isInfinite, isNegativeZero, atan2

    Minimal complete definition:
        floatRange, isNan, isInfinite, isNegativeZero, atan2
    """
    @classmethod
    def make_instance(typeclass, cls, floatRange, isNan, isInfinite,
            isNegativeZero, atan2):
        attrs = {"floatRange":floatRange, "isNan":isNan,
                "isInfinite":isInfinite, "isNegativeZero":isNegativeZero,
                "atan2":atan2}
        build_instance(RealFloat, cls, attrs)
        return


@sig(H[(RealFloat, "a")]/ "a" >> bool)
def isNaN(x):
    """
    isNaN :: RealFloat a => a -> bool

    True if the argument is an IEEE "not-a-number" (NaN) value
    """
    return RealFloat[x].isNan(x)


@sig(H[(RealFloat, "a")]/ "a" >> bool)
def isInfinite(x):
    """
    isInfinite :: RealFloat a => a -> bool

    True if the argument is an IEEE infinity or negative infinity
    """
    return RealFloat[x].isInfinite(x)


@sig(H[(RealFloat, "a")]/ "a" >> bool)
def isNegativeZero(x):
    """
    isNegativeZero :: RealFloat a => a -> bool

    True if the argument is an IEEE negative zero
    """
    return RealFloat[x].isNegativeZero(x)


@sig(H[(RealFloat, "a")]/ "a" >> "a" >> "a")
def atan2(y, x):
    """
    atan2 :: RealFloat a => a -> a -> a

    a version of arctangent taking two real floating-point arguments
    """
    return RealFloat[x].atan2(y, x)


instance(RealFloat, float).where(
    floatRange=(sys.float_info.min, sys.float_info.max),
    isNan=math.isnan,
    isInfinite=lambda x: x == float('inf') or x == -float('inf'),
    isNegativeZero=lambda x: math.copysign(1, x) == -1.0,
    atan2=math.atan2
)