# A Python interface to libboard from gnugo
# Also has functions for printing the board via pygame,
# and for controlling that printing to some degree.
#
# I'm only implementing what I need for my own purposes.
# Further API is welcome!

from ctypes import *

try:
    import pygame
    have_pygame = True
except ImportError:
    print 'Warning: pygame is not installed. Goban.draw_board() and Goban.draw_info() will fail.'
    have_pygame = False

BOARDSIZE=361
MAX_MOVE_HISTORY=361

class board_state(Structure):
    _fields_ = [
        ('board_size', c_int),
        ('board', c_char * BOARDSIZE),
        ('board_ko_pos', c_int),
        ('black_captured', c_int),
        ('white_captured', c_int),
        ('initial_board', c_char * BOARDSIZE),

        ('initial_board_ko_pos', c_int),
        ('initial_white_captured', c_int),
        ('initial_black_captured', c_int),

        ('move_history_color', c_int * MAX_MOVE_HISTORY),
        ('move_history_pos', c_int * MAX_MOVE_HISTORY),
        ('move_history_pointer', c_int),

        ('komi', c_float),
        ('move_number', c_int)
        ]


class SGFTree(Structure):
    _fields_ = [
        ('root', c_void_p), # SGFNode *root;
        ('lastnode', c_void_p) # SGFNode *lastnode;
        ]


# class Gameinfo(Structure):
#     _fields_ = [
#         ('handicap', c_int),
#         ('to_move', c_int),
#         ('game_record', SGFTree),
#         ('computer_player', c_int),
#         ('outfilename', c_char * 128),
#         ('outfile', c_void_p)
#         ]



class Goban:
    # This is our dynamic interface to the gnugo libraries
    libboard = None
    BLACK = 2
    WHITE = 1
    EMPTY = 0

    def __init__(self):
        if not Goban.libboard:
            Goban.libboard = CDLL('lib/libboard.so')

        Goban.libboard.clear_board()
        self.to_move = Goban.BLACK
        self.hover = None


    def play_move(self, pos, color=None):
        """Make a move."""

        realpos = _real_pos(pos)

        if color is None:
            color = self.to_move

        if Goban.libboard.is_legal(realpos, color):
            Goban.libboard.play_move(realpos, color)
            return True
        else:
            return False


    def undo_move(self, n):
        """Undo n moves. Return True on success, False on failure. On failure, no moves are removed."""

        return Goban.libboard.undo_move(n)


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

        if Goban.libboard.is_legal(realpos):
            self.hover = realpos
        else:
            self.clear_hover()


    def clear_hover(self):
        self.hover = None


    def OTHER_COLOR(self, color):
        if color == Goban.WHITE:
            return Goban.BLACK
        elif color == Goban.BLACK:
            return Goban.WHITE
        else:
            return Goban.EMPTY
        
    
    def draw_board(self, size, img_res):
        """ Return a pygame.Surface() with an image of the board.
        If pygame isn't available, this function prints an error and returns harmlessly."""
        
        if not have_pygame:
            return

        ret = pygame.Surface((size,size))

        inc = size / 19

        p = c_int * 421
        board = p.in_dll(Goban.libboard, 'board')

        for pos in range(421):
            if _i(pos) < 0 or _j(pos) < 0:
                continue

            code = self._get_draw_code(pos, board[pos])
            if code == 'e':
                continue

            s = img_res[code]
            s = pygame.transform.scale(s, (inc, inc))
            ret.blit(s, (_i(pos)*inc, _j(pos)*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, (_i(pos)*inc, _j(pos)*inc))

        return ret.convert_alpha()
        

    def draw_info(self):
        """ Return a pygame.Surface() with an image of text describing the game state.
        If pygame isn't available, this function prints an error and returns harmlessly."""

        if not have_pygame:
            return

        textbox = pygame.Surface((150, 300))
        textbox = textbox.convert()
        textbox.fill((250, 250, 250))

        font = pygame.font.Font(None, 24)
        # heading = font.render('Captures', 1, (10, 10, 10))
        # black_cap = font.render('Black: {}'.format(self.captures[0]), 1, (10, 10, 10))
        # white_cap = font.render('White: {}'.format(self.captures[1]), 1, (10, 10, 10))
        # turn = font.render('Turn: {}'.format(['Black', 'White'][self.turn]), 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))

        return textbox


    def _get_draw_code(self, pos, board_value):
        if board_value == Goban.BLACK:
            return 'b'
        if board_value == Goban.WHITE:
            return 'w'
        if _i(pos) == 0 and _j(pos) == 0:
            return 'ul'
        if _i(pos) == 18 and _j(pos) == 0:
            return 'ur'
        if _i(pos) == 0 and _j(pos) == 18:
            return 'dl'
        if _i(pos) == 18 and _j(pos) == 18:
            return 'dr'
        if (_i(pos), _j(pos)) in [(3,3), (3,9), (3,15), (9,3), (9,9), (9,15), (15,3), (15,9), (15,15)]:
            return 'h'
        if _i(pos) == 0:
            return 'l'
        if _i(pos) == 18:
            return 'r'
        if _j(pos) == 0:
            return 'u'
        if _j(pos) == 18:
            return 'd'
        else:
            return 'm'


# This is equivalent to gnugo's POS macro
def _real_pos(pos):
    x,y = pos
    return 21 + x * 20 + y


def _i(pos):
    return (pos / 20) - 1


def _j(pos):
    return (pos % 20) - 1