# -*- coding: utf-8 -*-
# ENGG 2440A Lecture 3 -- python code
# Requires: python3; python scipy package


from matplotlib import pyplot
from random import randint

# plot a tiling for a 2**n by 2**n grid with missing square (mx, my)
def plot_tiling(n, mx, my):
    if mx < 0 or mx > 2**n or my < 0 or my > 2**n:
        print("Empty square is outside the grid")
        return
    g = Grid(n)
    g.tile(mx, my)
    g.plot_tiling()

class Grid:
    def __init__(self, n):
        self.n = n
        self.N = 2**n
        self.tiling = [[0] * self.N for i in range(self.N)]
        self.colors = range(16, 24)
    
    # compute a tiling of the grid with missing square mx, my
    def tile(self, mx, my):
        # mark the missing square
        self.mx = mx
        self.my = my
        
        # tile the grid recursively    
        Subgrid(self, 0, 0, self.n).tile(mx, my)
             
    # add a tile with bottom left corner at (x, y) of a given shape
    # rx, ry indicates the removed corner position (0, 0), (0, 1), (1, 0) or (1, 1)
    def add_tile(self, x, y, rx, ry):
        tile_color = self._find_available_color(x, y, rx, ry)

        for i in range(0, 2):
            for j in range(0, 2):
                if (i != rx or j != ry):
                    self.tiling[x + i][y + j] = tile_color
    
    # display the computed tiling
    def plot_tiling(self):
            pyplot.imshow(self.tiling, cmap='jet', interpolation='none')
                       
    # find an available color for the current tile that does not conflict with neighboring tiles
    def _find_available_color(self, x, y, rx, ry):
        neighbors = [(rx, ry), (-1, -1), (-1, 0), (-1, 1), (-1, 2), (0, 2), (1, 2), (2, 2), (2, 1), (2, 0), (2, -1), (1, -1), (0, -1)]
        forbidden_colors = []
        for i, j in neighbors:
            xn, yn = x + i, y + j
            if 0 <= xn and xn < self.N and 0 <= yn and yn < self.N:
                forbidden_colors.append(self.tiling[xn][yn])
        for c in self.colors:
            if not (c in forbidden_colors):
                return c       

class Subgrid(Grid):
    # instantiate a subgrid of grid with bottom left corner (x0, y0) and dimension 2**n by 2**n
    def __init__(self, grid, x0, y0, n):
        self.parent = grid
        self.x0 = x0
        self.y0 = y0
        self.n = n
        
    def tile(self, mx, my):
        if self.n == 1:
            self.add_tile(0, 0, mx, my)
        else:
            halfN = 2**(self.n - 1)

            # removed corner of central tile
            rx = mx // halfN
            ry = my // halfN
 
            for i in range(0, 2):
                for j in range(0, 2):
                    s = Subgrid(self, halfN * i, halfN * j, self.n - 1)
                    if i == rx and j == ry:
                        # tile quadrant containing removed square
                        s.tile(mx - halfN * i, my - halfN * j)
                    else:
                        # tile other quadrants
                        s.tile((1 - i) * (halfN - 1), (1 - j) * (halfN - 1))
            
            # cover central squares
            self.add_tile(halfN - 1, halfN - 1, rx, ry)
        
    def add_tile(self, x, y, rx, ry):
        self.parent.add_tile(x + self.x0, y + self.y0, rx, ry)
        
        
# play the clicker puzzle
# input the piece to be moved, 'q' to quit, 'i' to display inversions
def puzzle(show_inversions = False):
    blank = ' '
    board = [1, 2, 3, 4, 5, 6, 8, 7, blank]
    
    # play game
    while True:
        # print board
        print('\n', board[0], board[1], board[2], '\n', board[3], board[4], board[5], '\n', board[6], board[7], board[8])
        
        # print inversions
        if show_inversions:
            print('\nInversions: ', end='')
            for j in range(9):
                for i in range(j):
                    if board[i] != blank and board[j] != blank and board[i] > board[j]:
                        print('{', board[i], ',',  board[j], '}  ', end='')
            print()
            
        # make move
        move = input("Your move: ")
        try:
            move_pos = board.index(int(move))
            blank_pos = board.index(blank)
            if move_pos - blank_pos in [-3, -1, 1, 3]:
                board[blank_pos] = board[move_pos]
                board[move_pos] = blank
            else:
                print("Tile can't be moved")
        except:
            if move == 'i':
                show_inversions = not show_inversions
            elif move == 'q':
                return
            else:
                print("Incorrect input")            
    