# -*- coding: utf-8 -*-
__author__ = 'Robert Ancell <bob27@users.sourceforge.net>'
__license__ = 'GNU General Public License Version 2'
__copyright__ = 'Copyright 2005-2006  Robert Ancell'

import config
import player
import scene
import scene.cairo
import scene.opengl
import scene.human
import ui
import game
import chess.board

class MovePlayer(game.ChessPlayer):
    """This class provides a pseudo-player to watch for piece movements"""

    def __init__(self, view):
        """Constructor for a move player.
        
        'view' is the view to update
        """
        self.view = view
        game.ChessPlayer.__init__(self, '@view')

    # Extended methods

    def onPieceMoved(self, piece, start, end, delete):
        """Called by chess.board.ChessPlayer"""
        if self.view.moveNumber != -1:
            return
        animate = self.view.game.isStarted()
        p = self.view.scene.movePiece(piece, end, delete, animate)

        # If a player move notify when animation completes
        if animate and self.view.moveNumber == -1 and start is not None and start != end:
            self.view.scene.waitingPiece = p

    def onPlayerMoved(self, p, move):
        self.view._redrawHighlight()
        
    def onPlayerStartTurn(self, player):
        self.view._redrawHighlight()

class CairoPiece(scene.ChessPieceFeedback):
    """
    """

    def __init__(self, scene, piece):
        self.scene = scene
        self.piece = piece
        self.model = None
        self.location = ''

    def onDeleted(self):
        """Called by scene.ChessPieceFeedback"""
        self.scene.pieces.pop(self.piece)

    def onMoved(self):
        """Called by scene.ChessPieceFeedback"""
        self.scene.view._pieceMoved(self)

class SceneCairo(scene.SceneFeedback, scene.human.SceneHumanInput):
    """
    """

    def __init__(self, view):
        """
        """
        self.view = view
        self.controller = scene.cairo.Scene(self)
        self.game = view.game
        self.pieces = {}
        self.waitingPiece = None
        scene.human.SceneHumanInput.__init__(self)
        
    def getPieces(self):
        return self.pieces.values()

    def movePiece(self, piece, location, delete, animate):
        """
        """
        # Get the model for this piece creating one if it doesn't exist
        try:
            p = self.pieces[piece]
        except KeyError:
            # Make the new model
            pieceName = {chess.board.PAWN: 'pawn', chess.board.ROOK: 'rook', chess.board.KNIGHT: 'knight',
                         chess.board.BISHOP: 'bishop', chess.board.QUEEN: 'queen', chess.board.KING: 'king'}[piece.getType()]
            chessSet = {chess.board.WHITE: 'white', chess.board.BLACK: 'black'}[piece.getColour()]
            p = CairoPiece(self, piece)
            style = piecesStyle = config.get('piece_style')
            p.model = self.controller.addChessPiece(chessSet, pieceName, location, p, style=style)
            self.pieces[piece] = p

        # Move the model
        p.location = location
        p.model.move(location, delete, animate)
        
        return p

    # Extended methods

    def onRedraw(self):
        """Called by scene.cairo.Scene"""
        if self.game.view is not None and self.game.view.controller is not None:
            self.game.view.controller.render()

    def startAnimation(self):
        """Called by scene.cairo.Scene"""
        self.game.application.ui.controller.startAnimation()
        
    def getSquare(self, x, y):
        """Called by scene.human.SceneHumanInput"""
        return self.controller.getSquare(x, y)

    def playerIsHuman(self):
        """Called by scene.human.SceneHumanInput"""
        return self.game.currentPlayerIsHuman()

    def squareIsFriendly(self, coord):
        """Called by scene.human.SceneHumanInput"""
        return self.playerIsHuman() and self.game.squareIsFriendly(coord)
    
    def canMove(self, start, end):
        """Called by scene.human.SceneHumanInput"""
        return self.playerIsHuman() and self.game.getCurrentPlayer().canMove(start, end) # FIXME: Promotion type

    def selectSquare(self, coord):
        """Called by scene.human.SceneHumanInput"""
        self.view.setSelectedSquare(coord)

    def moveHuman(self, start, end):
        """Called by scene.human.SceneHumanInput"""
        self.game.moveHuman(start, end)
        
class OpenGLPiece(scene.ChessPieceFeedback):
    """
    """

    def __init__(self, scene, piece):
        self.scene = scene
        self.piece = piece
        self.model = None
        self.location = ''

    def onDeleted(self):
        """Called by scene.ChessPieceFeedback"""
        self.scene.pieces.pop(self.piece)

    def onMoved(self):
        """Called by scene.ChessPieceFeedback"""
        self.scene.view._pieceMoved(self)

class SceneOpenGL(scene.SceneFeedback, scene.human.SceneHumanInput):
    """
    """   

    def __init__(self, view):
        """Constructor for a glChess scene.
        

        """
        self.view = view
        self.game = view.game
        self.pieces = {}
        self.waitingPiece = None

        # Call parent constructors
        self.controller = scene.opengl.Scene(self)
        scene.human.SceneHumanInput.__init__(self)

    def getPieces(self):
        return self.pieces.values()
        
    def movePiece(self, piece, location, delete, animate):
        """
        """
        # Get the model for this piece creating one if it doesn't exist
        try:
            p = self.pieces[piece]
        except KeyError:
            # Make the new model
            pieceName = {chess.board.PAWN: 'pawn', chess.board.ROOK: 'rook', chess.board.KNIGHT: 'knight',
                         chess.board.BISHOP: 'bishop', chess.board.QUEEN: 'queen', chess.board.KING: 'king'}[piece.getType()]
            chessSet = {chess.board.WHITE: 'white', chess.board.BLACK: 'black'}[piece.getColour()]
            p = OpenGLPiece(self, piece)
            p.model = self.controller.addChessPiece(chessSet, pieceName, location, p)
            self.pieces[piece] = p
            
        # Move the model
        p.location = location
        p.model.move(location, delete)

        return p

    # Extended methods

    def onRedraw(self):
        """Called by scene.opengl.Scene"""
        if self.game.view is not None and self.game.view.controller is not None:
            self.game.view.controller.render()

    def startAnimation(self):
        """Called by scene.opengl.Scene"""
        self.game.application.ui.controller.startAnimation()
        
    def getSquare(self, x, y):
        """Called by scene.human.SceneHumanInput"""
        return self.controller.getSquare(x, y)

    def playerIsHuman(self):
        """Called by scene.human.SceneHumanInput"""
        return self.game.currentPlayerIsHuman()

    def squareIsFriendly(self, coord):
        """Called by scene.human.SceneHumanInput"""
        return self.playerIsHuman() and self.game.squareIsFriendly(coord)
    
    def canMove(self, start, end):
        """Called by scene.human.SceneHumanInput"""
        return self.playerIsHuman() and self.game.getCurrentPlayer().canMove(start, end) # FIXME: Promotion type
    
    def selectSquare(self, coord):
        """Called by scene.human.SceneHumanInput"""
        self.view.setSelectedSquare(coord)
    
    def moveHuman(self, start, end):
        """Called by scene.human.SceneHumanInput"""
        self.game.moveHuman(start, end)

class Splashscreen(ui.ViewFeedback):
    """
    """

    def __init__(self, application):
        """Constructor.
        
        'application' is ???
        """
        self.application = application
        self.cairoScene = scene.cairo.Scene(self)
        self.scene = scene.opengl.Scene(self)

    def updateRotation(self, animate = True):
        boardView = config.get('board_view')
        if boardView == 'black':
            rotation = 180.0
        else:
            rotation = 0.0
        self.cairoScene.controller.setBoardRotation(rotation, boardView == 'facetoface', animate)
        self.scene.controller.setBoardRotation(rotation, boardView == 'facetoface', animate)

    def onRedraw(self):
        """Called by scene.SceneFeedback"""
        if self.controller is not None:
            self.controller.render()
            
    def showBoardNumbering(self, showNumbering):
        """Called by ui.ViewFeedback"""
        self.scene.showBoardNumbering(showNumbering)
        self.cairoScene.showBoardNumbering(showNumbering)

    def showMoveHints(self, showHints):
        """Called by ui.ViewFeedback"""
        pass

    def renderGL(self, background_colour = (0, 0, 0)):
        """Called by ui.ViewFeedback"""
        self.scene.render(background_colour)
        
    def renderCairoStatic(self, context, background_color = (0, 0, 0)):
        """Called by ui.ViewFeedback"""
        return self.cairoScene.renderStatic(context, background_color)
        
    def renderCairoDynamic(self, context):
        """Called by ui.ViewFeedback"""
        self.cairoScene.renderDynamic(context)
    
    def reshape(self, width, height):
        """Called by ui.ViewFeedback"""
        self.scene.reshape(width, height)
        self.cairoScene.reshape(width, height)

    def select(self, x, y):
        pass
    
    def deselect(self, x, y):
        pass    

    def selectSquare(self, coord):
        pass
    
    def undo(self):
        """Called by ui.ViewFeedback"""
        pass

    def resign(self):
        """Called by ui.ViewFeedback"""
        pass
    
    def claimDraw(self):
        """Called by ui.ViewFeedback"""
        pass
    
    def setMoveNumber(self, moveNumber):
        """Called by ui.ViewFeedback"""
        pass

class View(ui.ViewFeedback):
    """
    """

    def __init__(self, game):
        """Constructor.
        
        'game' is the game this view is rendering
        """
        self.game = game
        
        # The move we are watching
        self.moveNumber = -1
        
        # The selected square
        self.selectedCoord = None
        self.showHints = False
        self.showNumbering = False
        self.doSmooth = False
        self.highlightParams = (None, None, None, None)
        self.changedHighlight = True
        
        self.controller  = None
        
        # Use a Cairo scene by default - it will be replaced by an OpenGL one if that is the requested view
        # I wanted to remove this but then scene is None and there are a number of issues...
        # This should be cleaned up
        self.scene = SceneCairo(self)
        config.watch('board_view', self.__onBoardViewChanged)
        config.watch('piece_style', self.__onPiecesStyleChanged)

        # Look for game events to update the scene
        movePlayer = MovePlayer(self)
        game.addSpectator(movePlayer)

    def setSelectedSquare(self, coord):
        if self.selectedCoord == coord:
            return
        self.selectedCoord = coord
        self._redrawHighlight()

    def _redrawHighlight(self):
        self.changedHighlight = True
        if self.controller is not None:
            self.controller.render()

    def __onBoardViewChanged(self, key, value):
        self.updateRotation()

    def __onPiecesStyleChanged(self, key, value):
        self.updatePiecesStyle()

    def _updateHighlight(self, coord):
        if self.moveNumber == -1:
            player = self.game.getCurrentPlayer()
            if player is self.game.getWhite():
                colour = chess.board.WHITE
            else:
                colour = chess.board.BLACK
        else:
            if self.moveNumber % 2 == 0:
                colour = chess.board.WHITE
            else:
                colour = chess.board.BLACK

        # Don't update if nothing has changed
        params = (colour, self.moveNumber, self.selectedCoord, self.showHints)
        if self.highlightParams == params:
            return
        self.highlightParams = params

        highlights = {}
        if self.showHints:
            for file in '12345678':
                for rank in 'abcdefgh':
                    c = rank + file
                    highlight = None
                    if self.game.board.squareUnderAttack(colour, c, moveNumber = self.moveNumber):
                        highlight = scene.HIGHLIGHT_THREATENED
                    elif coord is not None:
                        move = self.game.board.testMove(colour, coord, c, moveNumber = self.moveNumber)
                        if move is not None:
                            if self.game.board.getPiece(c, self.moveNumber):
                                highlight = scene.HIGHLIGHT_CAN_TAKE
                            else:
                                highlight = scene.HIGHLIGHT_CAN_MOVE

                    if highlight is not None:
                        highlights[c] = highlight
        if coord is not None:
            highlights[coord] = scene.HIGHLIGHT_SELECTED

        self.scene.controller.setBoardHighlight(highlights)

    def updateRotation(self, animate = True):
        """
        """
        # Get the angle to face
        p = self.game.getCurrentPlayer()
        if p is self.game.getWhite():
            rotation = 0.0
        elif p is self.game.getBlack():
            rotation = 180.0
        
        # Decide if we should face this angle
        boardView = config.get('board_view')
        if boardView == 'white' or boardView == 'facetoface':
            rotation = 0.0
        elif boardView == 'black':
            rotation = 180.0
        elif boardView == 'human':
            if not isinstance(p, player.HumanPlayer):
                return

        self.scene.controller.setBoardRotation(rotation, boardView == 'facetoface', animate)

    def updatePiecesStyle(self):
        piecesStyle = config.get('piece_style')
        self.scene.controller.setPiecesStyle(piecesStyle)
    
    def _pieceMoved(self, piece):
        """
        """
        self._redrawHighlight()
        
        # If waiting for this piece then end players turn
        if piece is not None and piece is self.scene.waitingPiece:
            self.scene.waitingPiece = None
            self.game.getCurrentPlayer().endMove()

    def showMoveHints(self, showHints):
        """Called by ui.ViewFeedback"""
        self.showHints = showHints
        self._redrawHighlight()

    def showBoardNumbering(self, showNumbering):
        """Called by ui.ViewFeedback"""
        self.showNumbering = showNumbering
        self.scene.controller.showBoardNumbering(showNumbering)
        
    def showSmooth(self, doSmooth):
        """Called by ui.ViewFeedback"""
        self.doSmooth = doSmooth
        self.scene.controller.showSmooth(doSmooth)

    def updateScene(self, sceneClass):
        """
        """
        if self.changedHighlight:
            self._updateHighlight(self.selectedCoord)
        self.changedHighlight = False
        
        if isinstance(self.scene, sceneClass):
            return
        self._pieceMoved(None)
        self.scene = sceneClass(self)
        self.reshape(self.width, self.height)
        self.setMoveNumber(self.moveNumber)
        self.showBoardNumbering(self.showNumbering)
        self.showSmooth(self.doSmooth)
        self.updateRotation(animate = False)

    def renderGL(self, background_colour = (0, 0, 0)):
        """Called by ui.ViewFeedback"""
        self.updateScene(SceneOpenGL)
        self.scene.controller.render(background_colour)

    def renderCairoStatic(self, context, background_color = (0, 0, 0)):
        """Called by ui.ViewFeedback"""
        self.updateScene(SceneCairo)
        return self.scene.controller.renderStatic(context, background_color)
        
    def renderCairoDynamic(self, context):
        """Called by ui.ViewFeedback"""
        self.updateScene(SceneCairo)
        self.scene.controller.renderDynamic(context)

    def reshape(self, width, height):
        """Called by ui.ViewFeedback"""
        self.width = width
        self.height = height
        self.scene.controller.reshape(width, height)
    
    def select(self, x, y):
        """Called by ui.ViewFeedback"""
        self.scene.select(x, y)
    
    def deselect(self, x, y):
        """Called by ui.ViewFeedback"""
        self.scene.deselect(x, y)
    
    def setMoveNumber(self, moveNumber):
        """Called by ui.ViewFeedback"""
        self.moveNumber = moveNumber
        
        # Lock the scene if not tracking the game
        self.scene.enableHumanInput(moveNumber == -1)
        
        # Get the state of this scene
        piecesByLocation = self.game.getAlivePieces(moveNumber)
        
        # Remove any models not present
        requiredPieces = piecesByLocation.values()
        for piece in self.scene.getPieces():
            try:
                requiredPieces.index(piece.piece)
            except ValueError:
                piece.model.move(piece.location, True)
        
        # Move the models in the scene
        for (location, piece) in piecesByLocation.iteritems():
            self.scene.movePiece(piece, location, False, True)

        # Can't wait for animation if not looking at the latest move
        if moveNumber != -1:
            self._pieceMoved(piece)

        self._redrawHighlight()

    def save(self, fileName = None):
        """Called by ui.ViewFeedback"""
        # If filename supplied take out of the history
        if fileName is not None:
            if self.game.inHistory:
                self.game.inHistory = False
                if self.game.fileName is not None:
                    self.game.application.history.rename(self.game.fileName, fileName)
            self.game.fileName = fileName
        return self.game.save()

    def getFileName(self):
        """Called by ui.ViewFeedback"""
        # If in the history then prompt for a new name
        if self.game.inHistory:
            return None
        return self.game.fileName
    
    def undo(self):
        """Called by ui.ViewFeedback"""
        p = self.game.getHumanPlayer()
        if p is not None:
            p.undo()
        
    def resign(self):
        """Called by ui.ViewFeedback"""
        p = self.game.getHumanPlayer()
        if p is None:
            # If no human players then abandon game
            self.game.abandon()
        else:
            p.resign()
            
    def claimDraw(self):
        """Called by ui.ViewFeedback"""
        # TODO: Have the UI ask if the player wants to make a move first or claim now (or abort)
        p = self.game.getHumanPlayer()
        if p is None:
            return False
        return p.claimDraw()
