r"""
Echelon matrices over finite fields.
"""
#*****************************************************************************
#       Copyright (C) 2014 Vincent Delecroix <20100.delecroix@gmail.com>
#
#  Distributed under the terms of the GNU General Public License (GPL)
#  as published by the Free Software Foundation; either version 2 of
#  the License, or (at your option) any later version.
#                  http://www.gnu.org/licenses/
#*****************************************************************************
from __future__ import print_function

from sage.matrix.matrix0 cimport Matrix

def reduced_echelon_matrix_iterator(K, k, n, bint sparse=False, bint copy=True, bint set_immutable=False):
    r"""
    An iterator over `(k,n)` reduced echelon matrices over the finite field `K`.

    INPUT:

    - ``K`` -- a finite field

    - ``k`` -- number of rows (or the size of the subspace)

    - ``n`` -- number of columns (or the dimension of the ambient space)

    - ``sparse`` -- boolean (default is ``False``)

    - ``copy`` -- boolean. If set to ``False`` then iterator yields the same matrix
      over and over (but with different entries).  Default is ``True`` which is
      safer but might be slower.

    - ``set_immutable`` -- boolean. If set to ``True`` then the output matrices
      are immutable. This option automatically turns ``copy`` into ``True``.


    .. NOTE::

        We ensure that the iteration order is so that all matrices with given
        pivot columns are generated consecutively. Furthermore, the order in
        which the pivot columns appear is lexicographic.

        It would be faster to generate the pivots columns following a Gray code.
        There would be only one pivot changing at a time, avoiding the possibly
        expensive ``m0.__copy__()``. However that would modify the generation
        order some functions depend upon.

    EXAMPLES::

        sage: from sage.matrix.echelon_matrix import reduced_echelon_matrix_iterator
        sage: it = reduced_echelon_matrix_iterator(GF(2),2,3)
        sage: for m in it:
        ....:     print(m)
        ....:     print(m.pivots())
        ....:     print("*******")
        [1 0 0]
        [0 1 0]
        (0, 1)
        *******
        [1 0 0]
        [0 1 1]
        (0, 1)
        *******
        [1 0 1]
        [0 1 0]
        (0, 1)
        *******
        [1 0 1]
        [0 1 1]
        (0, 1)
        *******
        [1 0 0]
        [0 0 1]
        (0, 2)
        *******
        [1 1 0]
        [0 0 1]
        (0, 2)
        *******
        [0 1 0]
        [0 0 1]
        (1, 2)
        *******

    TESTS:

    Testing cardinalities::

        sage: q = 71
        sage: F = GF(q)
        sage: len(list(reduced_echelon_matrix_iterator(F, 1, 3, copy=False))) == q**2+q+1
        True
        sage: len(list(reduced_echelon_matrix_iterator(F, 2, 3, copy=False))) == q**2+q+1
        True

    Testing options::

        sage: it = reduced_echelon_matrix_iterator(GF(4,'z'), 2, 4, copy=False)
        sage: next(it) is next(it)
        True
        sage: for a in it: pass

        sage: it = reduced_echelon_matrix_iterator(GF(4,'z'), 2, 4, set_immutable=True)
        sage: all(a.is_immutable() and a.echelon_form() == a for a in it)
        True
    """
    cdef Matrix m0,m,mm
    cdef int i
    n = int(n)
    k = int(k)

    if n < k:
        raise NotImplementedError("echelon matrix with fewer rows than columns "
                "i.e. not full rank) are not implemented")

    from sage.matrix.matrix_space import MatrixSpace
    from itertools import combinations,product

    copy = copy or set_immutable

    m0 = MatrixSpace(K,k,n,sparse=sparse)()
    Klist = list(K)
    K1 = K.one()

    # First, we select which columns will be pivots:
    for pivots in combinations(range(n),k):
        m = m0.__copy__()
        free_positions = []
        for i in range(k):
            m[i,pivots[i]] = K1
            for j in range(pivots[i]+1,n):
                if j not in pivots:
                    free_positions.append((i,j))

        # Next, we fill in those entries that are not
        # determined by the echelon form alone:
        num_free_pos = len(free_positions)
        for v in product(Klist, repeat=num_free_pos):
            for i in range(num_free_pos):
                m[free_positions[i]] = v[i]

            if copy:
                mm = m.__copy__()
                mm.cache('pivots',pivots)
                mm.cache('rank',k)
                mm.cache('in_echelon_form',True)
                if set_immutable:
                    mm.set_immutable()
                yield mm
            else:
                yield m
            del v   # hack: Python itertools reuses the tuple if nobody else uses it
        del pivots  # hack: Python itertools reuses the tuple if nobody else uses it

