# -*- coding: utf-8 -*-
r"""
Class Groups of Number Fields

An element of a class group is stored as a pair consisting of both an explicit
ideal in that ideal class, and a list of exponents giving that ideal class in
terms of the generators of the parent class group. These can be accessed with
the ``ideal()`` and ``exponents()`` methods respectively.

EXAMPLES::

    sage: K.<a> = NumberField(x^2 + 23)
    sage: I = K.class_group().gen(); I
    Fractional ideal class (2, 1/2*a - 1/2)
    sage: I.ideal()
    Fractional ideal (2, 1/2*a - 1/2)
    sage: I.exponents()
    (1,)

    sage: I.ideal() * I.ideal()
    Fractional ideal (4, 1/2*a + 3/2)
    sage: (I.ideal() * I.ideal()).reduce_equiv()
    Fractional ideal (2, 1/2*a + 1/2)
    sage: J = I * I; J    # class group multiplication is automatically reduced
    Fractional ideal class (2, 1/2*a + 1/2)
    sage: J.ideal()
    Fractional ideal (2, 1/2*a + 1/2)
    sage: J.exponents()
    (2,)

    sage: I * I.ideal()   # ideal classes coerce to their representative ideal
    Fractional ideal (4, 1/2*a + 3/2)

    sage: O = K.OK(); O
    Maximal Order in Number Field in a with defining polynomial x^2 + 23
    sage: O*(2, 1/2*a + 1/2)
    Fractional ideal (2, 1/2*a + 1/2)
    sage: (O*(2, 1/2*a + 1/2)).is_principal()
    False
    sage: (O*(2, 1/2*a + 1/2))^3
    Fractional ideal (1/2*a - 3/2)
"""

from sage.groups.abelian_gps.values import AbelianGroupWithValues_class, AbelianGroupWithValuesElement
from sage.groups.abelian_gps.abelian_group_element import AbelianGroupElement
from sage.structure.sequence import Sequence
from sage.structure.element import MonoidElement
from sage.groups.old import Group
from sage.rings.arith import LCM
from sage.rings.all import ZZ


class FractionalIdealClass(AbelianGroupWithValuesElement):
    r"""
    A fractional ideal class in a number field.

    EXAMPLES::

        sage: G = NumberField(x^2 + 23,'a').class_group(); G
        Class group of order 3 with structure C3 of Number Field in a with defining polynomial x^2 + 23
        sage: I = G.0; I
        Fractional ideal class (2, 1/2*a - 1/2)
        sage: I.ideal()
        Fractional ideal (2, 1/2*a - 1/2)

        EXAMPLES::

            sage: K.<w>=QuadraticField(-23)
            sage: OK=K.ring_of_integers()
            sage: C=OK.class_group()
            sage: P2a,P2b=[P for P,e in (2*OK).factor()]
            sage: c = C(P2a); c
            Fractional ideal class (2, 1/2*w - 1/2)
            sage: c.gens()
            (2, 1/2*w - 1/2)
    """
    def __init__(self, parent, element, ideal=None):
        """
        Returns the ideal class of this fractional ideal.

        EXAMPLE::

            sage: K.<a> = NumberField(x^2 + 23,'a'); G = K.class_group()
            sage: G(K.ideal(13, a + 4))
            Fractional ideal class (13, 1/2*a + 17/2)
        """
        if element is None:
            element = parent._ideal_log(ideal)
        AbelianGroupWithValuesElement.__init__(self, parent, element, ideal)

    def _repr_(self):
        r"""
        Return string representation of this fractional ideal class.

         EXAMPLE::

            sage: K.<a> = NumberField(x^2 + 23,'a'); G = K.class_group()
            sage: G(K.ideal(13, a + 4))._repr_()
            'Fractional ideal class (13, 1/2*a + 17/2)'
            sage: G(K.ideal(59, a+6))._repr_()
            'Trivial principal fractional ideal class'
        """
        if self.is_principal():
            return 'Trivial principal fractional ideal class'
        return 'Fractional ideal class %s'%self._value._repr_short()

    def _mul_(self, other):
        r"""
        Multiplication of two (S-)ideal classes.

        EXAMPLE::

            sage: G = NumberField(x^2 + 23,'a').class_group(); G
            Class group of order 3 with structure C3 of Number Field in a with defining polynomial x^2 + 23
            sage: I = G.0; I
            Fractional ideal class (2, 1/2*a - 1/2)
            sage: I*I # indirect doctest
            Fractional ideal class (2, 1/2*a + 1/2)
            sage: I*I*I # indirect doctest
            Trivial principal fractional ideal class

            sage: K.<a> = QuadraticField(-14)
            sage: I = K.ideal(2,a)
            sage: S = (I,)
            sage: CS = K.S_class_group(S)
            sage: G = K.ideal(3,a+1)
            sage: CS(G)*CS(G)
            Trivial S-ideal class
        """
        m = AbelianGroupElement._mul_(self, other)
        m._value = (self.ideal() * other.ideal()).reduce_equiv()
        return m

    def _div_(self, other):
        r"""
        Division of two ideal classes.

        EXAMPLE::

            sage: G = NumberField(x^2 + 23,'a').class_group(); G
            Class group of order 3 with structure C3 of Number Field in a with defining polynomial x^2 + 23
            sage: I = G.0; I
            Fractional ideal class (2, 1/2*a - 1/2)
            sage: I*I # indirect doctest
            Fractional ideal class (2, 1/2*a + 1/2)
            sage: I*I*I # indirect doctest
            Trivial principal fractional ideal class
        """
        m = AbelianGroupElement._div_(self, other)
        m._value = (self.ideal() / other.ideal()).reduce_equiv()
        return m

    def __pow__(self, n):
        r"""
        Raise this element to the power n.

        EXAMPLE::

            sage: K.<a> = NumberField(x^3 - 3*x + 8)
            sage: C=K.class_group()
            sage: c = C(2, a)
            sage: c^2
            Fractional ideal class (2, a^2 + 2*a - 1)
            sage: c^3
            Trivial principal fractional ideal class
            sage: c^1000
            Fractional ideal class (2, a)
            sage: (c^2)^2
            Fractional ideal class (2, a)
        """
        # We use MonoidElement's __pow__ routine, since that does
        # repeated squaring, and hence the ideal gets reduced as
        # we go along; actually computing self._value ** n would
        # be disastrous.
        n = n % self.order()
        return MonoidElement.__pow__(self, n)

    def inverse(self):
        r"""
        Return the multiplicative inverse of this ideal class.

        EXAMPLE::

            sage: K.<a> = NumberField(x^3 - 3*x + 8); G = K.class_group()
            sage: G(2, a).inverse()
            Fractional ideal class (2, a^2 + 2*a - 1)
            sage: ~G(2, a)
            Fractional ideal class (2, a^2 + 2*a - 1)
        """
        m = AbelianGroupElement.inverse(self)
        m._value = (~self.ideal()).reduce_equiv()
        return m

    __invert__ = inverse

    def is_principal(self):
        r"""
        Returns True iff this ideal class is the trivial (principal) class

        EXAMPLES::

            sage: K.<w>=QuadraticField(-23)
            sage: OK=K.ring_of_integers()
            sage: C=OK.class_group()
            sage: P2a,P2b=[P for P,e in (2*OK).factor()]
            sage: c=C(P2a)
            sage: c.is_principal()
            False
            sage: (c^2).is_principal()
            False
            sage: (c^3).is_principal()
            True
        """
        return self.is_one()

    def reduce(self):
        r"""
        Return representative for this ideal class that has been
        reduced using PARI's idealred.

        EXAMPLES::

            sage: k.<a> = NumberField(x^2 + 20072); G = k.class_group(); G
            Class group of order 76 with structure C38 x C2
            of Number Field in a with defining polynomial x^2 + 20072
            sage: I = (G.0)^11; I
            Fractional ideal class (41, 1/2*a + 5)
            sage: J = G(I.ideal()^5); J
            Fractional ideal class (115856201, 1/2*a + 40407883)
            sage: J.reduce()
            Fractional ideal class (57, 1/2*a + 44)
            sage: J == I^5
            True
        """
        return self.parent()(self.ideal().reduce_equiv())

    def ideal(self):
        r"""
        Return a representative ideal in this ideal class.

        EXAMPLE::

            sage: K.<w>=QuadraticField(-23)
            sage: OK=K.ring_of_integers()
            sage: C=OK.class_group()
            sage: P2a,P2b=[P for P,e in (2*OK).factor()]
            sage: c=C(P2a); c
            Fractional ideal class (2, 1/2*w - 1/2)
            sage: c.ideal()
            Fractional ideal (2, 1/2*w - 1/2)
        """
        return self.value()

    def representative_prime(self, norm_bound=1000):
        r"""
        Return a prime ideal in this ideal class.

        INPUT:

        ``norm_bound`` (positive integer) -- upper bound on the norm of primes tested.

        EXAMPLE::

           sage: K.<a> = NumberField(x^2+31)
           sage: K.class_number()
           3
           sage: Cl = K.class_group()
           sage: [c.representative_prime() for c in Cl]
           [Fractional ideal (3),
           Fractional ideal (2, 1/2*a + 1/2),
           Fractional ideal (2, 1/2*a - 1/2)]

           sage: K.<a> = NumberField(x^2+223)
           sage: K.class_number()
           7
           sage: Cl = K.class_group()
           sage: [c.representative_prime() for c in Cl]
           [Fractional ideal (3),
           Fractional ideal (2, 1/2*a + 1/2),
           Fractional ideal (17, 1/2*a + 7/2),
           Fractional ideal (7, 1/2*a - 1/2),
           Fractional ideal (7, 1/2*a + 1/2),
           Fractional ideal (17, 1/2*a + 27/2),
           Fractional ideal (2, 1/2*a - 1/2)]
        """
        if self.value().is_prime():
            return self.value()
        c = self.reduce()
        if c.value().is_prime():
            return c.value()
        # otherwise we just search:
        Cl = self.parent()
        K = Cl.number_field()
        from sage.rings.all import RR
        for P in K.primes_of_bounded_norm_iter(RR(norm_bound)):
            if Cl(P)==c:
                return P
        raise RuntimeError("No prime of norm less than %s found in class %s" % (norm_bound, c))


    def gens(self):
        r"""
        Return generators for a representative ideal in this
        (S-)ideal class.

        EXAMPLES::

            sage: K.<w>=QuadraticField(-23)
            sage: OK = K.ring_of_integers()
            sage: C = OK.class_group()
            sage: P2a,P2b=[P for P,e in (2*OK).factor()]
            sage: c = C(P2a); c
            Fractional ideal class (2, 1/2*w - 1/2)
            sage: c.gens()
            (2, 1/2*w - 1/2)
       """
        return self.ideal().gens()



class SFractionalIdealClass(FractionalIdealClass):
    r"""
    An S-fractional ideal class in a number field for a tuple of primes S.

        EXAMPLES::

            sage: K.<a> = QuadraticField(-14)
            sage: I = K.ideal(2,a)
            sage: S = (I,)
            sage: CS = K.S_class_group(S)
            sage: J = K.ideal(7,a)
            sage: G = K.ideal(3,a+1)
            sage: CS(I)
            Trivial S-ideal class
            sage: CS(J)
            Trivial S-ideal class
            sage: CS(G)
            Fractional S-ideal class (3, a + 1)

        EXAMPLES::

            sage: K.<a> = QuadraticField(-14)
            sage: I = K.ideal(2,a)
            sage: S = (I,)
            sage: CS = K.S_class_group(S)
            sage: J = K.ideal(7,a)
            sage: G = K.ideal(3,a+1)
            sage: CS(I).ideal()
            Fractional ideal (2, a)
            sage: CS(J).ideal()
            Fractional ideal (7, a)
            sage: CS(G).ideal()
            Fractional ideal (3, a + 1)


        EXAMPLES::

            sage: K.<a> = QuadraticField(-14)
            sage: I = K.ideal(2,a)
            sage: S = (I,)
            sage: CS = K.S_class_group(S)
            sage: G = K.ideal(3,a+1)
            sage: CS(G).inverse()
            Fractional S-ideal class (3, a + 2)

    TESTS::

        sage: K.<a> = QuadraticField(-14)
        sage: I = K.ideal(2,a)
        sage: S = (I,)
        sage: CS = K.S_class_group(S)
        sage: J = K.ideal(7,a)
        sage: G = K.ideal(3,a+1)
        sage: CS(I).order()
        1
        sage: CS(J).order()
        1
        sage: CS(G).order()
        2
    """

    def _repr_(self):
        r"""
        Returns a string representation of the S-ideal class of this fractional ideal.

        EXAMPLE::

            sage: K.<a> = QuadraticField(-14)
            sage: I = K.ideal(2,a)
            sage: J = K.ideal(3, a + 2)
            sage: S = (I,)
            sage: CS = K.S_class_group(S)
            sage: CS(J)
            Fractional S-ideal class (3, a + 2)
            sage: CS(J^2)
            Trivial S-ideal class
        """
        if self.is_trivial():
            return 'Trivial S-ideal class'
        return 'Fractional S-ideal class %s' % self._value._repr_short()



class ClassGroup(AbelianGroupWithValues_class):
    r"""
    The class group of a number field.

    EXAMPLES::

        sage: K.<a> = NumberField(x^2 + 23)
        sage: G = K.class_group(); G
        Class group of order 3 with structure C3 of Number Field in a with defining polynomial x^2 + 23
        sage: G.category()
        Category of finite commutative groups

    Note the distinction between abstract generators, their ideal, and
    exponents::

        sage: C = NumberField(x^2 + 120071, 'a').class_group(); C
        Class group of order 500 with structure C250 x C2
        of Number Field in a with defining polynomial x^2 + 120071
        sage: c = C.gen(0)
        sage: c  # random
        Fractional ideal class (5, 1/2*a + 3/2)
        sage: c.ideal()  # random
        Fractional ideal (5, 1/2*a + 3/2)
        sage: c.ideal() is c.value()   # alias
        True
        sage: c.exponents()
        (1, 0)
    """
    Element = FractionalIdealClass

    def __init__(self, gens_orders, names, number_field, gens, proof=True):
        r"""
        Create a class group.

        TESTS::

            sage: K.<a> = NumberField(x^2 + 23)
            sage: G = K.class_group()
            sage: TestSuite(G).run()
        """
        AbelianGroupWithValues_class.__init__(self, gens_orders, names, gens,
                                              values_group=number_field.ideal_monoid())
        self._proof_flag = proof
        self._number_field = number_field

    def _element_constructor_(self, *args, **kwds):
        r"""
        Create an element of this class group from the given data. This may be:
        an ideal class in this number field; an ideal class in a subfield; or
        anything from which an ideal in this number field can be constructed.

        EXAMPLES::

            sage: K.<b> = NumberField(x^2 + 389)
            sage: C = K.class_group()
            sage: C(K.ideal(b)) # indirect doctest
            Trivial principal fractional ideal class
            sage: C(K.ideal(59049, b + 35312)) # indirect doctest
            Fractional ideal class (59049, b + 35312)
            sage: C((59049, b + 35312)) # indirect doctest
            Fractional ideal class (59049, b + 35312)
            sage: C(59049, b + 35312) # indirect doctest
            Fractional ideal class (59049, b + 35312)

            sage: K.<a> = QuadraticField(-23)
            sage: L.<b> = K.extension(x^2 - 2)
            sage: CK = K.class_group()
            sage: CL = L.class_group()
            sage: [CL(I).exponents() for I in CK]
            [(0,), (2,), (4,)]
        """
        if isinstance(args[0], FractionalIdealClass):
            return self.element_class(self, None, self._number_field.ideal(args[0].ideal()))
        else:
            I = self._number_field.ideal(*args, **kwds)
            if I.is_zero():
                raise TypeError("The zero ideal is not a fractional ideal")
            return self.element_class(self, None, I)

    def _ideal_log(self, ideal):
        """
        Compute the exponents from the ``ideal``.

        Used by the element constructor if necessary.

        EXAMPLES::

            sage: K.<a> = NumberField(x^2 + 23,'a')
            sage: G = K.class_group()
            sage: g = G.an_element()
            sage: G._ideal_log(g.ideal())
            (1,)
            sage: g.exponents()
            (1,)
        """
        return tuple(ZZ(order) for order in ideal.ideal_class_log(proof=self._proof_flag))

    def gens_ideals(self):
        r"""
        Return generating ideals for the (S-)class group.

        This is an alias for :meth:`gens_values`.

        OUTPUT:

        A tuple of ideals, one for each abstract Abelian group generator.

        EXAMPLES::

            sage: K.<a> = NumberField(x^4 + 23)
            sage: K.class_group().gens_ideals()   # random gens (platform dependent)
            (Fractional ideal (2, 1/4*a^3 - 1/4*a^2 + 1/4*a - 1/4),)

            sage: C = NumberField(x^2 + x + 23899, 'a').class_group(); C
            Class group of order 68 with structure C34 x C2 of Number Field
            in a with defining polynomial x^2 + x + 23899
            sage: C.gens()
            (Fractional ideal class (7, a + 5), Fractional ideal class (5, a + 3))
            sage: C.gens_ideals()
            (Fractional ideal (7, a + 5), Fractional ideal (5, a + 3))
        """
        return self.gens_values()

    def __iter__(self):
        r"""
        Return an iterator of all ideal classes in this class group.

        EXAMPLES::

            sage: K.<a> = NumberField(x^4 + 23)
            sage: G = K.class_group()
            sage: G
            Class group of order 3 with structure C3 of Number Field
            in a with defining polynomial x^4 + 23
            sage: list(G)
            [Trivial principal fractional ideal class,
             Fractional ideal class (2, 1/4*a^3 - 1/4*a^2 + 1/4*a - 1/4),
             Fractional ideal class (2, 1/2*a^2 + 1/2)]
            sage: G.list()
            (Trivial principal fractional ideal class,
             Fractional ideal class (2, 1/4*a^3 - 1/4*a^2 + 1/4*a - 1/4),
             Fractional ideal class (2, 1/2*a^2 + 1/2))

        TESTS::

            sage: K.<a> = NumberField(x^2 + 1)
            sage: G = K.class_group()
            sage: G
            Class group of order 1 of Number Field in a with defining polynomial x^2 + 1
            sage: list(G)
            [Trivial principal fractional ideal class]
            sage: G.list()
            (Trivial principal fractional ideal class,)
        """
        from sage.misc.mrange import mrange
        orders = self.gens_orders()
        T = mrange(orders)
        g = self.gens()
        for t in T:
            I = self(1)
            for i, j in enumerate(t):
                I *= g[i]**j
            yield I
        if not T:
            yield self(1)

    def _repr_(self):
        r"""
        Return string representation of self.

        EXAMPLES::

            sage: C = NumberField(x^2 + 23, 'a').class_group()
            sage: C._repr_()
            'Class group of order 3 with structure C3 of Number Field in a with defining polynomial x^2 + 23'
        """
        s = 'Class group of order %s '%self.order()
        if self.order() > 1:
            s += 'with structure %s '%self._group_notation(self.gens_orders())
        s += 'of %s'%self.number_field()
        return s

    def number_field(self):
        r"""
        Return the number field that this (S-)class group is attached to.

        EXAMPLES::

            sage: C = NumberField(x^2 + 23, 'w').class_group(); C
            Class group of order 3 with structure C3 of Number Field in w with defining polynomial x^2 + 23
            sage: C.number_field()
            Number Field in w with defining polynomial x^2 + 23

            sage: K.<a> = QuadraticField(-14)
            sage: CS = K.S_class_group(K.primes_above(2))
            sage: CS.number_field()
            Number Field in a with defining polynomial x^2 + 14
        """
        return self._number_field





class SClassGroup(ClassGroup):
    r"""
    The S-class group of a number field.

    EXAMPLES::

        sage: K.<a> = QuadraticField(-14)
        sage: S = K.primes_above(2)
        sage: K.S_class_group(S).gens()   # random gens (platform dependent)
        (Fractional S-ideal class (3, a + 2),)

        sage: K.<a> = QuadraticField(-974)
        sage: CS = K.S_class_group(K.primes_above(2)); CS
        S-class group of order 18 with structure C6 x C3
        of Number Field in a with defining polynomial x^2 + 974
        sage: CS.gen(0) # random
        Fractional S-ideal class (3, a + 2)
        sage: CS.gen(1) # random
        Fractional S-ideal class (31, a + 24)
    """
    Element = SFractionalIdealClass

    def __init__(self, gens_orders, names, number_field, gens, S, proof=True):
        r"""
        Create an S-class group.

        EXAMPLES::

            sage: K.<a> = QuadraticField(-14)
            sage: I = K.ideal(2,a)
            sage: S = (I,)
            sage: K.S_class_group(S)
            S-class group of order 2 with structure C2 of Number Field in a with defining polynomial x^2 + 14
            sage: K.<a> = QuadraticField(-105)
            sage: K.S_class_group([K.ideal(13, a + 8)])
            S-class group of order 4 with structure C2 x C2 of Number Field in a with defining polynomial x^2 + 105
        """
        AbelianGroupWithValues_class.__init__(self, gens_orders, names, gens,
                                              values_group=number_field.ideal_monoid())
        self._proof_flag = proof
        self._number_field = number_field
        self._S = S

    def S(self):
        r"""
        Return the set (or rather tuple) of primes used to define this class group.

        EXAMPLES::

            sage: K.<a> = QuadraticField(-14)
            sage: I = K.ideal(2,a)
            sage: S = (I,)
            sage: CS = K.S_class_group(S);CS
            S-class group of order 2 with structure C2 of Number Field in a with defining polynomial x^2 + 14
            sage: T = tuple([])
            sage: CT = K.S_class_group(T);CT
            S-class group of order 4 with structure C4 of Number Field in a with defining polynomial x^2 + 14
            sage: CS.S()
            (Fractional ideal (2, a),)
            sage: CT.S()
            ()
        """
        return self._S

    def _ideal_log(self, ideal):
        """
        Compute the exponents from the ``ideal``.

        Used by the element constructor if necessary.

        EXAMPLES::

            sage: K.<a> = QuadraticField(-14)
            sage: I = K.ideal(2,a)
            sage: S = (I,)
            sage: CS = K.S_class_group(S)
            sage: s = CS.an_element()
            sage: CS._ideal_log(s.ideal())
            (1,)
            sage: s.exponents()
            (1,)
        """
        return tuple(ZZ(order) for order in ideal.S_ideal_class_log(self.S()))

    def _element_constructor_(self, *args, **kwds):
        r"""
        Create an element of this class group from the given data.

        EXAMPLES::

            sage: K.<a> = QuadraticField(-14)
            sage: I = K.ideal(2,a)
            sage: S = (I,)
            sage: CS = K.S_class_group(S)
            sage: J = K.ideal(7,a)
            sage: G = K.ideal(3,a+1)
            sage: CS(I)
            Trivial S-ideal class
            sage: CS(J)
            Trivial S-ideal class
            sage: CS(G)
            Fractional S-ideal class (3, a + 1)
        """
        if isinstance(args[0], FractionalIdealClass):
            return self.element_class(self, None, args[0].ideal())
        else:
            I = self.number_field().ideal(*args, **kwds)
            if I.is_zero():
                raise TypeError("The zero ideal is not a fractional ideal")
            return self.element_class(self, None, I)

    def _repr_(self):
        r"""
        Return string representation of this S-class group.

        EXAMPLES::

            sage: K.<a> = QuadraticField(-14)
            sage: CS = K.S_class_group(K.primes_above(2))
            sage: CS._repr_()
            'S-class group of order 2 with structure C2 of Number Field in a with defining polynomial x^2 + 14'
        """
        s = 'S-class group of order %s ' % self.order()
        if self.order() > 1:
            s += 'with structure %s ' % self._group_notation(self.gens_orders())
        s += 'of %s' % self.number_field()
        return s
