import asyncio

from gi.repository import GObject

from pychess.Utils.const import (
    BLACKWON,
    WHITEWON,
    DRAW,
    UNDOABLE_STATES,
    CANCELLED,
    PRACTICE_GOAL_REACHED,
    BLACK,
    WHITE,
    LECTURE,
    LESSON,
    PUZZLE,
    ENDGAME,
)
from pychess.Utils.GameModel import GameModel

learn2str = {
    LECTURE: "Lecture",
    PUZZLE: "Puzzle",
    LESSON: "Lesson",
    ENDGAME: "Endgame",
}

MATE, MATE_IN, DRAW_IN, EQUAL_IN, EVAL_IN, PROMOTION = range(6)


class Goal:
    def __init__(self, termination):
        self.termination = termination
        # print(termination)

        if termination.startswith("mate in"):
            self.result = MATE_IN
            self.moves = int(termination.split()[-1])
            self.cp = None
        elif termination.startswith("draw in"):
            self.result = DRAW_IN
            self.moves = int(termination.split()[-1])
            self.cp = None
        elif termination.startswith("equalize in"):
            self.result = EQUAL_IN
            self.moves = int(termination.split()[-1])
            self.cp = None
        elif "cp in" in termination:
            self.result = EVAL_IN
            parts = termination.split()
            self.moves = int(parts[-1])
            self.cp = int(parts[0][1:-2])
        elif "promotion with" in termination:
            self.result = PROMOTION
            self.moves = None
            parts = termination.split()
            self.cp = int(parts[-1][1:-2])
        else:
            self.result = MATE
            self.moves = None
            self.cp = None
            # print("No termination tag, expecting MATE", termination)


class LearnModel(GameModel):
    __gsignals__ = {
        # goal_checked is emitted after puzzle game goal check finished
        "goal_checked": (GObject.SignalFlags.RUN_FIRST, None, ()),
        "learn_success": (GObject.SignalFlags.RUN_FIRST, None, ()),
    }

    def isChanged(self):
        """Never save learn games changes to .pgn"""
        return False

    def set_learn_data(
        self, learn_type, source, current_index=None, game_count=None, from_lesson=False
    ):
        self.learn_type = learn_type
        self.source = source
        self.current_index = current_index
        self.game_count = game_count
        self.from_lesson = from_lesson

        self.hints = {}
        self.goal = None
        self.failed_playing_best = False

        if learn_type == LECTURE:
            self.offline_lecture = True
            self.lecture_skip_event = asyncio.Event()  # set when 'Go on' button pressed
            self.lecture_pause_event = (
                asyncio.Event()
            )  # set when 'Pause' button pressed
            self.lecture_exit_event = asyncio.Event()  # set when 'Exit' button pressed

        elif learn_type == PUZZLE:
            self.puzzle_game = True
            self.goal = Goal(self.tags["Termination"])

        elif learn_type == LESSON:
            self.lesson_game = True

        elif learn_type == ENDGAME:
            self.end_game = True

    def add_hints(self, hint):
        ply, move = hint
        self.hints[ply] = [(move, 10000)]

    def check_failed_playing_best(self, status):
        if self.ply - 1 in self.hints:
            best_score = self.hints[self.ply - 1][0][1]
            best_moves = [
                hint[0]
                for hint in self.hints[self.ply - 1]
                if abs(hint[1] - best_score) <= 10
            ]
        else:
            best_moves = []

        if self.puzzle_game:
            # No need to check in best moves (and let add more time to analyzer) in trivial cases
            if self.goal.result in (MATE, MATE_IN) and (
                (status == BLACKWON and self.starting_color == BLACK)
                or (status == WHITEWON and self.starting_color == WHITE)
            ):
                return False
            elif self.goal.result == DRAW_IN and status == DRAW:
                return False
            elif self.goal.result in (MATE_IN, DRAW_IN, EQUAL_IN, EVAL_IN, PROMOTION):
                expect_best = True
            else:
                expect_best = False
        else:
            expect_best = False

        failed = expect_best and self.moves[-1].as_uci() not in best_moves
        return failed

    def check_goal(self, status, reason):
        if self.end_game:
            if status in UNDOABLE_STATES:
                self.end(status, reason)
            self.emit("goal_checked")
            return

        full_moves = (self.ply - self.lowply) // 2 + 1
        # print("Is Goal not reached?", self.goal.result, status, full_moves, self.goal.moves, self.failed_playing_best, self.tags, self.hints)

        if (
            (
                self.goal.result == DRAW_IN
                and status == DRAW
                and full_moves <= self.goal.moves
            )
            or (
                self.goal.result == MATE_IN
                and status == WHITEWON
                and full_moves <= self.goal.moves
                and self.starting_color == WHITE
            )
            or (
                self.goal.result == MATE_IN
                and status == BLACKWON
                and full_moves <= self.goal.moves
                and self.starting_color == BLACK
            )
            or (
                self.goal.result in (EVAL_IN, EQUAL_IN)
                and full_moves == self.goal.moves
                and not self.failed_playing_best
            )
            or (self.goal.result == MATE and status in (WHITEWON, BLACKWON))
            or (self.goal.result == PROMOTION and self.moves[-1].promotion)
        ):
            if status in UNDOABLE_STATES:
                self.end(status, PRACTICE_GOAL_REACHED)
            else:
                self.end(CANCELLED, PRACTICE_GOAL_REACHED)
        else:
            if status in UNDOABLE_STATES:
                # print("check_goal() status in UNDOABLE_STATES -> self.end(status, reason)")
                self.failed_playing_best = True
                self.end(status, reason)
            # print("Goal not reached yet.", self.goal.result, status, full_moves, self.goal.moves, self.failed_playing_best)

        self.emit("goal_checked")
        return
