"""Test sparse rational functions. """

import pytest

from diofant import I, Rational, sqrt
from diofant.core import symbols
from diofant.domains import QQ, ZZ
from diofant.polys.fields import FracElement, field
from diofant.polys.polyerrors import CoercionFailed
from diofant.polys.rings import ring


__all__ = ()


def test_FractionField___init__():
    F1 = ZZ.frac_field("x", "y")
    F2 = ZZ.frac_field("x", "y")
    F3 = ZZ.frac_field("x", "y", "z")

    assert F1.x == F1.gens[0]
    assert F1.y == F1.gens[1]
    assert F1.x == F2.x
    assert F1.y == F2.y
    assert F1.x != F3.x
    assert F1.y != F3.y

    F4 = ZZ.frac_field("gens")
    assert type(F4.gens) is tuple


def test_FractionField___hash__():
    F, x, y, z = field("x,y,z", QQ)
    assert hash(F)


def test_FractionField___eq__():
    assert field("x,y,z", QQ)[0] == field("x,y,z", QQ)[0]
    assert field("x,y,z", QQ)[0] is field("x,y,z", QQ)[0]

    assert field("x,y,z", QQ)[0] != field("x,y,z", ZZ)[0]
    assert field("x,y,z", QQ)[0] is not field("x,y,z", ZZ)[0]

    assert field("x,y,z", ZZ)[0] != field("x,y,z", QQ)[0]
    assert field("x,y,z", ZZ)[0] is not field("x,y,z", QQ)[0]

    assert field("x,y,z", QQ)[0] != field("x,y", QQ)[0]
    assert field("x,y,z", QQ)[0] is not field("x,y", QQ)[0]

    assert field("x,y", QQ)[0] != field("x,y,z", QQ)[0]
    assert field("x,y", QQ)[0] is not field("x,y,z", QQ)[0]


def test_FractionField_methods():
    F = ZZ.frac_field("x")

    assert F.domain_new(2) == ZZ(2)

    x = symbols("x")
    assert F.field_new(x**2 + x) == F.x**2 + F.x


def test_FracElement___hash__():
    F, x, y, z = field("x,y,z", QQ)
    assert hash(x*y/z)


def test_FracElement_copy():
    F, x, y, z = field("x,y,z", ZZ)

    f = x*y/3*z
    g = f.copy()

    assert f == g
    g.numer[(1, 1, 1)] = 7
    assert f != g


def test_FracElement_as_expr():
    F, x, y, z = field("x,y,z", ZZ)
    f = (3*x**2*y - x*y*z)/(7*z**3 + 1)

    X, Y, Z = F.symbols
    g = (3*X**2*Y - X*Y*Z)/(7*Z**3 + 1)

    assert f != g
    assert f.as_expr() == g

    X, Y, Z = symbols("x,y,z")
    g = (3*X**2*Y - X*Y*Z)/(7*Z**3 + 1)

    assert f != g
    assert f.as_expr(X, Y, Z) == g

    pytest.raises(ValueError, lambda: f.as_expr(X))


def test_FracElement_from_expr():
    x, y, z = symbols("x,y,z")
    F, X, Y, Z = field((x, y, z), ZZ)

    f = F.convert(1)
    assert f == 1 and isinstance(f, F.dtype)

    f = F.convert(Rational(3, 7))
    assert f == F(3)/7 and isinstance(f, F.dtype)

    f = F.convert(x)
    assert f == X and isinstance(f, F.dtype)

    f = F.convert(Rational(3, 7)*x)
    assert f == 3*X/7 and isinstance(f, F.dtype)

    f = F.convert(1/x)
    assert f == 1/X and isinstance(f, F.dtype)

    f = F.convert(x*y*z)
    assert f == X*Y*Z and isinstance(f, F.dtype)

    f = F.convert(x*y/z)
    assert f == X*Y/Z and isinstance(f, F.dtype)

    f = F.convert(x*y*z + x*y + x)
    assert f == X*Y*Z + X*Y + X and isinstance(f, F.dtype)

    f = F.convert((x*y*z + x*y + x)/(x*y + 7))
    assert f == (X*Y*Z + X*Y + X)/(X*Y + 7) and isinstance(f, F.dtype)

    f = F.convert(x**3*y*z + x**2*y**7 + 1)
    assert f == X**3*Y*Z + X**2*Y**7 + 1 and isinstance(f, F.dtype)

    pytest.raises(CoercionFailed, lambda: F.convert(2**x))
    pytest.raises(CoercionFailed, lambda: F.convert(7*x + sqrt(2)))

    F,  X, Y = field((2**x, y), ZZ)
    f = F.convert(2**(2*x) + 1)
    assert f == X**2 + 1


def test_FracElement_to_poly():
    F, x, y = field("x y", ZZ)
    pytest.raises(ValueError, lambda: (x/y).to_poly())


def test_FracElement__lt_le_gt_ge__():
    F, x, y = field("x,y", ZZ)

    assert F(1) < 1/x < 1/x**2 < 1/x**3
    assert F(1) <= 1/x <= 1/x**2 <= 1/x**3

    assert -7/x < 1/x < 3/x < y/x < 1/x**2
    assert -7/x <= 1/x <= 3/x <= y/x <= 1/x**2

    assert 1/x**3 > 1/x**2 > 1/x > F(1)
    assert 1/x**3 >= 1/x**2 >= 1/x >= F(1)

    assert 1/x**2 > y/x > 3/x > 1/x > -7/x
    assert 1/x**2 >= y/x >= 3/x >= 1/x >= -7/x


def test_FracElement__pos_neg__():
    F,  x, y = field("x,y", QQ)

    f = (7*x - 9)/y
    g = (-7*x + 9)/y

    assert +f == f
    assert +g == g
    assert -f == g
    assert -g == f


def test_FracElement___add__():
    F,  x, y = field("x,y", QQ)

    f, g = 1/x, 1/y
    assert f + g == g + f == (x + y)/(x*y)

    z = symbols('z')
    pytest.raises(TypeError, lambda: x + z)

    assert x + F.ring.gens[0] == F.ring.gens[0] + x == 2*x

    F,  x, y = field("x,y", ZZ)
    assert x + 3 == 3 + x
    assert x + QQ(3, 7) == QQ(3, 7) + x == (7*x + 3)/7

    Fuv,  u, v = field("u,v", ZZ)
    Fxyzt,  x, y, z, t = field("x,y,z,t", Fuv)

    f = (u*v + x)/(y + u*v)
    assert dict(f.numer) == {(1, 0, 0, 0): 1, (0, 0, 0, 0): u*v}
    assert dict(f.denom) == {(0, 1, 0, 0): 1, (0, 0, 0, 0): u*v}

    Ruv,  u, v = ring("u,v", ZZ)
    Fxyzt,  x, y, z, t = field("x,y,z,t", Ruv)

    f = (u*v + x)/(y + u*v)
    assert dict(f.numer) == {(1, 0, 0, 0): 1, (0, 0, 0, 0): u*v}
    assert dict(f.denom) == {(0, 1, 0, 0): 1, (0, 0, 0, 0): u*v}


def test_FracElement___sub__():
    F,  x, y = field("x,y", QQ)

    f, g = 1/x, 1/y
    assert f - g == (-x + y)/(x*y)

    assert x - F.ring.gens[0] == F.ring.gens[0] - x == 0

    F,  x, y = field("x,y", ZZ)
    assert x - 3 == -(3 - x)
    assert x - QQ(3, 7) == -(QQ(3, 7) - x) == (7*x - 3)/7

    Fuv,  u, v = field("u,v", ZZ)
    Fxyzt,  x, y, z, t = field("x,y,z,t", Fuv)

    f = (u*v - x)/(y - u*v)
    assert dict(f.numer) == {(1, 0, 0, 0): -1, (0, 0, 0, 0): u*v}
    assert dict(f.denom) == {(0, 1, 0, 0): 1, (0, 0, 0, 0): -u*v}

    Ruv,  u, v = ring("u,v", ZZ)
    Fxyzt,  x, y, z, t = field("x,y,z,t", Ruv)

    f = (u*v - x)/(y - u*v)
    assert dict(f.numer) == {(1, 0, 0, 0): -1, (0, 0, 0, 0): u*v}
    assert dict(f.denom) == {(0, 1, 0, 0): 1, (0, 0, 0, 0): -u*v}

    Fuv,  u, v = field("u,v", ZZ)
    Rxyz,  x, y, z = ring("x,y,z", Fuv)

    f = u - x
    assert dict(f) == {(0, 0, 0): u, (1, 0, 0): -Fuv.one}


def test_FracElement___mul__():
    F,  x, y = field("x,y", QQ)

    f, g = 1/x, 1/y
    assert f*g == g*f == 1/(x*y)

    assert x*F.ring.gens[0] == F.ring.gens[0]*x == x**2

    F,  x, y = field("x,y", ZZ)
    assert x*3 == 3*x
    assert x*QQ(3, 7) == QQ(3, 7)*x == 3*x/7

    Fuv,  u, v = field("u,v", ZZ)
    Fxyzt,  x, y, z, t = field("x,y,z,t", Fuv)

    f = ((u + 1)*x*y + 1)/((v - 1)*z - t*u*v - 1)
    assert dict(f.numer) == {(1, 1, 0, 0): u + 1, (0, 0, 0, 0): 1}
    assert dict(f.denom) == {(0, 0, 1, 0): v - 1, (0, 0, 0, 1): -u*v, (0, 0, 0, 0): -1}

    Ruv,  u, v = ring("u,v", ZZ)
    Fxyzt,  x, y, z, t = field("x,y,z,t", Ruv)

    f = ((u + 1)*x*y + 1)/((v - 1)*z - t*u*v - 1)
    assert dict(f.numer) == {(1, 1, 0, 0): u + 1, (0, 0, 0, 0): 1}
    assert dict(f.denom) == {(0, 0, 1, 0): v - 1, (0, 0, 0, 1): -u*v, (0, 0, 0, 0): -1}


def test_FracElement___truediv__():
    F,  x, y = field("x,y", QQ)

    f, g = 1/x, 1/y
    assert f/g == y/x

    assert x/F.ring.gens[0] == F.ring.gens[0]/x == 1

    F,  x, y = field("x,y", ZZ)
    assert x*3 == 3*x
    assert x/QQ(3, 7) == (QQ(3, 7)/x)**-1 == 7*x/3

    pytest.raises(ZeroDivisionError, lambda: x/0)
    pytest.raises(ZeroDivisionError, lambda: 1/(x - x))
    pytest.raises(ZeroDivisionError, lambda: x/(x - x))

    Fuv,  u, v = field("u,v", ZZ)
    Fxyzt,  x, y, z, t = field("x,y,z,t", Fuv)

    f = (u*v)/(x*y)
    assert dict(f.numer) == {(0, 0, 0, 0): u*v}
    assert dict(f.denom) == {(1, 1, 0, 0): 1}

    g = (x*y)/(u*v)
    assert dict(g.numer) == {(1, 1, 0, 0): 1}
    assert dict(g.denom) == {(0, 0, 0, 0): u*v}

    Ruv,  u, v = ring("u,v", ZZ)
    Fxyzt,  x, y, z, t = field("x,y,z,t", Ruv)

    f = (u*v)/(x*y)
    assert dict(f.numer) == {(0, 0, 0, 0): u*v}
    assert dict(f.denom) == {(1, 1, 0, 0): 1}

    g = (x*y)/(u*v)
    assert dict(g.numer) == {(1, 1, 0, 0): 1}
    assert dict(g.denom) == {(0, 0, 0, 0): u*v}

    Fuv,  u, v = field("u,v", ZZ)
    Rxyz,  x, y, z = ring("x,y,z", Fuv)

    pytest.raises(TypeError, lambda: u/x)


def test_FracElement___pow__():
    F,  x, y = field("x,y", QQ)

    f, g = 1/x, 1/y

    assert f**3 == 1/x**3
    assert g**3 == 1/y**3

    assert (f*g)**3 == 1/(x**3*y**3)
    assert (f*g)**-3 == (x*y)**3

    pytest.raises(ZeroDivisionError, lambda: (x - x)**-3)


def test_FracElement_diff():
    F,  x, y, z = field("x,y,z", ZZ)

    assert ((x**2 + y)/(z + 1)).diff(x) == 2*x/(z + 1)

    F,  x, y = field('x,y', QQ.algebraic_field(I))

    assert ((x - y)/x).diff(x) == y/x**2


def test_FracElement___call__():
    F,  x, y, z = field("x,y,z", ZZ)
    f = (x**2 + 3*y)/z

    pytest.raises(ValueError, lambda: f(1, 1, 1, 1))

    r = f(1, 1, 1)
    assert r == 4 and not isinstance(r, FracElement)
    pytest.raises(ZeroDivisionError, lambda: f(1, 1, 0))

    Fz = ZZ.frac_field("z")
    assert f(1, 1) == 4/Fz.z


def test_FracElement_evaluate():
    F,  x, y, z = field("x,y,z", ZZ)
    Fyz = field("y,z", ZZ)[0]
    f = (x**2 + 3*y)/z

    assert f.evaluate(x, 0) == 3*Fyz.y/Fyz.z
    pytest.raises(ZeroDivisionError, lambda: f.evaluate(z, 0))


def test_FracElement_subs():
    F,  x, y, z = field("x,y,z", ZZ)
    f = (x**2 + 3*y)/z

    assert f.subs({(x, 0)}) == 3*y/z
    assert f.subs({x: 0}) == 3*y/z
    pytest.raises(ValueError, lambda: f.subs(object()))
    pytest.raises(ZeroDivisionError, lambda: f.subs({z: 0}))

    assert f.subs([(x, 0), (y, 1)]) == 3/z


def test_FracElement_compose():
    pass
