import pygame
from pygame.locals import *


class Goban:
    """Represents the go board. Handles stone placement, captures, etc"""

    # enum values for the board array
    EMPTY=0
    WHITE=1
    BLACK=2
    # GRAY_WHITE=3
    # GRAY_BLACK=4

    def __init__(self, board_size=19):
        # Build the board intersections
        self.board_size = board_size
        num_points = board_size * board_size
        self.board = [Goban.EMPTY] * num_points

        self.def_draw_codes = self._make_default_draw_codes()

        self.to_move = Goban.BLACK
        self.black_captures = 0
        self.white_captures = 0
        self.last_move = None
        self.passed_last = False
        self.ko = None
        self.hover = None
        self.elapsed_time = 0
        self.winner = Goban.EMPTY


    def set_hover(self, pos):
        rpos = self._real_pos(pos)
        if rpos == self.hover:
            return

        if not self._valid_move(rpos) or self.to_move == Goban.EMPTY:
            self.clear_hover()
            return

        self.hover = rpos

    
    def clear_hover(self):
        self.hover = None
        

    def play_move(self, pos):
        if self.to_move == Goban.EMPTY:
            return

        rpos = self._real_pos(pos)
        
        if not self._valid_move(rpos):
            return

        self.board[rpos] = self.to_move
        self._capture(rpos)
        self.last_move = rpos
        self.passed_last = False

        self.to_move = self._other_color(self.to_move)
        self.clear_hover()



    # fixme: need to handle post-game stuff here... scoring code
    def pass_move(self):
        if self.passed_last:
            self.to_move = Goban.EMPTY
        else:
            self.to_move = self._other_color(self.to_move)
            self.passed_last = True

        self.last_move = None
        self.ko = None


    def resign(self):
        self.passed_last = False
        self.last_move = None
        self.ko = None
        self.winner = self._other_color(self.to_move)
        self.to_move = Goban.EMPTY


    def _capture(self, pos):
        """Look for stones captured on the 4 sides of pos, remove them and increment
        capture counter. This pos must be a *real* position value, not an x,y tuple."""

        # If we get here, we've definitely played a move,
        # clearing any existing ko point
        self.ko = None

        # Who are we capturing
        who = self._other_color(self.to_move)

        captures = 0

        for p in self._neighbors(pos):
            if not self._on_board(p):
                continue
                
            if not self._num_liberties(p, who):
                captures += self._delete_group(p)

        if self.to_move == Goban.BLACK:
            self.black_captures += captures
        elif self.to_move == Goban.WHITE:
            self.white_captures += captures

        # Check for ko
        if captures == 1 and self._num_liberties(pos, self.to_move):
            # find the empty point
            for p in self._neighbors(pos):
                if self.board[p] == Goban.EMPTY:
                    self.ko = p
                    break



    def _valid_move(self, pos):
        if not self._on_board(pos):
            return False

        # Can't play atop another stone or on the ko point
        if self.board[pos] != Goban.EMPTY or pos == self.ko:
            return False

        # Temporarily place the stone
        self.board[pos] = self.to_move

        liberties = self._num_liberties(pos, self.to_move)
        opponent = self._other_color(self.to_move)

        kills_group = False
        for d in self._neighbors(pos):
            if not self._on_board(d):
                continue
            
            if self._num_liberties(d, opponent) == 0:
                kills_group = True
                break

        # Remove temporary stone
        self.board[pos] = Goban.EMPTY

        return liberties > 0 or kills_group



    # Recursively find whether there are liberties for the group
    # at pos.
    def _num_liberties(self, pos, who):
        if not self._on_board(pos) or self.board[pos] != who:
            return -1

        bs = self.board_size * self.board_size
        checked = [False] * bs

        return self._num_liberties_r(pos, who, checked)


    def _num_liberties_r(self, pos, who, checked=None):
        if checked[pos]:
            return 0
        else:
            checked[pos] = True

        square = self.board[pos]

        if square == Goban.EMPTY:
            return 1
        elif square != who:
            return 0
        else:
            liberties = 0
            for d in self._neighbors(pos):
                liberties += self._num_liberties_r(d, who, checked)

            return liberties



    # We don't need to worry about crossing ourselves with the
    # recursion here, because we've already deleted the stone.
    def _delete_group(self, pos):
        if not self._on_board(pos):
            return

        who = self.board[pos]

        if who == Goban.EMPTY:
            return 0

        return self._delete_group_r(pos, who)

        if who == Goban.EMPTY:
            return 0

        self.board[x][y].state = Goban.EMPTY



    def _delete_group_r(self, pos, who):
        if not self._on_board(pos):
            return

        if self.board[pos] != who:
            return 0

        self.board[pos] = Goban.EMPTY

        num_deleted = 1

        num_deleted += self._delete_group_r(pos + 1, who)
        num_deleted += self._delete_group_r(pos - 1, who)
        num_deleted += self._delete_group_r(pos + self.board_size, who)
        num_deleted += self._delete_group_r(pos - self.board_size, who)

        return num_deleted



    def draw_board(self, size, img_res):
        ret = pygame.Surface((size,size))

        inc = size / self.board_size;

        for pos in range(len(self.board)):
            point = self.board[pos]

            if point == Goban.EMPTY:
                code = self.def_draw_codes[pos]
            elif point == Goban.BLACK:
                code = 'b'
            elif point == Goban.WHITE:
                code = 'w'
            
            if pos == self.last_move:
                code = code + 'T'

            if pos == self.ko:
                code = code + 'C'

            s = img_res[code]
            s = pygame.transform.scale(s, (inc, inc))
            ret.blit(s, ((pos % self.board_size) *inc, (pos / self.board_size) *inc))
            
            if self.hover == pos:
                c = img_res['bH']
                if self.to_move == Goban.WHITE:
                    c = img_res['wH']
                c = pygame.transform.scale(c, (inc, inc))
                ret.blit(c, ((pos % self.board_size) *inc, (pos / self.board_size) *inc))

        return ret.convert_alpha()



    def draw_info(self):
        textbox = pygame.Surface((150, 300))
        textbox = textbox.convert()
        textbox.fill((250, 250, 250))

        font = pygame.font.Font(None, 24)
        # time = font.render('Time: {:02d}:{:02d}'.format(self.elapsed_time / 60, self.elapsed_time % 60), 1, (10, 10, 10))
        heading = font.render('Captures', 1, (10, 10, 10))
        black_cap = font.render('Black: {}'.format(self.black_captures), 1, (10, 10, 10))
        white_cap = font.render('White: {}'.format(self.white_captures), 1, (10, 10, 10))
        if self.to_move == Goban.BLACK:
            turn = font.render('To move: Black', 1, (10, 10, 10))
        elif self.to_move == Goban.WHITE:
            turn = font.render('To move: White', 1, (10, 10, 10))
        else:
            if self.winner == Goban.WHITE:
                turn = font.render('Winner: White', 1, (10, 10, 10))
            elif self.winner == Goban.BLACK:
                turn = font.render('Winner: Black', 1, (10, 10, 10))
            else:
                turn = font.render('Scoring', 1, (10, 10, 10))

        textbox.blit(heading, (0, 0))
        textbox.blit(black_cap, (0, 28))
        textbox.blit(white_cap, (0, 56))
        textbox.blit(turn, (0, 100))
        # textbox.blit(time, (0, 150))

        return textbox



    def _make_default_draw_codes(self):
        ret = []

        for pos in range(len(self.board)):
            if pos == 0:
                ret.append('ul')
            elif pos == self.board_size - 1:
                ret.append('ur')
            elif pos == self.board_size * self.board_size - self.board_size:
                ret.append('dl')
            elif pos == self.board_size * self.board_size - 1:
                ret.append('dr')
            elif pos in [60, 66, 72, 174, 180, 186, 288, 294, 300]:
                ret.append('h')
            elif pos < self.board_size - 1:
                ret.append('u')
            elif pos % self.board_size == 0:
                ret.append('l')
            elif pos > (self.board_size * self.board_size - self.board_size - 1):
                ret.append('d')
            elif pos % self.board_size == 18:
                ret.append('r')
            else:
                ret.append('m')

        return ret
            


    def _real_pos(self, pos):
        x,y = pos
        return x * self.board_size + y


    def _on_board(self, pos):
        return pos >= 0 or pos < self.board_size * self.board_size


    def _other_color(self, color):
        if color == Goban.BLACK:
            return Goban.WHITE
        elif color == Goban.WHITE:
            return Goban.BLACK


    def _neighbors(self, pos):
        neighbors = []
        if pos >= self.board_size:
            neighbors.append(pos - self.board_size)
        if pos <= self.board_size * self.board_size - self.board_size - 1:
            neighbors.append(pos + self.board_size)
        if pos % self.board_size != 0:
            neighbors.append(pos - 1)
        if (pos + 1) % self.board_size != 0:
            neighbors.append(pos + 1)

        return neighbors