"""
Utilities for iterating over group elements from a finite generating set.

In Sage, many algebraic objects (finite abelian groups, finite multiplicative
groups, etc.) provide a list of *generators* via a ``.gens()`` method.
These generators are used to build (finite) combinations by taking suitable
products or sums of generator powers.

This module provides pure-Python iterator implementations that enumerate all
elements in the finite subgroup generated by ``M.gens()``:

* ``multiplicative_iterator(M)`` yields all products of generator powers,
  using each generator's finite multiplicative order.
* ``abelian_iterator(M)`` yields all sums of generator multiples,
  using each generator's finite additive order.

The iteration is implemented as a mixed-radix "odometer": an exponent/count
vector is incremented and carried across generators, and the current element
is updated incrementally.
"""

# ****************************************************************************
#       Copyright (C) 2005 William Stein <wstein@gmail.com>
#
#  Distributed under the terms of the GNU General Public License (GPL)
#
#    This code is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#    General Public License for more details.
#
#  The full text of the GPL is available at:
#
#                  https://www.gnu.org/licenses/
# ****************************************************************************


def multiplicative_iterator(M):
    """
    Iterate over elements of ``M`` using multiplicative generator combinations.

    The object ``M`` is expected to provide:

    - ``M.gens()``: a list/sequence of generators for the finite group.
    - ``M(1)``: a callable constructor returning the identity element.
    - Each generator ``g`` must implement ``g.multiplicative_order()``.

    The iterator yields each group element produced by multiplying generator
    powers where the exponent of generator ``g`` runs from ``0`` up to
    ``g.multiplicative_order() - 1``.

    INPUT:

    - ``M`` -- a finite multiplicative group-like object

    OUTPUT:

    - elements of ``M`` in a deterministic order

    If not all generators have finite multiplicative order, an
    ``ArithmeticError`` is raised.
    """
    from sage.rings.infinity import infinity

    generators = M.gens()
    if not generators:
        yield M(1)
        return

    # For each generator, compute the maximum count (order-1).
    limits = [g.multiplicative_order() for g in generators]
    for i in range(len(limits)):
        if limits[i] is infinity:
            raise ArithmeticError("%s is not finite." % M)

        # We increment "counts" from 0 through (order-1), inclusive.
        limits[i] -= 1

    current = M(1)
    yield current

    # `counts` is the current exponent vector (for an odometer).
    counts = [0] * len(generators)
    while counts != limits:
        current *= generators[0]
        counts[0] += 1

        i = 0
        # Carry to the next generator when the current one overflows.
        while i < len(counts) - 1 and counts[i] > limits[i]:
            counts[i] = 0
            counts[i + 1] += 1
            current *= generators[i + 1]
            i += 1
        yield current


def abelian_iterator(M):
    """
    Iterate over elements of ``M`` using additive generator combinations.

    The object ``M`` is expected to provide:

    - ``M.gens()``: a list/sequence of generators for the finite abelian group.
    - ``M(0)``: a callable constructor returning the additive identity element.
    - Each generator ``g`` must implement ``g.additive_order()``.

    The iterator yields each group element produced by taking sums of
    generator multiples where the coefficient of generator ``g`` runs from
    ``0`` up to ``g.additive_order() - 1``.

    INPUT:

    - ``M`` -- a finite additive group-like object

    OUTPUT:

    - elements of ``M`` in a deterministic order

    If not all generators have finite additive order, an
    ``ArithmeticError`` is raised.
    """
    from sage.rings.infinity import infinity

    generators = M.gens()
    if not generators:
        yield M(0)
        return

    # For each generator, compute the maximum count (order-1).
    limits = [g.additive_order() for g in generators]
    for i in range(len(limits)):
        if limits[i] is infinity:
            raise ArithmeticError("%s is not finite." % M)

        # We increment "counts" from 0 through (order-1), inclusive.
        limits[i] -= 1

    current = M(0)
    yield current

    # `counts` is the current coefficient vector (for an odometer).
    counts = [0] * len(generators)
    while counts != limits:
        counts[0] = counts[0] + 1
        current = current + generators[0]
        i = 0

        # Carry to the next generator when the current one overflows.
        while i < len(counts) - 1 and counts[i] > limits[i]:
            counts[i] = 0
            counts[i + 1] += 1
            current += generators[i + 1]
            i += 1
        yield current
