import copy

class Player():
    def __init__(self, name, sign):
        self.name = name  # player's name
        self.sign = sign  # player's sign O or X
        
    def get_sign(self):
        return self.sign

    def get_name(self):
        return self.name

    def choose(self, board):
        valid_input = False
        while not valid_input:
            cell = input(f"\n{self.name}, {self.sign}: Enter a cell [A-C][1-3]: ").upper()
            if len(cell) == 2 and cell[0] in "ABC" and cell[1] in "123":
                if board.isempty(cell):
                    board.set(cell, self.sign)
                    valid_input = True
                    print("\n")
                else:
                    print("\nYou did not choose correctly.")
            else:
                print("\nYou did not choose correctly.")

from random import choice
class AI(Player):
    def __init__(self, name, sign, board=None):
        self.name = name
        self.sign = sign
        self.moves = self._moves(board)
    def undo_move(self, board, cell):
        # Reset the specified cell on the board to an empty state
        board.set(cell, ' ')
    
    def _moves(self, board=None):
        if board != None:
            n = board.get_size()
        else:
            n = 3
        cells = []
        for char in range(65, 65+n):
            for i in range(1, 1+n):
                cells.append(chr(char) + str(i))
        return cells
    
    def set(self, cell, sign):
        
        # Convert cell to index values from 0 to 8
        row, col = int(cell[1]) - 1, ord(cell[0]) - ord('A')
        index = row * self.size + col

        # Mark the cell on the board with the sign X or O
        if self.board[index] == self.sign:
            self.board[index] = sign
        else:
            print(" P Cell is already occupied. Choose another cell.")
        
    def choose(self, board):
        print(f"{self.name}, {self.sign}: Enter a cell [A-C][1-3]: ", end="")
        valid_input = False
        while not valid_input:
            cell = choice(self.moves)
            if board.isempty(cell):
                board.set(cell, self.sign)
                valid_input = True
            else:
                pass
        print(cell)
        
'''Minimax: Logic
1. Check the base case: if the game is over, then return -1 if self lost, 0 if it is a tie, or 1 if self won.
2. Set the min score to infinity and max score to -infinity
3. Choose a cell (or make a move): 
        a. iterate through all available cells (9 cells)
        b. check if the cell is empty
        c. mark the cell with X or O (if self then mark it with its sign, otherwise mark it with another sign)
        d. get score by calling the minimax recursively (you need to alternate between self and opponent)
        e. update score: if self then use the max score (compare it to the max score), otherwise use the min score (compare it to the min score)
        f. update move: update the cell index
        g. unmark the cell (make it a space again " ") and reset all other variables that were affected by the game play
4. If it is the start level (the last level to be executed completely and the last score to be returned) return the move. 
'''



class MiniMax(AI):
    def __init__(self, name, sign, board=None):
        self.name = name
        self.sign = sign
        self.opponent_sign = 'X' if sign == 'O' else 'O'

    def choose(self, board):
        print(f"\n{self.name}, {self.sign}: Enter a cell [A-C][1-3]: ")
        cell = self.minimax(board, True, True)
        print(cell)
        board.set1(cell, self.sign)

    def minimax(self, board, self_player, start):
        board_copy = board.copy_board()

        if board_copy.isdone():
            if board_copy.get_winner() == self.sign:
                return 1
            elif board_copy.get_winner() == "":
                return 0
            else:
                return -1

        if self_player:
            max_score = float('-inf')
            best_move = None
            for i in range(3):
                for j in range(3):
                    cell = f"{chr(ord('A') + i)}{j + 1}"
                    if board_copy.isempty(cell):
                        board_copy.set1(cell, self.sign)
                        score = self.minimax(board_copy, False, False)
                        board_copy.set1(cell, ' ')

                        print(f"Self Player: {self.name}, Score: {score}, Move: {cell}")

                        if score > max_score or best_move is None:
                            max_score = score
                            best_move = cell
            if start:
                print(f"Start: {best_move}, Max Score: {max_score}")
                return best_move
            elif self_player:
                print(f"Max Score: {max_score}")
                return max_score
            else:
                print(f"Self Player: {self.name}, Score: {score}")
                return -1  # Adjusted to represent "infinity"

        else:
            min_score = float('inf')
            best_move = None
            for i in range(3):
                for j in range(3):
                    cell = f"{chr(ord('A') + i)}{j + 1}"
                    if board_copy.isempty(cell):
                        board_copy.set1(cell, self.opponent_sign)
                        score = self.minimax(board_copy, True, False)
                        board_copy.set1(cell, ' ')

                        print(f"Opponent: {self.name}, Score: {score}, Move: {cell}")

                        if score < min_score or best_move is None:
                            min_score = score
                            best_move = cell
            if start:
                print(f"Start: {best_move}, Min Score: {min_score}")
                return best_move
            elif self_player:
                print(f"Min Score: {min_score}")
                return max_score
            else:
                print(f"Opponent: {self.name}, Score: {score}")
                return min_score

# Driver code
player = 'x'
opponent = 'o'

def isMovesLeft(board):
    for i in range(3):
        for j in range(3):
            if (board[i][j] == '_'):
                return True
    return False

def evaluate(b):
    for row in range(3):
        if (b[row][0] == b[row][1] and b[row][1] == b[row][2]):
            if (b[row][0] == player):
                return 10
            elif (b[row][0] == opponent):
                return -10

    for col in range(3):
        if (b[0][col] == b[1][col] and b[1][col] == b[2][col]):
            if (b[0][col] == player):
                return 10
            elif (b[0][col] == opponent):
                return -10

    if (b[0][0] == b[1][1] and b[1][1] == b[2][2]):
        if (b[0][0] == player):
            return 10
        elif (b[0][0] == opponent):
            return -10

    if (b[0][2] == b[1][1] and b[1][1] == b[2][0]):
        if (b[0][2] == player):
            return 10
        elif (b[0][2] == opponent):
            return -10

    return 0

def minimax(board, depth, isMax):
    score = evaluate(board)

    if (score == 10):
        return score

    if (score == -10):
        return score

    if (isMovesLeft(board) == False):
        return 0

    if (isMax):
        best = -1000
        for i in range(3):
            for j in range(3):
                if (board[i][j] == '_'):
                    board[i][j] = player
                    best = max(best, minimax(board, depth + 1, not isMax))
                    board[i][j] = '_'
        return best
    else:
        best = 1000
        for i in range(3):
            for j in range(3):
                if (board[i][j] == '_'):
                    board[i][j] = opponent
                    best = min(best, minimax(board, depth + 1, not isMax))
                    board[i][j] = '_'
        return best

def findBestMove(board):
    bestVal = -1000
    bestMove = (-1, -1)

    for i in range(3):
        for j in range(3):
            if (board[i][j] == '_'):
                board[i][j] = player
                moveVal = minimax(board, 0, False)
                board[i][j] = '_'
                if (moveVal > bestVal):
                    bestMove = (i, j)
                    bestVal = moveVal
            return bestMove

class SmartAI(Player):
        def __init__(self, name, sign, board=None):
            self.name = name
            self.sign = sign
            self.moves = self._moves(board)
        def undo_move(self, board, cell):
            # Reset the specified cell on the board to an empty state
            board.set(cell, ' ')
        
        def _moves(self, board=None):
            if board != None:
                n = board.get_size()
            else:
                n = 3
            cells = []
            for char in range(65, 65+n):
                for i in range(1, 1+n):
                    cells.append(chr(char) + str(i))
            return cells
        
        def set(self, cell, sign):
            
            # Convert cell to index values from 0 to 8
            row, col = int(cell[1]) - 1, ord(cell[0]) - ord('A')
            index = row * self.size + col

            # Mark the cell on the board with the sign X or O
            if self.board[index] == self.sign:
                self.board[index] = sign
            else:
                print("Cell is already occupied. Choose another cell.")
            
        def choose(self, board):
            print(f"{self.name}, {self.sign}: Enter a cell [A-C][1-3]: ", end="")
            valid_input = False
            while not valid_input:
                cell = choice(self.moves)
                if board.isempty(cell):
                    board.set(cell, self.sign)
                    valid_input = True
                else:
                    pass
            print(cell)