'''
Created on Jun 27, 2013

@author: Chris
'''

import random
import hashlib

class Board(object): 
    
    SHUFFLES = 200
    ROWS = 6
    COLS = 7
    BLANK_TILE = ' '
    
    # Function: Constructor
    # ---------------------
    # Create a new puzzle state. You can optionally 
    # pass in the pieces as a list of lists. There is 
    # no safegaurd against passing in pieces that violate
    # the ROWS and COLS constants.
    def __init__(self, other = None):
        if other:
            self.pieces = self.copyPieces(other.pieces)
            self.turn = other.turn
            self.lastRow = other.lastRow
            self.lastCol = other.lastCol
        else:
            self.pieces = Board.createEmptyBoard()
            self.turn = True
            self.lastRow = None
            self.lastCol = None
        self.heights = self.getHeights()

    
    # Function: Get Pieces
    # ---------------------
    # Returns the list of lists that is used to 
    # represent the pieces of this puzzle state
    def getPieces(self):
        return self.pieces
    
    def printBoard(self):
        for r in range(Board.ROWS - 1, 0 - 1, -1):
            row = self.pieces[r]
            rowString = ''
            for value in row:
                rowString += str(value) + ' '
            print rowString
        print ''

    def isUserTurn(self):
        return self.turn
           
    # Function: Get Successor State
    # ---------------------
    # Given an action, creates a new state by
    # applying the action to this state.
    def getSuccessorState(self, action):
        newPuzzle = Board(self)
        newPuzzle.makeMove(action, newPuzzle.turn)
        return newPuzzle
     
    # Function: Get Legal Actions
    # ---------------------
    # Returns all the actions (as ints) that
    # can legally be applied from the current state     
    def getLegalActions(self):
        legalActions = []
        for col in range(Board.COLS):
            if self.heights[col] < (Board.ROWS):
                legalActions.append(col)
        return legalActions

    def getHeight(self, col):
        return self.heights[col]

    def makeMove(self, move, isUser):
        row = self.getHeight(move)
        self.heights[move] += 1
        self.pieces[row][move] = self.getString(isUser)
        self.lastRow = row
        self.lastCol = move
        self.turn = not self.turn

    def isTerminal(self):
        if self.lastRow == None or self.lastCol == None: return False
        piece = self.pieces[self.lastRow][self.lastCol]
        assert piece != ' '
        for dRow in range(-1, 2):
            for dCol in range(-1, 2):
                if dRow != 0 or dCol != 0:
                    if self.hasSolution(dRow, dCol, piece):
                        return True
        return False

    def hasSolution(self, dRow, dCol, piece):
        countDir1 = self.countChange(dRow, dCol, piece)
        countDir2 = self.countChange(-dRow, -dCol, piece)
        return countDir1 + countDir2 + 1 >= 4

    def countChange(self, dRow, dCol, piece):
        r = self.lastRow + dRow
        c = self.lastCol + dCol
        count = 0
        for i in range(4):
            if not self.inBounds(r, c): return count
            if self.pieces[r][c] != piece: return count
            r += dRow
            c += dCol
            count += 1
        return count          
              
              
               
    #######################################
    # PRIVATE STATIC VARIABLES         
    # You can ignore all these fields :)
    #######################################
    ACTION_MAP = {
        'up': (-1, 0),
        'down': (1, 0),
        'left': (0, -1),
        'right': (0, 1)
    }
    OPPOSITE_MAP = {
        'left':'right',
        'right':'left',
        'up':'down',
        'down':'up'
    }

    def digest(self):
        string = self.toString()
        return hashlib.md5(string).hexdigest()

    def toString(self):
        string = ''
        for r in range(Board.ROWS - 1, 0 - 1, -1):
            row = self.pieces[r]
            rowString = ''
            for value in row:
                rowString += str(value) + ' '
            string += rowString + '\n'
        return string

    def inBounds(self, row, col):
        if row < 0 or row >= Board.ROWS: return False
        if col < 0 or col >= Board.COLS: return False
        return True

    def getString(self, isUser):
        if isUser: return 'h'
        return 'c'
    
    def copyPieces(self, pieces):
        newPieces = []
        for oldRow in pieces:
            newRow = []
            for value in oldRow:
                newRow.append(value)
            newPieces.append(newRow)
        return newPieces

    def getHeights(self):
        heights = []
        for c in range(Board.COLS):
            height = 0
            for r in range(Board.ROWS):
                if self.pieces[r][c] != ' ':
                    height += 1
            heights.append(height)
        return heights
    
    @staticmethod
    def createEmptyBoard():
        board = []
        for r in range(Board.ROWS):
            row = []
            for c in range(Board.COLS):
                row.append(' ')
            board.append(row)
        return board
                
    def applyAction(self, action):
        dRow, dCol = Puzzle.ACTION_MAP[action]
        newBlankRow = self.blankRow + dRow
        newBlankCol = self.blankCol + dCol
        
        movingPiece = self.pieces[newBlankRow][newBlankCol]
        self.pieces[self.blankRow][self.blankCol] = movingPiece
        self.pieces[newBlankRow][newBlankCol] = ' '
        
        self.blankRow = newBlankRow
        self.blankCol = newBlankCol
    
    def isLegalPos(self, row, col):
        if row < 0 or row >= Puzzle.ROWS: return False
        if col < 0 or col >= Puzzle.COLS: return False
        return True
    
    def getBlankCol(self, blankRowIndex):
        blankRow = self.pieces[blankRowIndex]
        return blankRow.index(' ')
    
    def getBlankRow(self):
        for i in range(len(self.pieces)):
            if ' ' in self.pieces[i]:
                return i
        raise Exception('no blank found')
    
    def shufflePieces(self):
        lastAction = 'left'
        for _ in range(Puzzle.SHUFFLES):
            actions = self.getLegalActions()
            opposite = Puzzle.OPPOSITE_MAP[lastAction]
            if opposite in actions:
                index = actions.index(opposite)
                del actions[index]
            action = random.choice(actions)
            lastAction = action
            self.applyAction(action)
    
    def __str__(self):
        string = ''
        for row in self.pieces:
            rowString = ''
            for value in row:
                rowString += str(value) + ' '
            string += rowString
        return string
    
    
    
