# Initialization script executed by using "import glife".
# Based on python/life/__init__.py in Eugene Langvagen's PLife.

import golly
import sys
import time

__doc__ = """High-level scripting aids for Golly.""";

# --------------------------------------------------------------------

class rect(list):
    """A simple class to make it easier to manipulate rectangles."""

    def visible(self):
        """Return true if rect is completely visible in viewport."""
        return golly.visrect( [self.x, self.y, self.wd, self.ht] )

    def __init__(self, R = []):
        if len(R) == 0:
            self.empty = True
        elif len(R) == 4:
            self.empty = False
            self.x  = self.left   = R[0]
            self.y  = self.top    = R[1]
            self.wd = self.width  = R[2]
            self.ht = self.height = R[3]
            if self.wd <= 0: raise ValueError("rect width must be > 0")
            if self.ht <= 0: raise ValueError("rect height must be > 0")
            self.right  = self.left + self.wd - 1
            self.bottom = self.top  + self.ht - 1
        else:
            raise TypeError("rect arg must be [] or [x,y,wd,ht]")
        list.__init__(self, R)

# --------------------------------------------------------------------

# Define some useful synonyms:

# for golly.clear and golly.advance
inside = 0
outside = 1

# for golly.flip
left_right = 0
top_bottom = 1
up_down = 1

# for golly.rotate
clockwise = 0
anticlockwise = 1

# for golly.setcursor (must match strings in Cursor Mode submenu)
draw =    "Draw"
pick =    "Pick"
select =  "Select"
move =    "Move"
zoomin =  "Zoom In"
zoomout = "Zoom Out"

# --------------------------------------------------------------------

# Define some transformation matrices:

identity = ( 1,  0,  0,  1)

flip     = (-1,  0,  0, -1)
flip_x   = (-1,  0,  0,  1)
flip_y   = ( 1,  0,  0, -1)

swap_xy      = ( 0,  1,  1,  0)
swap_xy_flip = ( 0, -1, -1,  0)

# Rotation:

rcw  = ( 0, -1,  1,  0)
rccw = ( 0,  1, -1,  0)

# --------------------------------------------------------------------

def rule(s = "B3/S23"):
    """\
 Set the rule for the Game of Life.
 Although it affects subsequent calls to pattern.evolve(),
 only the last call to this function matters for the viewer."""
    golly.setrule(s)

# --------------------------------------------------------------------

def description(s):
    """Supply a textual description to the whole pattern."""
    for line in s.split("\n"):
        print "#D", line

# --------------------------------------------------------------------

def compose(S, T):
    """\
 Return the composition of two transformations S and T.
 A transformation is a tuple of the form (x, y, A), which denotes
 multiplying by matrix A and then translating by vector (x, y).
 These tuples can be passed to pattern.__call__()."""
    x = S[0]; y = S[1]; A = S[2]
    s = T[0]; t = T[1]; B = T[2]
    return (x * B[0] + y * B[1] + s, x * B[2] + y * B[3] + t,
       (A[0] * B[0] + A[2] * B[1], A[1] * B[0] + A[3] * B[1],
        A[0] * B[2] + A[2] * B[3], A[1] * B[2] + A[3] * B[3]))

# --------------------------------------------------------------------

class pattern(list):
    """This class represents a cell list."""

    def __add__(self, q):
        """Join patterns."""
        return pattern(golly.join(self, q))

    def __getitem__(self, N):
        """\
  The __getitem__() function is an alias to evolve().
  It allows to access the pattern's phases as elements of an array."""
        return self.evolve(N)

    def __call__(self, x, y, A = identity):
        """The same as 'apply(A).translate(x, y)'."""
        return pattern(golly.transform(self, x, y, *A))

    def translate(self, x, y):
        """Translate the pattern."""
        return self(x, y)

    def apply(self, A):
        """\
  Apply a matrix transformation to the pattern.
  Predefined matrices are:
  identity, flip, flip_x, flip_y, swap_xy, swap_xy_flip,
  rcw (rotate clockwise) and rccw (rotate counter-clockwise)."""
        return self(0, 0, A)

    def put(self, x = 0, y = 0, A = identity):
        """Paste pattern into current universe."""
        golly.putcells(self, x, y, *A)

    def display(self, title = "untitled", x = 0, y = 0, A = identity):
        """Paste pattern into new universe and display it all."""
        golly.new(title)
        golly.putcells(self, x, y, *A)
        golly.fit()
        golly.setcursor(zoomin)

    def save(self, fn, desc = None):
        """\
  Save the pattern to file 'fn' in RLE format.
  An optional description 'desc' may be given."""
        golly.store(self, fn, desc)

    def evolve(self, N):
        """\
  Return N-th generation of the pattern.
  Once computed, the N-th generation is remembered and quickly accessible.
  It is also the base for computing generations subsequent to N-th."""
        if N < 0:
            raise ValueError("backward evolving requested")
        if self.__phases.has_key(N):
            return self.__phases[N]
        M = 0
        for k in self.__phases.keys():
            if M < k < N: M = k
        p = self.__phases[N] = pattern(golly.evolve(self.__phases[M], N - M))
        return p

    def __init__(self, P = [], x0 = 0, y0 = 0, A = identity):
        """\
  Initialize a pattern from argument P.
  P may be another pattern, a cell list, or a multi-line string.
  A cell list should look like [x1, y1, x2, y2, ...];
  a string may be in one of the two autodetected formats:
  'visual' or 'RLE'.
  o  'visual' format means that the pattern is represented
     in a visual way using symbols '*' (on cell), '.' (off cell)
     and '\\n' (newline), just like in Life 1.05 format.
     (Note that an empty line should contain at least one dot).
  o  'RLE' format means that a string is Run-Length Encoded.
     The format uses 'o' for on-cells, 'b' for off-cells and
     '$' for newlines.
     Moreover, any of these symbols may be prefixed by a number,
     to denote that symbol repeated that number of times.

  When P is a string, an optional transformation
  (x0, y0, A) may be specified.
  """
        self.__phases = dict()

        if type(P) == list:
            list.__init__(self, P)
        elif type(P) == pattern:
            list.__init__(self, list(P))
        elif type(P) == str:
            list.__init__(self, golly.parse(P, x0, y0, *A))
        else:
            raise TypeError("list or string is required here")
        self.__phases[0] = self

# --------------------------------------------------------------------

def load(fn):
    # note that top left cell of bounding box will be at 0,0
    return pattern(golly.load(fn))

# --------------------------------------------------------------------

def getminbox(patt):
    # return a rect which is the minimal bounding box of given pattern;
    # note that if the pattern is a multi-state list then any dead cells
    # are included in the bounding box
    minx =  sys.maxint
    maxx = -sys.maxint
    miny =  sys.maxint
    maxy = -sys.maxint
    clist = list(patt)
    clen = len(clist)
    inc = 2
    if clen & 1 == 1:
        # multi-state list (3 ints per cell)
        inc = 3
        # ignore padding int if it is present
        if clen % 3 == 1: clen -= 1

    for x in xrange(0, clen, inc):
        if clist[x] < minx: minx = clist[x]
        if clist[x] > maxx: maxx = clist[x]

    for y in xrange(1, clen, inc):
        if clist[y] < miny: miny = clist[y]
        if clist[y] > maxy: maxy = clist[y]

    return rect( [ minx, miny, maxx - minx + 1, maxy - miny + 1 ] )

# --------------------------------------------------------------------

def validint(s):
    # return True if given string represents a valid integer
    if len(s) == 0: return False
    s = s.replace(",","")
    if s[0] == '+' or s[0] == '-': s = s[1:]
    return s.isdigit()

# --------------------------------------------------------------------

def getposint():
    # return current viewport position as integer coords
    x, y = golly.getpos()
    return int(x), int(y)

# --------------------------------------------------------------------

def setposint(x,y):
    # convert integer coords to strings and set viewport position
    golly.setpos(str(x), str(y))

# --------------------------------------------------------------------

def getstring(prompt):
    # this routine is deprecated
    golly.warn("Change the script to use the getstring() command\n"+
               "from golly rather than from glife.")
    golly.exit("")
