
import copy
import optparse

class TileIterator:
    def __init__( self, _elements, _symmetries, _count = -1 ):
        if _count < 0:
            assert len( _symmetries ) > 0
            _count = len( _symmetries[ 0 ] )

        for sym in _symmetries:
            ll = sym.len()
            assert ll == _count

        self.firstGuess = True
        self.guess = [ 0 ] * _count

        self.elements = copy.deepcopy( _elements )
        self.symmetries = copy.deepcopy( _symmetries )

    def expandGuess( self ):
        ret = []
        for index in self.guess:
            ret.append( self.elements[ index ] )
        return ret

    def incrementGuess( self ):
        max = len( self.elements )
        index = 0
        while True:
            if index >= len( self.guess ):
                return False
            self.guess[ index ] += 1
            if self.guess[ index ] >= max:
                self.guess[ index ] = 0
                index += 1
            else:
                return True

    def guessIsSmaller( self, _sym ):
        other = copy.copy( self.guess )
        ii = 0
        for index in _sym:
            other[ ii ] = self.guess[ index ]
            ii += 1
        for ii in range( len( self.guess ) ):
            if other[ ii ] > self.guess[ ii ]:
                return False
            elif other[ ii ] < self.guess[ ii ]:
                return True
        return False

    def __iter__( self ):
        return self

    def next( self ):
        if self.firstGuess:
            if len( self.elements ) > 0:
                self.firstGuess = False
                return self.expandGuess()
            else:
                raise StopIteration
        else:
            while self.incrementGuess():
                busted = False
                for sym in self.symmetries:
                    if self.guessIsSmaller( sym ):
                        busted = True
                        break
                if not busted:
                    return self.expandGuess()
            raise StopIteration

class Symmetry:
    def __init__( self, _sym ):
        self.symmetry = copy.copy( _sym )
        _sym.sort()
        assert _sym == range( len( _sym ) )

    def isIdentity( self ):
        return self.symmetry == range( self.len() )

    def __eq__( self, _other ):
        return self.symmetry == _other.symmetry

    def __hash__( self ):
        hh = 0
        for ii in range( self.len() ):
            hh = hh * self.len() + self.symmetry[ ii ]
        return hash( hh )

    def __iter__( self ):
        return self.symmetry.__iter__()

    def len( self ):
        return len( self.symmetry )

    def multiply( self, _sym ):
        assert self.len() == _sym.len()
        nn = [ 0 ] * len( self.symmetry )
        for ii in range( self.len() ):
            index = self.symmetry[ ii ]
            nn[ ii ] = _sym.symmetry[ index ]
        return Symmetry( nn )

class TileIteratorOptionsParser( optparse.OptionParser ):
    def __init__( self ):
        self.elements = []
        self.symmetries = set()

        self.parser = optparse.OptionParser( "usage: %prog [options]" )
        self.parser.add_option(
            "-e", "--element", action="callback", dest="elements",
            nargs=1, callback=self.addElement, help="the elements"
        )
        self.parser.add_option(
            "-s", "--symmetry", action="callback", dest="symmetries",
            callback=self.addSymmetry, help="a symmetry"
        )
        self.parser.add_option(
            "-c", "--count", action="store", type="int", dest="count",
            help="number of items to choose"
        )
        self.parser.count = None
        ( opts, args ) = self.parser.parse_args()

        if len( self.symmetries ) > 0:
            ee = self.symmetries.pop()
            self.symmetries.add( ee )
            self.count = ee.len()
            assert opts.count == None or opts.count == self.count
        else:
            assert not opts.count == None
            self.count = opts.count

    def addElement( self, _option, _opt_str, _value, _parser ):
        assert _value is None
        done = False
        value = []
        rargs = _parser.rargs
        while rargs:
            arg = rargs[0]
            if ( ( arg[:2] == "--" and len(arg) > 2 ) or
            ( arg[:1] == "-" and len(arg) > 1 and arg[1] != "-" ) ):
                break
            else:
                value.append( arg )
                del rargs[ 0 ]
        self.elements = value

    def addSymmetry( self, _option, _opt_str, _value, _parser ):
        assert _value is None
        done = False
        value = []
        rargs = _parser.rargs
        while rargs:
            arg = rargs[0]
            if arg[:1] == "-" and len(arg) > 1 and arg[1] != "-":
                break
            elif arg[:2] == "--" and len(arg) > 2:
                if len( value ) > 0:
                    self.addAllSymmetries( Symmetry( value ) )
                    value = []
                del rargs[ 0 ]
            else:
                value.append( int(arg)-1 )
                del rargs[ 0 ]
        if len( value ) > 0:
            self.addAllSymmetries( Symmetry( value ) )

    def isIdentity( self, _sym ):
        return _sym == range( len(_sym) )

    def addAllSymmetries( self, _sym ):
        if _sym.isIdentity() or _sym in self.symmetries:
            return

        ns = set( [ _sym ] )
        while len( ns ) > 0:
            self.symmetries.update( ns )
            old = ns
            ns = set( [] )
            for tt in self.symmetries:
                for ss in old:

                    aa = ss.multiply( tt )
                    if not aa.isIdentity():
                        if aa not in self.symmetries and aa not in ns:
                            ns.add( aa )

                    bb = tt.multiply( ss )
                    if not bb.isIdentity():
                        if bb not in self.symmetries and bb not in ns:
                            ns.add( bb )

if __name__ == "__main__":
    parser = TileIteratorOptionsParser()

    for ii in TileIterator(parser.elements,parser.symmetries,parser.count):
        line = ""
        for ee in ii:
            line += " " + ee
        print line[1:]
