from src.Board.board import Board
from src.AI.ai import AI
import random

class Game:
    def __init__(self, board: Board, ai: AI) -> None:
        """
        :params:
            board: Board
            ai: AI
        :return:
            None

        This is the constructor for the Game class. It takes a Board object and an AI object as parameters. It also sets the score to 0-0 and randomly chooses who goes first.
        """
        self.__board = board
        self.__ai = ai
        self.__score = [0, 0]
        self.__first_player = random.choice(["Player", "AI"])
        if self.__first_player == "AI":
            self.__play_ai()
    
    def __str__(self) -> str:
        return f"    Connect 4 - Score : Player: {self.__score[0]} | AI: {self.__score[1]}"+str(self.__board)
    
    def __repr__(self) -> str:
        return str(self)
    
    def play(self, column: int) -> None:
        """
        :params:
            column: int
        :return:
            None

        This function is called by the main class to play a move. It takes an integer between 0 and 6 as a parameter. This integer represents the column the player wants to play in.
        """
        lpo = self.__play_human(column)
        if self.__check_win(lpo):
            self.__score[0] += 1
            return "Player"
        if self.__board.is_full():
            return "Draw"
        lpo = self.__play_ai()
        if self.__check_win(lpo):
            self.__score[1] += 1
            return "AI"
        if self.__board.is_full():
            return "Draw"
        return None

    def get_score(self) -> list[int]:
        """
        :params:
            None
        :return:
            list[int]

        This method returns the score as a list of ints. The first int represents the player's score and the second int represents the AI's score.
        """
        return self.__score

    def __check_win(self, lpo: tuple[int,int]) -> bool:
        """
        :params:
            lpo: tuple[int,int]
        :return:
            bool
        
        This method checks if the last piece played has won the game. It takes a tuple of ints as a parameter. This tuple represents the last piece played.
        """
        return self.__check_column_win(lpo) or self.__check_row_win(lpo) or self.__check_diagonal1_win(lpo) or self.__check_diagonal2_win(lpo) 
        
    def __check_column_win(self, lpo: tuple[int,int]) -> bool:
        """
        :params:
            lpo: tuple[int,int]
        :return:
            bool

        This method checks if the last piece played has won the game by checking the column. It takes a tuple of ints as a parameter. This tuple represents the last piece played.
        """
        check = 0
        for row in range(5):
            if self.__board.check_cell(row, lpo[1]) == self.__board.check_cell(row + 1, lpo[1]) and self.__board.check_cell(row, lpo[1]) != None:
                check += 1
            else:
                check = 0
            if check == 3:
                return True
        return False

    def __check_row_win(self, lpo: tuple[int,int]) -> bool:
        """
        :params:
            lpo: tuple[int,int]
        :return:
            bool

        This method checks if the last piece played has won the game by checking the row. It takes a tuple of ints as a parameter. This tuple represents the last piece played.
        """
        check = 0
        for column in range(6):
            if self.__board.check_cell(lpo[0], column) == self.__board.check_cell(lpo[0], column + 1) and self.__board.check_cell(lpo[0], column) != None:
                check += 1
            else:
                check = 0
            if check == 3:
                return True
        return False

    def __check_diagonal1_win(self, lpo: tuple[int,int]) -> bool:
        """
        :params:
            lpo: tuple[int,int]
        :return:
            bool
        
        This method checks if the last piece played has won the game by checking the first diagonal. It takes a tuple of ints as a parameter. This tuple represents the last piece played.
        """
        check = 0
        offset = -lpo[0] if lpo[0] < lpo[1] else -lpo[1]
        while lpo[0]+offset < 5 and lpo[1]+offset < 6:
            if self.__board.check_cell(lpo[0]+offset, lpo[1]+offset) == self.__board.check_cell(lpo[0]+offset+1, lpo[1]+offset+1) and self.__board.check_cell(lpo[0]+offset, lpo[1]+offset) != None:
                check += 1
            else:
                check = 0
            if check == 3:
                return True
            offset += 1
        
    def __check_diagonal2_win(self, lpo: tuple[int,int]) -> bool:
        """
        :params:
            lpo: tuple[int,int]
        :return:
            bool

        This method checks if the last piece played has won the game by checking the second diagonal. It takes a tuple of ints as a parameter. This tuple represents the last piece played.
        """
        check = 0
        offset = -lpo[0] if lpo[0] < 6-lpo[1] else lpo[1]-5
        while lpo[0]+offset < 5 and lpo[1]-offset > 0:
            if self.__board.check_cell(lpo[0]+offset, lpo[1]-offset) == self.__board.check_cell(lpo[0]+offset+1, lpo[1]-offset-1) and self.__board.check_cell(lpo[0]+offset, lpo[1]-offset) != None:
                check += 1
            else:
                check = 0
            if check == 3:
                return True
            offset += 1


    def reset_board(self) -> None:
        """
        :params:
            None
        :return:
            None

        This method resets the board and chooses a random player to start the game.
        """
        self.__board.reset()
        self.__first_player = random.choice(["Player", "AI"])
        if self.__first_player == "AI":
            self.__play_ai()
    
    def reset_game(self) -> None:
        """
        :params:
            None
        :return:
            None

        This method resets the game and the board.
        """
        self.__score = [0, 0]
        self.reset_board()

    def __play_ai(self) -> tuple[int,int]:
        """
        :params:
            None
        :return:
            tuple[int,int]

        This method plays the AI and returns the row and column of the last piece played. 
        """
        column = self.__ai.get_move(self.__board)
        row = self.__board.place(column, 2)
        return (row, column)

    def __play_human(self, column: int) -> tuple[int,int]:
        """
        :params:
            column: int
        :return:
            tuple[int,int]
        
        This method plays the human and returns the row and column of the last piece played.
        """
        row = self.__board.place(column-1, 1)
        return (row, column-1)

