# Copyright (C) 2012 Bob Bowles <bobjohnbowles@gmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.


from game import *
from board import *
from reversiToken import *
import ui, time



class Player(object):
    """This defines the behaviour and attributes common to all players of the
    game.
    A player is assigned a token.
    When it is the player's turn, the player selects a move and places their
    token on the board."""


    def __init__(self, name, choices, mode):
        """The player has a token that is assigned during instantiation."""
        # initialize a toggle for requesting hints
        self.hints = False
        self.name = name
        self.mode = mode
        self.strategies = [] # this is the plug for computer player AI
        self._chooseToken(choices)


    def beginGame(self):
        """Enables the player to do any necessary preparation at the start of
        the game (reset counters, etc.). This parent method does nothing.
        Implementations of PLayer must provide any required functionality."""
        pass


    def chooseMove(self, board):
        """Empty method to be implemented in subclasses. Returns the player's
        chosen move and the list of tokens to flip as a tuple."""
        return None, None


    def selectMove(self, board):
        """Display the current board and the scores, then select the move for
        the next turn on the board.
        Returns the chosen move and the list of opponent's tokens that will flip
        as a tuple. """
        if not self.mode.bench():
            self.displayBoard(board)
            self.displayScores(board)
        return self.chooseMove(board)


    def _chooseToken(self, choices):
        """The player chooses which token to use at the start of the game.
        The available choices are presented in a list. There are only 2 options.
        The second player has no real choice.
        If the seed letter is initially anything other than 'B' or 'W' a
        dialogue is implemented until a suitable choice has been made."""
        letter = ''
        if len(choices) == 1:
            # the computer (or second player) gets Hobson's choice!
            letter, self.token = choices.popitem()
        else:
            # the human's choice
            letter = ''
            while not letter in choices.keys():
                letter = \
                ui.chooseToken(str(self.name + _(', choose ') + \
                                   _(' or ').join(str('%s' % choices[k].name()) \
                                               for k in choices.keys())))\
                                               .upper()
            self.token = choices.pop(letter)

        if not self.mode.bench() and not self.token == DRAW_TK:
            ui.displayMessage(str(self.name + _(', you play ') + \
                                  self.token.name()))


    def displayBoard(self, board):
        """Display the current board."""
        ui.displayBoard(board)


    def displayScores(self, board):
        """Display the current scores for this player and their opponent."""
        ui.updateGameScores(board.getScores())



class Human(Player):
    """The human subclass of player selects their next move using keyboard input."""


    def chooseMove(self, board):
        """Display the board and accept keyboard input for the human player to
        make their choice of move. Various checks are applied to make sure the
        player types something meaningful to the program.
        Normal exit returns a list with the selected move and the list of tokens
        that are to be flipped.
        Exceptions may be generated by the player that require handling in the
        main game loop."""
        while True:
            move = ui.chooseMove(self)

            # are the coordinates on the board?
            if not move.isOnTheBoard():
                ui.displayMessage(_('Not on board'))
                continue

            # has the position already been taken?
            if not board.isPositionFree(move):
                ui.displayMessage(_('Position occupied'))
                continue

            # is the move valid, i.e., does it result in at least 1 flip?
            tokensToBeFlipped = board.isValidMove(self.token, move)
            if not tokensToBeFlipped:
                ui.displayMessage(\
                        _('Not valid - no flips'))
                continue

            # if the loop gets this far down the move must be OK.
            return move, tokensToBeFlipped


    def displayBoard(self, board):
        """Override the parent method to print hints if they have been
        requested."""
        if self.hints: Player.displayBoard(self, board.giveHints(self.token))
        else: Player.displayBoard(self, board)



class Computer(Player):
    """The computer player uses an AI algorithm to choose its next move."""


    def beginGame(self):
        """The AI player(s) may need to reset some of their strategies where
        these have kept a record of moves in the last game."""
        for strategy in self.strategies: strategy.reset()


    def chooseMove(self, board):
        """Implement the AI logic to choose the computer's next move.
        The computer has a very simple strategy:
        1)     if one of the board corners is a valid move, take it.
        2)     search all possible valid moves and choose the one that gives the
               highest score this turn.
        Returns the chosen move and the flips that will result from it as a
        tuple."""
        # pause here so the human opponent can see the move
        if self.mode.normal():
            ui.playerPause()

        elif self.mode.computer():
            time.sleep(5.0)

        validMoves = board.findAllValidMoves(self.token)
        random.shuffle(validMoves)

        # go through all the strategies this computer has till we find a move
        for strategy in self.strategies:
            move = strategy.findBestMove(self.token, board, validMoves)
            if move: return move

        # provided we have proper fallbacks we should never do this
        return False

