# -*- coding: utf-8 -*-

import collections
import re
import math
import plugin_super_class

from  qtpy.QtCore import *
from  qtpy.QtWidgets import *
from  qtpy.QtGui import *
from  qtpy.QtSvg import *


START_FEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"


def opposite_color(color):
    """:return: The opposite color.

    :param color:
        "w", "white, "b" or "black".
    """
    if color == "w":
        return "b"
    elif color == "white":
        return "black"
    elif color == "b":
        return "w"
    elif color == "black":
        return "white"
    else:
        raise ValueError("Expected w, b, white or black, got: %s." % color)


class Piece(object):

    __cache = dict()

    def __init__(self, symbol):
        self.__symbol = symbol

        self.__color = "w" if symbol != symbol.lower() else "b"
        self.__full_color = "white" if self.__color == "w" else "black"

        self.__type = symbol.lower()
        if self.__type == "p":
            self.__full_type = "pawn"
        elif self.__type == "n":
            self.__full_type = "knight"
        elif self.__type == "b":
            self.__full_type = "bishop"
        elif self.__type == "r":
            self.__full_type = "rook"
        elif self.__type == "q":
            self.__full_type = "queen"
        elif self.__type == "k":
            self.__full_type = "king"
        else:
            raise ValueError("Expected valid piece symbol, got: %s." % symbol)

        self.__hash = ord(self.__symbol)

    @classmethod
    def from_color_and_type(cls, color, type):
        """Creates a piece object from color and type.
        """
        if type == "p" or type == "pawn":
            symbol = "p"
        elif type == "n" or type == "knight":
            symbol = "n"
        elif type == "b" or type == "bishop":
            symbol = "b"
        elif type == "r" or type == "rook":
            symbol = "r"
        elif type == "q" or type == "queen":
            symbol = "q"
        elif type == "k" or type == "king":
            symbol = "k"
        else:
            raise ValueError("Expected piece type, got: %s." % type)

        if color == "w" or color == "white":
            return cls(symbol.upper())
        elif color == "b" or color == "black":
            return cls(symbol)
        else:
            raise ValueError("Expected w, b, white or black, got: %s." % color)

    @property
    def symbol(self):
        return self.__symbol

    @property
    def color(self):
        """The color of the piece as `"b"` or `"w"`."""
        return self.__color

    @property
    def full_color(self):
        """The full color of the piece as `"black"` or `"white`."""
        return self.__full_color

    @property
    def type(self):
        """The type of the piece as `"p"`, `"b"`, `"n"`, `"r"`, `"k"`,
        or `"q"` for pawn, bishop, knight, rook, king or queen.
        """
        return self.__type

    @property
    def full_type(self):
        """The full type of the piece as `"pawn"`, `"bishop"`,
        `"knight"`, `"rook"`, `"king"` or `"queen"`.
        """
        return self.__full_type

    def __str__(self):
        return self.__symbol

    def __repr__(self):
        return "Piece('%s')" % self.__symbol

    def __eq__(self, other):
        return isinstance(other, Piece) and self.__symbol == other.symbol

    def __ne__(self, other):
        return not self.__eq__(other)

    def __hash__(self):
        return self.__hash


class Square(object):
    """Represents a square on the chess board.

    :param name: The name of the square in algebraic notation.

    Square objects that represent the same square compare as equal.
    """

    __cache = dict()

    def __init__(self, name):
        if not len(name) == 2:
            raise ValueError("Expected square name, got: %s." % repr(name))
        self.__name = name

        if not name[0] in ["a", "b", "c", "d", "e", "f", "g", "h"]:
            raise ValueError("Expected file, got: %s." % repr(name[0]))
        self.__file = name[0]
        self.__x = ord(self.__name[0]) - ord("a")

        if not name[1] in ["1", "2", "3", "4", "5", "6", "7", "8"]:
            raise ValueError("Expected rank, got: %s." % repr(name[1]))
        self.__rank = int(name[1])
        self.__y = ord(self.__name[1]) - ord("1")

        self.__x88 = self.__x + 16 * (7 - self.__y)

    @classmethod
    def from_x88(cls, x88):
        """Creates a square object from an `x88 <http://en.wikipedia.org/wiki/Board_representation_(chess)#0x88_method>`_
        index.

        :param x88:
            The x88 index as integer between 0 and 128.
        """
        if x88 < 0 or x88 > 128:
            raise ValueError("x88 index is out of range: %s." % repr(x88))

        if x88 & 0x88:
            raise ValueError("x88 is not on the board: %s." % repr(x88))

        return cls("abcdefgh"[x88 & 7] + "87654321"[x88 >> 4])

    @classmethod
    def from_rank_and_file(cls, rank, file):
        """Creates a square object from rank and file.

        :param rank:
            An integer between 1 and 8.
        :param file:
            The rank as a letter between `"a"` and `"h"`.
        """
        if rank < 1 or rank > 8:
            raise ValueError("Expected rank to be between 1 and 8: %s." % repr(rank))

        if not file in ["a", "b", "c", "d", "e", "f", "g", "h"]:
            raise ValueError("Expected the file to be a letter between 'a' and 'h': %s." % repr(file))

        return cls(file + str(rank))

    @classmethod
    def from_x_and_y(cls, x, y):
        """Creates a square object from x and y coordinates.

        :param x:
            An integer between 0 and 7 where 0 is the a-file.
        :param y:
            An integer between 0 and 7 where 0 is the first rank.
        """
        return cls("abcdefgh"[x] + "12345678"[y])

    @property
    def name(self):
        """The algebraic name of the square."""
        return self.__name

    @property
    def file(self):
        """The file as a letter between `"a"` and `"h"`."""
        return self.__file

    @property
    def x(self):
        """The x-coordinate, starting with 0 for the a-file."""
        return self.__x

    @property
    def rank(self):
        """The rank as an integer between 1 and 8."""
        return self.__rank

    @property
    def y(self):
        """The y-coordinate, starting with 0 for the first rank."""
        return self.__y

    @property
    def x88(self):
        """The `x88 <http://en.wikipedia.org/wiki/Board_representation_(chess)#0x88_method>`_
        index of the square."""
        return self.__x88

    def is_dark(self):
        """:return: Whether it is a dark square."""
        return (self.__x - self.__y % 2) == 0

    def is_light(self):
        """:return: Whether it is a light square."""
        return not self.is_dark()

    def is_backrank(self):
        """:return: Whether the square is on either sides backrank."""
        return self.__y == 0 or self.__y == 7

    def __str__(self):
        return self.__name

    def __repr__(self):
        return "Square('%s')" % self.__name

    def __eq__(self, other):
        return isinstance(other, Square) and self.__name == other.name

    def __ne__(self, other):
        return not self.__eq__(other)

    def __hash__(self):
        return self.__x88


class Move(object):
    """Represents a move.
    """

    __uci_move_regex = re.compile(r"^([a-h][1-8])([a-h][1-8])([rnbq]?)$")

    def __init__(self, source, target, promotion=None):
        if not isinstance(source, Square):
            raise TypeError("Expected source to be a Square.")
        self.__source = source

        if not isinstance(target, Square):
            raise TypeError("Expected target to be a Square.")
        self.__target = target

        if not promotion:
            self.__promotion = None
            self.__full_promotion = None
        else:
            promotion = promotion.lower()
            if promotion == "n" or promotion == "knight":
                self.__promotion = "n"
                self.__full_promotion = "knight"
            elif promotion == "b" or promotion == "bishop":
                self.__promotion = "b"
                self.__full_promotion = "bishop"
            elif promotion == "r" or promotion == "rook":
                self.__promotion = "r"
                self.__full_promotion = "rook"
            elif promotion == "q" or promotion == "queen":
                self.__promotion = "q"
                self.__full_promotion = "queen"
            else:
                raise ValueError("Expected promotion type, got: %s." % repr(promotion))

    @classmethod
    def from_uci(cls, uci):
        """The UCI move string like `"a1a2"` or `"b7b8q"`."""
        if uci == "0000":
            return cls.get_null()

        match = cls.__uci_move_regex.match(uci)

        return cls(
            source=Square(match.group(1)),
            target=Square(match.group(2)),
            promotion=match.group(3) or None)

    @classmethod
    def get_null(cls):
        """:return: A null move."""
        return cls(Square("a1"), Square("a1"))

    @property
    def source(self):
        """The source square."""
        return self.__source

    @property
    def target(self):
        """The target square."""
        return self.__target

    @property
    def promotion(self):
        """The promotion type as `None`, `"r"`, `"n"`, `"b"` or `"q"`."""
        return self.__promotion

    @property
    def full_promotion(self):
        """Like `promotion`, but with full piece type names."""
        return self.__full_promotion

    @property
    def uci(self):
        """The UCI move string like `"a1a2"` or `"b7b8q"`."""
        if self.is_null():
            return "0000"
        else:
            if self.__promotion:
                return self.__source.name + self.__target.name + self.__promotion
            else:
                return self.__source.name + self.__target.name

    def is_null(self):
        """:return: Whether the move is a null move."""
        return self.__source == self.__target

    def __nonzero__(self):
        return not self.is_null()

    def __str__(self):
        return self.uci

    def __repr__(self):
        return "Move.from_uci(%s)" % repr(self.uci)

    def __eq__(self, other):
        return isinstance(other, Move) and self.uci == other.uci

    def __ne__(self, other):
        return not self.__eq__(other)

    def __hash__(self):
        return hash(self.uci)


MoveInfo = collections.namedtuple("MoveInfo", [
    "move",
    "piece",
    "captured",
    "san",
    "is_enpassant",
    "is_king_side_castle",
    "is_queen_side_castle",
    "is_castle",
    "is_check",
    "is_checkmate"])


class Position(object):
    """Represents a chess position.

    :param fen:
        Optional. The FEN of the position. Defaults to the standard
        chess start position.
    """

    __san_regex = re.compile('^([NBKRQ])?([a-h])?([1-8])?x?([a-h][1-8])(=[NBRQ])?(\+|#)?$')

    def __init__(self, fen=START_FEN):
        self.__castling = "KQkq"
        self.fen = fen

    def copy(self):
        """Gets a copy of the position. The copy will not change when the
        original instance is changed.

        :return:
            An exact copy of the positon.
        """
        return Position(self.fen)

    def __get_square_index(self, square_or_int):
        if type(square_or_int) is int:
            # Validate the index by passing it through the constructor.
            return Square.from_x88(square_or_int).x88
        elif isinstance(square_or_int, str):
            return Square(square_or_int).x88
        elif type(square_or_int) is Square:
            return square_or_int.x88
        else:
            raise TypeError(
                "Expected integer or Square, got: %s." % repr(square_or_int))

    def __getitem__(self, key):
        return self.__board[self.__get_square_index(key)]

    def __setitem__(self, key, value):
        if value is None or type(value) is Piece:
            self.__board[self.__get_square_index(key)] = value
        else:
            raise TypeError("Expected Piece or None, got: %s." % repr(value))

    def __delitem__(self, key):
        self.__board[self.__get_square_index(key)] = None

    def clear_board(self):
        """Removes all pieces from the board."""
        self.__board = [None] * 128

    def reset(self):
        """Resets to the standard chess start position."""
        self.set_fen(START_FEN)

    def __get_disambiguator(self, move):
        same_rank = False
        same_file = False
        piece = self[move.source]

        for m in self.get_legal_moves():
            ambig_piece = self[m.source]
            if (piece == ambig_piece and move.source != m.source and
                move.target == m.target):
                if move.source.rank == m.source.rank:
                    same_rank = True

                if move.source.file == m.source.file:
                    same_file = True

                if same_rank and same_file:
                    break

        if same_rank and same_file:
            return move.source.name
        elif same_file:
            return str(move.source.rank)
        elif same_rank:
            return move.source.file
        else:
            return ""

    def get_move_from_san(self, san):
        """Gets a move from standard algebraic notation.

        :param san:
            A move string in standard algebraic notation.

        :return:
            A Move object.

        :raise Exception:
            If not exactly one legal move matches.
        """
        # Castling moves.
        if san == "O-O" or san == "O-O-O":
            rank = 1 if self.turn == "w" else 8
            if san == "O-O":
                return Move(
                    source=Square.from_rank_and_file(rank, 'e'),
                    target=Square.from_rank_and_file(rank, 'g'))
            else:
                return Move(
                    source=Square.from_rank_and_file(rank, 'e'),
                    target=Square.from_rank_and_file(rank, 'c'))
        # Regular moves.
        else:
            matches = Position.__san_regex.match(san)
            if not matches:
                raise ValueError("Invalid SAN: %s." % repr(san))

            piece = Piece.from_color_and_type(
                color=self.turn,
                type=matches.group(1).lower() if matches.group(1) else 'p')
            target = Square(matches.group(4))

            source = None
            for m in self.get_legal_moves():
                if self[m.source] != piece or m.target != target:
                    continue

                if matches.group(2) and matches.group(2) != m.source.file:
                    continue
                if matches.group(3) and matches.group(3) != str(m.source.rank):
                    continue

                # Move matches. Assert it is not ambiguous.
                if source:
                    raise Exception(
                        "Move is ambiguous: %s matches %s and %s."
                            % san, source, m)
                source = m.source

            if not source:
                raise Exception("No legal move matches %s." % san)

            return Move(source, target, matches.group(5) or None)

    def get_move_info(self, move):
        """Gets information about a move.

        :param move:
            The move to get information about.

        :return:
            A named tuple with these properties:

            `move`:
                The move object.
            `piece`:
                The piece that has been moved.
            `san`:
                The standard algebraic notation of the move.
            `captured`:
                The piece that has been captured or `None`.
            `is_enpassant`:
                A boolean indicating if the move is an en-passant
                capture.
            `is_king_side_castle`:
                Whether it is a king-side castling move.
            `is_queen_side_castle`:
                Whether it is a queen-side castling move.
            `is_castle`:
                Whether it is a castling move.
            `is_check`:
                Whether the move gives check.
            `is_checkmate`:
                Whether the move gives checkmate.

        :raise Exception:
            If the move is not legal in the position.
        """
        resulting_position = self.copy().make_move(move)

        capture = self[move.target]
        piece = self[move.source]

        # Pawn moves.
        enpassant = False
        if piece.type == "p":
            # En-passant.
            if move.target.file != move.source.file and not capture:
                enpassant = True
                capture = Piece.from_color_and_type(
                    color=resulting_position.turn, type='p')

        # Castling.
        if piece.type == "k":
            is_king_side_castle = move.target.x - move.source.x == 2
            is_queen_side_castle = move.target.x - move.source.x == -2
        else:
            is_king_side_castle = is_queen_side_castle = False

        # Checks.
        is_check = resulting_position.is_check()
        is_checkmate = resulting_position.is_checkmate()

        # Generate the SAN.
        san = ""
        if is_king_side_castle:
            san += "o-o"
        elif is_queen_side_castle:
            san += "o-o-o"
        else:
            if piece.type != 'p':
                san += piece.type.upper()

            san += self.__get_disambiguator(move)

            if capture:
                if piece.type == 'p':
                    san += move.source.file
                san += "x"
            san += move.target.name

            if move.promotion:
                san += "="
                san += move.promotion.upper()

        if is_checkmate:
            san += "#"
        elif is_check:
            san += "+"

        if enpassant:
            san += " (e.p.)"

        # Return the named tuple.
        return MoveInfo(
            move=move,
            piece=piece,
            captured=capture,
            san=san,
            is_enpassant=enpassant,
            is_king_side_castle=is_king_side_castle,
            is_queen_side_castle=is_queen_side_castle,
            is_castle=is_king_side_castle or is_queen_side_castle,
            is_check=is_check,
            is_checkmate=is_checkmate)

    def make_move(self, move, validate=True):
        """Makes a move.

        :param move:
            The move to make.
        :param validate:
            Defaults to `True`. Whether the move should be validated.

        :return:
            Making a move changes the position object. The same
            (changed) object is returned for chainability.

        :raise Exception:
            If the validate parameter is `True` and the move is not
            legal in the position.
        """
        if validate and move not in self.get_legal_moves():
            raise Exception(
                "%s is not a legal move in the position %s." % (move, self.fen))
        piece = self[move.source]
        capture = self[move.target]

        # Move the piece.
        self[move.target] = self[move.source]
        del self[move.source]

        # It is the next players turn.
        self.toggle_turn()

        # Pawn moves.
        self.ep_file = None
        if piece.type == "p":
            # En-passant.
            if move.target.file != move.source.file and not capture:
                if self.turn == "w":
                    self[move.target.x88 - 16] = None
                else:
                    self[move.target.x88 + 16] = None
                capture = True
            # If big pawn move, set the en-passant file.
            if abs(move.target.rank - move.source.rank) == 2:
                if self.get_theoretical_ep_right(move.target.file):
                    self.ep_file = move.target.file

        # Promotion.
        if move.promotion:
            self[move.target] = Piece.from_color_and_type(
                color=piece.color, type=move.promotion)

        # Potential castling.
        if piece.type == "k":
            steps = move.target.x - move.source.x
            if abs(steps) == 2:
                # Queen-side castling.
                if steps == -2:
                    rook_target = move.target.x88 + 1
                    rook_source = move.target.x88 - 2
                # King-side castling.
                else:
                    rook_target = move.target.x88 - 1
                    rook_source = move.target.x88 + 1
                self[rook_target] = self[rook_source]
                del self[rook_source]

        # Increment the half move counter.
        if piece.type == "p" or capture:
            self.half_moves = 0
        else:
            self.half_moves += 1

        # Increment the move number.
        if self.turn == "w":
            self.ply += 1

        # Update castling rights.
        for type in ["K", "Q", "k", "q"]:
            if not self.get_theoretical_castling_right(type):
                self.set_castling_right(type, False)

        return self

    @property
    def turn(self):
        """Whos turn it is as `"w"` or `"b"`."""
        return self.__turn

    @turn.setter
    def turn(self, value):
        if value not in ["w", "b"]:
            raise ValueError(
                "Expected 'w' or 'b' for turn, got: %s." % repr(value))
        self.__turn = value

    def toggle_turn(self):
        """Toggles whos turn it is."""
        self.turn = opposite_color(self.turn)

    def get_castling_right(self, type):
        """Checks the castling rights.

        :param type:
            The castling move to check. "K" for king-side castling of
            the white player, "Q" for queen-side castling of the white
            player. "k" and "q" for the corresponding castling moves of
            the black player.

        :return:
            A boolean indicating whether the player has that castling
            right.
        """
        if not type in ["K", "Q", "k", "q"]:
            raise KeyError(
                "Expected 'K', 'Q', 'k' or 'q' as a castling type, got: %s." % repr(type))
        return type in self.__castling

    def get_theoretical_castling_right(self, type):
        """Checks if a player could have a castling right in theory from
        looking just at the piece positions.

        :param type:
            The castling move to check. See
            `Position.get_castling_right(type)` for values.

        :return:
            A boolean indicating whether the player could theoretically
            have that castling right.
        """
        if not type in ["K", "Q", "k", "q"]:
            raise KeyError(
                "Expected 'K', 'Q', 'k' or 'q' as a castling type, got: %s."
                    % repr(type))
        if type == "K" or type == "Q":
            if self["e1"] != Piece("K"):
                return False
            if type == "K":
                return self["h1"] == Piece("R")
            elif type == "Q":
                return self["a1"] == Piece("R")
        elif type == "k" or type == "q":
            if self["e8"] != Piece("k"):
                return False
            if type == "k":
                return self["h8"] == Piece("r")
            elif type == "q":
                return self["a8"] == Piece("r")

    def get_theoretical_ep_right(self, file):
        """Checks if a player could have an ep-move in theory from
        looking just at the piece positions.

        :param file:
            The file to check as a letter between `"a"` and `"h"`.

        :return:
            A boolean indicating whether the player could theoretically
            have that en-passant move.
        """
        if not file in ["a", "b", "c", "d", "e", "f", "g", "h"]:
            raise KeyError(
                "Expected a letter between 'a' and 'h' for the file, got: %s."
                    % repr(file))

        # Check there is a pawn.
        pawn_square = Square.from_rank_and_file(
            rank=4 if self.turn == "b" else 5, file=file)
        opposite_color_pawn = Piece.from_color_and_type(
            color=opposite_color(self.turn), type="p")
        if self[pawn_square] != opposite_color_pawn:
            return False

        # Check the square below is empty.
        square_below = Square.from_rank_and_file(
            rank=3 if self.turn == "b" else 6, file=file)
        if self[square_below]:
            return False

        # Check there is a pawn of the other color on a neighbor file.
        f = ord(file) - ord("a")
        p = Piece("p")
        P = Piece("P")
        if self.turn == "b":
            if f > 0 and self[Square.from_x_and_y(f - 1, 3)] == p:
                return True
            elif f < 7 and self[Square.from_x_and_y(f + 1, 3)] == p:
                return True
        else:
            if f > 0 and self[Square.from_x_and_y(f - 1, 4)] == P:
                return True
            elif f < 7 and self[Square.from_x_and_y(f + 1, 4)] == P:
                return True
        return False

    def set_castling_right(self, type, status):
        """Sets a castling right.

        :param type:
            `"K"`, `"Q"`, `"k"`, or `"q"` as used by
            `Position.get_castling_right(type)`.
        :param status:
            A boolean indicating whether that castling right should be
            granted or denied.
        """
        if not type in ["K", "Q", "k", "q"]:
            raise KeyError(
                "Expected 'K', 'Q', 'k' or 'q' as a castling type, got: %s."
                    % repr(type))

        castling = ""
        for t in ["K", "Q", "k", "q"]:
            if type == t:
                if status:
                    castling += t
            elif self.get_castling_right(t):
                castling += t
        self.__castling = castling

    @property
    def ep_file(self):
        """The en-passant file as a lowercase letter between `"a"` and
        `"h"` or `None`."""
        return self.__ep_file

    @ep_file.setter
    def ep_file(self, value):
        if not value in ["a", "b", "c", "d", "e", "f", "g", "h", None]:
            raise ValueError(
                "Expected None or a letter between 'a' and 'h' for the "
                "en-passant file, got: %s." % repr(value))

        self.__ep_file = value

    @property
    def half_moves(self):
        """The number of half-moves since the last capture or pawn move."""
        return self.__half_moves

    @half_moves.setter
    def half_moves(self, value):
        if type(value) is not int:
            raise TypeError(
                "Expected integer for half move count, got: %s." % repr(value))
        if value < 0:
            raise ValueError("Half move count must be >= 0.")

        self.__half_moves = value

    @property
    def ply(self):
        """The number of this move. The game starts at 1 and the counter
        is incremented every time white moves.
        """
        return self.__ply

    @ply.setter
    def ply(self, value):
        if type(value) is not int:
            raise TypeError(
                "Expected integer for ply count, got: %s." % repr(value))
        if value < 1:
            raise ValueError("Ply count must be >= 1.")
        self.__ply = value

    def get_piece_counts(self, color = "wb"):
        """Counts the pieces on the board.

        :param color:
            Defaults to `"wb"`. A color to filter the pieces by. Valid
            values are "w", "b", "wb" and "bw".

        :return:
            A dictionary of piece counts, keyed by lowercase piece type
            letters.
        """
        if not color in ["w", "b", "wb", "bw"]:
            raise KeyError(
                "Expected color filter to be one of 'w', 'b', 'wb', 'bw', "
                "got: %s." % repr(color))

        counts = {
            "p": 0,
            "b": 0,
            "n": 0,
            "r": 0,
            "k": 0,
            "q": 0,
        }
        for piece in self.__board:
            if piece and piece.color in color:
                counts[piece.type] += 1
        return counts

    def get_king(self, color):
        """Gets the square of the king.

        :param color:
            `"w"` for the white players king. `"b"` for the black
            players king.

        :return:
            The first square with a matching king or `None` if that
            player has no king.
        """
        if not color in ["w", "b"]:
            raise KeyError("Invalid color: %s." % repr(color))

        for x88, piece in enumerate(self.__board):
            if piece and piece.color == color and piece.type == "k":
                return Square.from_x88(x88)

    @property
    def fen(self):
        """The FEN string representing the position."""
        # Board setup.
        empty = 0
        fen = ""
        for y in range(7, -1, -1):
            for x in range(0, 8):
                square = Square.from_x_and_y(x, y)

                # Add pieces.
                if not self[square]:
                    empty += 1
                else:
                    if empty > 0:
                        fen += str(empty)
                        empty = 0
                    fen += self[square].symbol

            # Boarder of the board.
            if empty > 0:
                fen += str(empty)
            if not (x == 7 and y == 0):
                fen += "/"
            empty = 0

        if self.ep_file and self.get_theoretical_ep_right(self.ep_file):
            ep_square = self.ep_file + ("3" if self.turn == "b" else "6")
        else:
            ep_square = "-"

        # Join the parts together.
        return " ".join([
            fen,
            self.turn,
            self.__castling if self.__castling else "-",
            ep_square,
            str(self.half_moves),
            str(self.__ply)])

    @fen.setter
    def fen(self, fen):
        # Split into 6 parts.
        tokens = fen.split()
        if len(tokens) != 6:
            raise Exception("A FEN does not consist of 6 parts.")

        # Check that the position part is valid.
        rows = tokens[0].split("/")
        assert len(rows) == 8
        for row in rows:
            field_sum = 0
            previous_was_number = False
            for char in row:
                if char in "12345678":
                    if previous_was_number:
                        raise Exception(
                            "Position part of the FEN is invalid: "
                            "Multiple numbers immediately after each other.")
                    field_sum += int(char)
                    previous_was_number = True
                elif char in "pnbrkqPNBRKQ":
                    field_sum += 1
                    previous_was_number = False
                else:
                    raise Exception(
                        "Position part of the FEN is invalid: "
                        "Invalid character in the position part of the FEN.")

            if field_sum != 8:
                Exception(
                    "Position part of the FEN is invalid: "
                    "Row with invalid length.")

        # Check that the other parts are valid.
        if not tokens[1] in ["w", "b"]:
            raise Exception(
                "Turn part of the FEN is invalid: Expected b or w.")
        if not re.compile(r"^(KQ?k?q?|Qk?q?|kq?|q|-)$").match(tokens[2]):
            raise Exception("Castling part of the FEN is invalid.")
        if not re.compile(r"^(-|[a-h][36])$").match(tokens[3]):
            raise Exception("En-passant part of the FEN is invalid.")
        if not re.compile(r"^(0|[1-9][0-9]*)$").match(tokens[4]):
            raise Exception("Half move part of the FEN is invalid.")
        if not re.compile(r"^[1-9][0-9]*$").match(tokens[5]):
            raise Exception("Ply part of the FEN is invalid.")

        # Set pieces on the board.
        self.__board = [None] * 128
        i = 0
        for symbol in tokens[0]:
            if symbol == "/":
                i += 8
            elif symbol in "12345678":
                i += int(symbol)
            else:
                self.__board[i] = Piece(symbol)
                i += 1

        # Set the turn.
        self.__turn = tokens[1]

        # Set the castling rights.
        for type in ["K", "Q", "k", "q"]:
            self.set_castling_right(type, type in tokens[2])

        # Set the en-passant file.
        if tokens[3] == "-":
            self.__ep_file = None
        else:
            self.__ep_file = tokens[3][0]

        # Set the move counters.
        self.__half_moves = int(tokens[4])
        self.__ply = int(tokens[5])

    def is_king_attacked(self, color):
        """:return: Whether the king of the given color is attacked.

        :param color: `"w"` or `"b"`.
        """
        square = self.get_king(color)
        if square:
            return self.is_attacked(opposite_color(color), square)
        else:
            return False

    def get_pseudo_legal_moves(self):
        """:yield: Pseudo legal moves in the current position."""
        PAWN_OFFSETS = {
            "b": [16, 32, 17, 15],
            "w": [-16, -32, -17, -15]
        }

        PIECE_OFFSETS = {
            "n": [-18, -33, -31, -14, 18, 33, 31, 14],
            "b": [-17, -15, 17, 15],
            "r": [-16, 1, 16, -1],
            "q": [-17, -16, -15, 1, 17, 16, 15, -1],
            "k": [-17, -16, -15, 1, 17, 16, 15, -1]
        }

        for x88, piece in enumerate(self.__board):
            # Skip pieces of the opponent.
            if not piece or piece.color != self.turn:
                continue

            square = Square.from_x88(x88)

            # Pawn moves.
            if piece.type == "p":
                # Single square ahead. Do not capture.
                target = Square.from_x88(x88 + PAWN_OFFSETS[self.turn][0])
                if not self[target]:
                    # Promotion.
                    if target.is_backrank():
                        for promote_to in "bnrq":
                            yield Move(square, target, promote_to)
                    else:
                        yield Move(square, target)

                    # Two squares ahead. Do not capture.
                    if (self.turn == "w" and square.rank == 2) or (self.turn == "b" and square.rank == 7):
                        target = Square.from_x88(square.x88 + PAWN_OFFSETS[self.turn][1])
                        if not self[target]:
                            yield Move(square, target)

                # Pawn captures.
                for j in [2, 3]:
                   target_index = square.x88 + PAWN_OFFSETS[self.turn][j]
                   if target_index & 0x88:
                       continue
                   target = Square.from_x88(target_index)
                   if self[target] and self[target].color != self.turn:
                       # Promotion.
                       if target.is_backrank():
                           for promote_to in "bnrq":
                               yield Move(square, target, promote_to)
                       else:
                           yield Move(square, target)
                   # En-passant.
                   elif not self[target] and target.file == self.ep_file:
                       yield Move(square, target)
            # Other pieces.
            else:
                for offset in PIECE_OFFSETS[piece.type]:
                    target_index = square.x88
                    while True:
                        target_index += offset
                        if target_index & 0x88:
                            break
                        target = Square.from_x88(target_index)
                        if not self[target]:
                            yield Move(square, target)
                        else:
                            if self[target].color == self.turn:
                                break
                            yield Move(square, target)
                            break

                        # Knight and king do not go multiple times in their
                        # direction.
                        if piece.type in ["n", "k"]:
                            break

        opponent = opposite_color(self.turn)

        # King-side castling.
        k = "k" if self.turn == "b" else "K"
        if self.get_castling_right(k):
            of = self.get_king(self.turn).x88
            to = of + 2
            if not self[of + 1] and not self[to] and not self.is_check() and not self.is_attacked(opponent, Square.from_x88(of + 1)) and not self.is_attacked(opponent, Square.from_x88(to)):
                yield Move(Square.from_x88(of), Square.from_x88(to))

        # Queen-side castling
        q = "q" if self.turn == "b" else "Q"
        if self.get_castling_right(q):
            of = self.get_king(self.turn).x88
            to = of - 2

            if not self[of - 1] and not self[of - 2] and not self[of - 3] and not self.is_check() and not self.is_attacked(opponent, Square.from_x88(of - 1)) and not self.is_attacked(opponent, Square.from_x88(to)):
                yield Move(Square.from_x88(of), Square.from_x88(to))

    def get_legal_moves(self):
        """:yield: All legal moves in the current position."""
        for move in self.get_pseudo_legal_moves():
            potential_position = self.copy()
            potential_position.make_move(move, False)
            if not potential_position.is_king_attacked(self.turn):
                yield move

    def get_attackers(self, color, square):
        """Gets the attackers of a specific square.

        :param color:
            Filter attackers by this piece color.
        :param square:
            The square to check for.

        :yield:
            Source squares of the attack.
        """
        if color not in ["b", "w"]:
            raise KeyError("Invalid color: %s." % repr(color))

        ATTACKS = [
            20, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 20, 0,
            0, 20, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 20, 0, 0,
            0, 0, 20, 0, 0, 0, 0, 24, 0, 0, 0, 0, 20, 0, 0, 0,
            0, 0, 0, 20, 0, 0, 0, 24, 0, 0, 0, 20, 0, 0, 0, 0,
            0, 0, 0, 0, 20, 0, 0, 24, 0, 0, 20, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 20, 2, 24, 2, 20, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 2, 53, 56, 53, 2, 0, 0, 0, 0, 0, 0,
            24, 24, 24, 24, 24, 24, 56, 0, 56, 24, 24, 24, 24, 24, 24, 0,
            0, 0, 0, 0, 0, 2, 53, 56, 53, 2, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 20, 2, 24, 2, 20, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 20, 0, 0, 24, 0, 0, 20, 0, 0, 0, 0, 0,
            0, 0, 0, 20, 0, 0, 0, 24, 0, 0, 0, 20, 0, 0, 0, 0,
            0, 0, 20, 0, 0, 0, 0, 24, 0, 0, 0, 0, 20, 0, 0, 0,
            0, 20, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 20, 0, 0,
            20, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 20
        ]

        RAYS = [
            17, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 15, 0,
            0, 17, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 15, 0, 0,
            0, 0, 17, 0, 0, 0, 0, 16, 0, 0, 0, 0, 15, 0, 0, 0,
            0, 0, 0, 17, 0, 0, 0, 16, 0, 0, 0, 15, 0, 0, 0, 0,
            0, 0, 0, 0, 17, 0, 0, 16, 0, 0, 15, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 17, 0, 16, 0, 15, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 17, 16, 15, 0, 0, 0, 0, 0, 0, 0,
            1, 1, 1, 1, 1, 1, 1, 0, -1, -1, -1, -1, -1, -1, -1, 0,
            0, 0, 0, 0, 0, 0, -15, -16, -17, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, -15, 0, -16, 0, -17, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, -15, 0, 0, -16, 0, 0, -17, 0, 0, 0, 0, 0,
            0, 0, 0, -15, 0, 0, 0, -16, 0, 0, 0, -17, 0, 0, 0, 0,
            0, 0, -15, 0, 0, 0, 0, -16, 0, 0, 0, 0, -17, 0, 0, 0,
            0, -15, 0, 0, 0, 0, 0, -16, 0, 0, 0, 0, 0, -17, 0, 0,
            -15, 0, 0, 0, 0, 0, 0, -16, 0, 0, 0, 0, 0, 0, -17
        ]

        SHIFTS = {
            "p": 0,
            "n": 1,
            "b": 2,
            "r": 3,
            "q": 4,
            "k": 5
        }

        for x88, piece in enumerate(self.__board):
            if not piece or piece.color != color:
                continue
            source = Square.from_x88(x88)

            difference = source.x88 - square.x88
            index = difference + 119

            if ATTACKS[index] & (1 << SHIFTS[piece.type]):
                # Handle pawns.
                if piece.type == "p":
                    if difference > 0:
                        if piece.color == "w":
                            yield source
                    else:
                        if piece.color == "b":
                            yield source
                    continue

                # Handle knights and king.
                if piece.type in ["n", "k"]:
                    yield source

                # Handle the others.
                offset = RAYS[index]
                j = source.x88 + offset
                blocked = False
                while j != square.x88:
                    if self[j]:
                        blocked = True
                        break
                    j += offset
                if not blocked:
                    yield source

    def is_attacked(self, color, square):
        """Checks whether a square is attacked.

        :param color:
            Check if this player is attacking.
        :param square:
            The square the player might be attacking.

        :return:
            A boolean indicating whether the given square is attacked
            by the player of the given color.
        """
        x = list(self.get_attackers(color, square))
        return len(x) > 0

    def is_check(self):
        """:return: Whether the current player is in check."""
        return self.is_king_attacked(self.turn)

    def is_checkmate(self):
        """:return: Whether the current player has been checkmated."""
        if not self.is_check():
            return False
        else:
            arr = list(self.get_legal_moves())
            return len(arr) == 0

    def is_stalemate(self):
        """:return: Whether the current player is in stalemate."""
        if self.is_check():
            return False
        else:
            arr = list(self.get_legal_moves())
            return len(arr) == 0

    def is_insufficient_material(self):
        """Checks if there is sufficient material to mate.

        Mating is impossible in:

        * A king versus king endgame.
        * A king with bishop versus king endgame.
        * A king with knight versus king endgame.
        * A king with bishop versus king with bishop endgame, where both
          bishops are on the same color. Same goes for additional
          bishops on the same color.

        Assumes that the position is valid and each player has exactly
        one king.

        :return:
            Whether there is insufficient material to mate.
        """
        piece_counts = self.get_piece_counts()
        if sum(piece_counts.values()) == 2:
            # King versus king.
            return True
        elif sum(piece_counts.values()) == 3:
            # King and knight or bishop versus king.
            if piece_counts["b"] == 1 or piece_counts["n"] == 1:
                return True
        elif sum(piece_counts.values()) == 2 + piece_counts["b"]:
            # Each player with only king and any number of bishops, where all
            # bishops are on the same color.
            white_has_bishop = self.get_piece_counts("w")["b"] != 0
            black_has_bishop = self.get_piece_counts("b")["b"] != 0
            if white_has_bishop and black_has_bishop:
                color = None
                for x88, piece in enumerate(self.__board):
                    if piece and piece.type == "b":
                        square = Square.from_x88(x88)
                        if color is not None and color != square.is_light():
                            return False
                        color = square.is_light()
                return True
        return False

    def is_game_over(self):
        """Checks if the game is over.

        :return:
            Whether the game is over by the rules of chess,
            disregarding that players can agree on a draw, claim a draw
            or resign.
        """
        return (self.is_checkmate() or self.is_stalemate() or
                self.is_insufficient_material())

    def __str__(self):
        return self.fen

    def __repr__(self):
        return "Position.from_fen(%s)" % repr(self.fen)

    def __eq__(self, other):
        return self.fen == other.fen

    def __ne__(self, other):
        return self.fen != other.fen


class Board(QWidget):

    def __init__(self, parent):
        super(Board, self).__init__()
        self.margin = 0.1
        self.padding = 0.06
        self.showCoordinates = True
        self.lightSquareColor = QColor(255, 255, 255)
        self.darkSquareColor = QColor(100, 100, 255)
        self.borderColor = QColor(100, 100, 200)
        self.shadowWidth = 2
        self.rotation = 0
        self.ply = 1
        self.setWindowTitle('Chess')
        self.backgroundPixmap = QPixmap(plugin_super_class.path_to_data('chess') + "background.png")

        self.draggedSquare = None
        self.dragPosition = None

        self.position = Position()

        self.parent = parent

        # Load piece set.
        self.pieceRenderers = dict()
        for symbol in "PNBRQKpnbrqk":
            piece = Piece(symbol)
            self.pieceRenderers[piece] = QSvgRenderer(plugin_super_class.path_to_data('chess') + "classic-pieces/%s-%s.svg" % (piece.full_color, piece.full_type))

    def update_title(self, my_move=False):
        if self.position.is_checkmate():
            self.setWindowTitle('Checkmate')
        elif self.position.is_stalemate():
            self.setWindowTitle('Stalemate')
        else:
            self.setWindowTitle('Chess' + (' [Your move]' if my_move else ''))

    def mousePressEvent(self, e):
        self.dragPosition = e.pos()
        square = self.squareAt(e.pos())
        if self.canDragSquare(square):
            self.draggedSquare = square

    def mouseMoveEvent(self, e):
        if self.draggedSquare:
            self.dragPosition = e.pos()
            self.repaint()

    def mouseReleaseEvent(self, e):
        if self.draggedSquare:
            dropSquare = self.squareAt(e.pos())
            if dropSquare == self.draggedSquare:
                self.onSquareClicked(self.draggedSquare)
            elif dropSquare:
                move = self.moveFromDragDrop(self.draggedSquare, dropSquare)
                if move:
                    self.position.make_move(move)
                    self.parent.move(move)
                    self.ply += 1
            self.draggedSquare = None
            self.repaint()

    def closeEvent(self, *args):
        self.parent.stop_game()

    def paintEvent(self, event):
        painter = QPainter()
        painter.begin(self)

        # Light shines from upper left.
        if math.cos(math.radians(self.rotation)) >= 0:
            lightBorderColor = self.borderColor.lighter()
            darkBorderColor = self.borderColor.darker()
        else:
            lightBorderColor = self.borderColor.darker()
            darkBorderColor = self.borderColor.lighter()

        # Draw the background.
        backgroundBrush = QBrush(Qt.red, self.backgroundPixmap)
        backgroundBrush.setStyle(Qt.TexturePattern)
        painter.fillRect(QRect(QPoint(0, 0), self.size()), backgroundBrush)

        # Do the rotation.
        painter.save()
        painter.translate(self.width() / 2, self.height() / 2)
        painter.rotate(self.rotation)

        # Draw the border.
        frameSize = min(self.width(), self.height()) * (1 - self.margin * 2)
        borderSize = min(self.width(), self.height()) * self.padding
        painter.translate(-frameSize / 2, -frameSize / 2)
        painter.fillRect(QRect(0, 0, frameSize, frameSize), self.borderColor)
        painter.setPen(QPen(QBrush(lightBorderColor), self.shadowWidth))
        painter.drawLine(0, 0, 0, frameSize)
        painter.drawLine(0, 0, frameSize, 0)
        painter.setPen(QPen(QBrush(darkBorderColor), self.shadowWidth))
        painter.drawLine(frameSize, 0, frameSize, frameSize)
        painter.drawLine(0, frameSize, frameSize, frameSize)

        # Draw the squares.
        painter.translate(borderSize, borderSize)
        squareSize = (frameSize - 2 * borderSize) / 8.0
        for x in range(0, 8):
            for y in range(0, 8):
                rect = QRect(x * squareSize, y * squareSize, squareSize, squareSize)
                if (x - y) % 2 == 0:
                     painter.fillRect(rect, QBrush(self.lightSquareColor))
                else:
                     painter.fillRect(rect, QBrush(self.darkSquareColor))

        # Draw the inset.
        painter.setPen(QPen(QBrush(darkBorderColor), self.shadowWidth))
        painter.drawLine(0, 0, 0, squareSize * 8)
        painter.drawLine(0, 0, squareSize * 8, 0)
        painter.setPen(QPen(QBrush(lightBorderColor), self.shadowWidth))
        painter.drawLine(squareSize * 8, 0, squareSize * 8, squareSize * 8)
        painter.drawLine(0, squareSize * 8, squareSize * 8, squareSize * 8)

        # Display coordinates.
        if self.showCoordinates:
            painter.setPen(QPen(QBrush(self.borderColor.lighter()), self.shadowWidth))
            coordinateSize = min(borderSize, squareSize)
            font = QFont()
            font.setPixelSize(coordinateSize * 0.6)
            painter.setFont(font)
            for i, rank in enumerate(["8", "7", "6", "5", "4", "3", "2", "1"]):
                pos = QRect(-borderSize, squareSize * i, borderSize, squareSize).center()
                painter.save()
                painter.translate(pos.x(), pos.y())
                painter.rotate(-self.rotation)
                painter.drawText(QRect(-coordinateSize / 2, -coordinateSize / 2, coordinateSize, coordinateSize), Qt.AlignCenter, rank)
                painter.restore()
            for i, file in enumerate(["a", "b", "c", "d", "e", "f", "g", "h"]):
                pos = QRect(squareSize * i, squareSize * 8, squareSize, borderSize).center()
                painter.save()
                painter.translate(pos.x(), pos.y())
                painter.rotate(-self.rotation)
                painter.drawText(QRect(-coordinateSize / 2, -coordinateSize / 2, coordinateSize, coordinateSize), Qt.AlignCenter, file)
                painter.restore()

        # Draw pieces.
        for x in range(0, 8):
            for y in range(0, 8):
                square = Square.from_x_and_y(x, 7 - y)
                piece = self.position[square]
                if piece and square != self.draggedSquare:
                    painter.save()
                    painter.translate((x + 0.5) * squareSize, (y + 0.5) * squareSize)
                    painter.rotate(-self.rotation)
                    self.pieceRenderers[piece].render(painter, QRectF(-squareSize / 2, -squareSize / 2, squareSize, squareSize))
                    painter.restore()

        # Draw a floating piece.
        painter.restore()
        if self.draggedSquare:
            piece = self.position[self.draggedSquare]
            if piece:
                painter.save()
                painter.translate(self.dragPosition.x(), self.dragPosition.y())
                painter.rotate(-self.rotation)
                self.pieceRenderers[piece].render(painter, QRect(-squareSize / 2, -squareSize / 2, squareSize, squareSize))
                painter.restore()

        painter.end()

    def squareAt(self, point):
        # Undo the rotation.
        transform = QTransform()
        transform.translate(self.width() / 2, self.height() / 2)
        transform.rotate(self.rotation)
        logicalPoint = transform.inverted()[0].map(point)

        frameSize = min(self.width(), self.height()) * (1 - self.margin * 2)
        borderSize = min(self.width(), self.height()) * self.padding
        squareSize = (frameSize - 2 * borderSize) / 8.0
        x = int(logicalPoint.x() / squareSize + 4)
        y = 7 - int(logicalPoint.y() / squareSize + 4)
        try:
            return Square.from_x_and_y(x, y)
        except IndexError:
            return None

    def canDragSquare(self, square):
        if (self.ply % 2 == 0 and self.parent.white) or (self.ply % 2 == 1 and not self.parent.white):
            return False
        for move in self.position.get_legal_moves():
            if move.source == square:
                return True
        return False

    def onSquareClicked(self, square):
        pass

    def moveFromDragDrop(self, source, target):
        for move in self.position.get_legal_moves():
            if move.source == source and move.target == target:
                if move.promotion:
                    dialog = PromotionDialog(self.position[move.source].color, self)
                    if dialog.exec_():
                        return Move(source, target, dialog.selectedType())
                else:
                    return move
                return move


class PromotionDialog(QDialog):

    def __init__(self, color, parent=None):
        super(PromotionDialog, self).__init__(parent)

        self.promotionTypes = ["q", "n", "r", "b"]

        grid = QGridLayout()
        hbox = QHBoxLayout()
        grid.addLayout(hbox, 0, 0)

        # Add the piece buttons.
        self.buttonGroup = QButtonGroup(self)
        for i, promotionType in enumerate(self.promotionTypes):
            # Create an icon for the piece.
            piece = Piece.from_color_and_type(color, promotionType)
            renderer = QSvgRenderer(plugin_super_class.path_to_data('chess') + "classic-pieces/%s-%s.svg" % (piece.full_color, piece.full_type))
            pixmap = QPixmap(32, 32)
            pixmap.fill(Qt.transparent)
            painter = QPainter()
            painter.begin(pixmap)
            renderer.render(painter, QRect(0, 0, 32, 32))
            painter.end()

            # Add the button.
            button = QPushButton(QIcon(pixmap), '', self)
            button.setCheckable(True)
            self.buttonGroup.addButton(button, i)
            hbox.addWidget(button)

        self.buttonGroup.button(0).setChecked(True)

        # Add the ok and cancel buttons.
        buttons = QDialogButtonBox(QDialogButtonBox.Cancel | QDialogButtonBox.Ok)
        buttons.rejected.connect(self.reject)
        buttons.accepted.connect(self.accept)
        grid.addWidget(buttons, 1, 0)

        self.setLayout(grid)
        self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint)

    def selectedType(self):
        return self.promotionTypes[self.buttonGroup.checkedId()]


class Chess(plugin_super_class.PluginSuperClass):

    def __init__(self, *args):
        super(Chess, self).__init__('Chess', 'chess', *args)
        self.game = -1
        self.board = None
        self.white = True
        self.pre = None
        self.last_move = None
        self.is_my_move = False
        self._app = args[0]
        self._profile=self._app._ms._profile
        self._window = None

    def get_description(self):
        return QApplication.translate("Chess", 'Plugin which allows you to play chess with your friends.')

    def get_window(self):
        inst = self
        if not self.board:
            self.board = Board(self)
        if not hasattr(self, '_window') or not self._window:
            self._window = self.board
        return self.board

    def lossless_packet(self, data, friend_number):
        if data == 'new':
            self.pre = None
            friend = self._profile.get_friend_by_number(friend_number)
            reply = QMessageBox.question(None,
                                         'New chess game',
                                         'Friend {} wants to play chess game against you. Start?'.format(friend.name),
                                         QMessageBox.Yes,
                                         QMessageBox.No)
            if reply != QMessageBox.Yes:
                self.send_lossless('no', friend_number)
            else:
                self.send_lossless('yes', friend_number)
                self.board = Board(self)
                self.board.show()
                self.game = friend_number
                self.white = False
                self.is_my_move = False
        elif data == 'yes' and friend_number == self.game:
            self.board = Board(self)
            self.board.show()
            self.board.update_title(True)
            self.is_my_move = True
            self.last_move = None
        elif data == 'no':
            self.game = -1
        elif data != self.pre:  # move
            self.pre = data
            self.is_my_move = True
            self.last_move = None
            a = Square.from_x_and_y(ord(data[0]) - ord('a'), ord(data[1]) - ord('1'))
            b = Square.from_x_and_y(ord(data[2]) - ord('a'), ord(data[3]) - ord('1'))
            self.board.position.make_move(Move(a, b, data[4] if len(data) == 5 else None))
            self.board.repaint()
            self.board.update_title(True)
            self.board.ply += 1

    def start_game(self, num):
        self.white = True
        self.send_lossless('new', num)
        self.game = num

    def resend_move(self):
        if self.is_my_move or self.last_move is None:
            return
        self.send_lossless(str(self.last_move), self.game)
        QTimer.singleShot(1000, self.resend_move)

    def stop_game(self):
        self.last_move = None

    def move(self, move):
        self.is_my_move = False
        self.last_move = move
        self.send_lossless(str(move), self.game)
        self.board.update_title()
        QTimer.singleShot(1000, self.resend_move)

    def get_menu(self, menu, num):
        act = QAction(QApplication.translate("Chess", "Start chess game"), menu)
        act.triggered.connect(lambda: self.start_game(num))
        return [act]
