import chess
import io

# run command "pip install stockfish"
from stockfish import Stockfish

skip_camera = None
def init_stockfish(bool1):
    global skip_camera
    skip_camera = bool1

# def read_from(file_name):
#     with open(r"%s" %file_name, 'r') as File_object: # 'with' autocloses file
#         file_data = File_object.read()
#     return file_data

# def write_to(file_name, file_data):
#     with open(r"%s" %file_name, 'w') as File_object:
#         File_object.write(file_data)
#     return 0

def FEN_check(FEN_string):
    '''
    DESCRIPTION:    Checks if the FEN_string is valid.
    INPUT:          FEN_string: String in FEN format.
    RETURN:         is_valid_FEN: Bool that says if the FEN_string was valid.
    '''
    try:
        board = chess.Board(FEN_string)
        return True
    except ValueError:
        return False

def cheat_check(FEN_string, FEN_board_state_before):
    # flag variable
    is_cheating = True

    # # set board and FEN from state before
    # FEN_board_state_before = read_from("saved_FEN")

    # Edge case: AI is first to move on start
    if (FEN_board_state_before == "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" or FEN_board_state_before == "" and FEN_string == "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"):
        is_cheating = False
        return is_cheating

    if FEN_string == "":
        return is_cheating
    
    board_before = chess.Board(FEN_board_state_before)

    # start of weird/custom game, ignore on the first time
    if board_before.ply() < 2:
        is_cheating = False
        return is_cheating

    # possible move set from board state before
    move_list = board_before.legal_moves

    # iterate all moves to find a move that makes curr board state
    for move in move_list:
        # make copy of board state before and push moves
        board_copy = board_before.copy()
        board_copy.push(move)

        # if there's a match, it's legal
        if (board_copy.fen() == FEN_string):
            is_cheating = False
            break

    return is_cheating

def special_move_check(FEN_string, best_move):
    '''
    DESCRIPTION:    Check if the move is capture, castling, promotion, or en passant.
    INPUT:          FEN_string: String that holds FEN.
                    best_move: String that holds move
    RETURN:         special_move_return: List holding flags of special moves and location of affected piece:
                        [0~4]: Hold 0/1 (False/ True) signifying what type of move.
                            0: Not a special move
                            1: En Passant
                            2: Castling
                            3: Capture
                            4: Promotion
                        [5]: Holds best move.

    '''
    special_move_return = [0, 0, 0, 0, 0, best_move]
    # print("This is best move:", best_move)
    board = chess.Board(FEN_string)
    move_processed = chess.Move.from_uci(best_move)
    
    # check en passant
    if board.is_en_passant(move_processed):
        special_move_return[1] = 1

    # check castling
    if board.is_castling(move_processed):
        special_move_return[2] = 1

    # check capture
    if board.is_capture(move_processed):
        special_move_return[3] = 1

    # check promotion
    is_promotion = chess.square_rank(chess.Move.from_uci(best_move).to_square) in [0, 7] and board.piece_type_at(chess.Move.from_uci(best_move).from_square) == chess.PAWN
    if is_promotion:
        special_move_return[4] = 1

    return special_move_return

def Stockfish_eval(FEN_string):
    '''
    DESCRIPTION:    Calculates a best move based off Stockfish engine.
    INPUT:          FEN_string: String in FEN notation.          
    RETURN:         best_move: String of best move (i.e. "e7e6").
    '''

    # setting up stockfish
    if skip_camera:
        stockfish = Stockfish(  path = "C:\ece445\stockfish\stockfish-windows-x86-64-avx2.exe", # install Stockfish and input path to .exe here
                                depth = 15,                          # 20 is good but slower, 15 is fine
                                parameters={"Skill Level":19})    # range: 0-19? 20 might exist or rounds to 19, https://github.com/vondele/Stockfish/blob/master/src/search.cpp
    else:
        stockfish = None # FIX FOR RASPI
        
    # set state of board based off FEN
    stockfish.set_fen_position(FEN_string)

    # eval best move
    best_move = stockfish.get_best_move()

    # === TEST: Visualize chess board state ===
    # print(stockfish.get_board_visual(True))

    return best_move

def Minimax_eval(FEN_string):
    return 0

def Personal_eval(FEN_string):
    return 0

def eval_best_move(FEN_string, which_algorithm):
    '''
    DESCRIPTION:    Takes in current game state as FEN and then evaluates a best move.
    INPUT:          FEN_string: A FEN string.
                    which_algorithm: Flag that determines best move algorithm:
                        0: Stockfish
                        1: Minimax
                        2: Personal
    RETURN:         best_move: Tuple that holds:
                        [0]: From move info.
                        [1]: To move info.
                        [2]: FEN string of board.
    '''    
    # calc best move based off flag
    best_move = [Stockfish_eval(FEN_string), Minimax_eval(FEN_string), Personal_eval(FEN_string)][which_algorithm]

    # === TEST: See best move ===
    # print("Best move is: ", best_move)

    return best_move

def prep_info_to_ESP(FEN_string, best_move_processed):
    '''
    DESCRIPTION:    Preps the info to be sent to the ESP32.
    INPUTS:         FEN_string: String that holds FEN.
                    best_move_processed: List that holds flags and best move.
    RETURN:         ESP_input: List containing info for the ESP32.
                        [0]: Contains move to go to.
                        [1]: Contains various strings of secondary piece, if applicable.
                            "": If reg move or promotion.
                            "<move>": If En Passant, Castling, or Capture.
                        [2]: Contains special move flags.
                            [0]: Reg move
                            [1]: En Passant
                            [2]: Castling
                            [3]: Capture
                            [4]: Promotion
                        [3]: Contains FEN before move.
    
    '''
    # print(best_move_processed)
    special_piece_location = ""

    # Promotion needs no case since:
        # if there's no piece in its way, it's a simple move
        # if there's a piece, it will go under Capture

    # Capture comes before En Passant since En Passant captures a piece in the process
    # Capture case
    if best_move_processed[3]:
        special_piece_location = [best_move_processed[5][2] + ' ' + best_move_processed[5][3]]

    # En Passant case
    if best_move_processed[1]:
        # left to right
        special_piece_location = [best_move_processed[5][2] + ' ' + best_move_processed[5][1]]
    
    # Castling case
    if best_move_processed[2]:
        # king side castle (e -> g)
        # if best_move_processed[5][2] == 'g':
        special_piece_location = [('h' if best_move_processed[5][2] == 'g' else 'a') + ' ' + best_move_processed[5][1], ('f' if best_move_processed[5][2] == 'g' else 'd') + ' ' + best_move_processed[5][1]] 

        # queen side castle (e -> c)
        # if best_move_processed[5][2] == 'c':
        
    ESP_input = [[best_move_processed[5][0] + ' ' + best_move_processed[5][1], best_move_processed[5][2] + ' ' + best_move_processed[5][3]], 
                 special_piece_location, 
                 best_move_processed[0:5], 
                 FEN_string]

    # print(ESP_input)

    # get move's dest and src (separated with space)
    # move_from = best_move[0:1] + ' ' + best_move[1:2]
    # move_to = best_move[2:3] + ' ' + best_move[3:4]


    return ESP_input

# def update_FEN_string(FEN_string, best_move, file_name):
#     '''
#     DESCRIPTION:    Updates the saved FEN with the best_move added to it.
#     INPUT:          FEN_string: String that hold FEN notation of board.
#                     best_move: String that holds best move from eval.
#                     file_name: String that holds the file name.
#     RETURN:         n/a
#     '''
#     board = chess.Board(FEN_string)   
#     board.push(chess.Move.from_uci(best_move))
#     write_to(file_name, board.fen())

def chess_AI(FEN_string, FEN_board_state_before, which_alg):
    # check if valid FEN
    is_valid_FEN = FEN_check(FEN_string)

    if not is_valid_FEN:
        print("FEN format is wrong, please fix")
        return -1
   
    else:
        # # check if there's cheating
        # is_cheating = cheat_check(FEN_string, FEN_board_state_before)
        
        # if is_cheating:
        #     print("Illegal move detected, please fix")
        #     return -1
        # else:
        
        # get best move
        best_move = eval_best_move(FEN_string, which_alg)

        # check move for special characteristics
        best_move_processed = special_move_check(FEN_string, best_move)

        # prepare info for ESP32
        ESP_input = prep_info_to_ESP(FEN_string, best_move_processed)

        # # update saved FEN 
        # file_name = "saved_FEN"
        # update_FEN_string(FEN_string, best_move, file_name)

        return ESP_input, best_move

# def tests(FEN_string, which_func, which_test):
#     '''
#     DESCRIPTION:    Contains and executes different tests for various functions.    
#     INPUT:          FEN_string: String containing FEN.
#                     which_func: Int that dictates which functionality to test for.
#                         0: FEN
#                         1: Eval
#                         2: Cheating
#                     which_test: Int that dictates which test of specific functionality to test for.
#     RETURN:
#     '''
#     message = "FEN Parsing" if which_func == 0 else "Eval" if which_func == 1 else "Cheating" if  which_func == 2 else "Special Move" if which_func == 3 else "Processing ESP32 Info" if which_func == 4 else "Chess AI"
#     print("You're currently testing: " + message + "'s Test #" + str(which_test))

#     # === TESTS: FEN Parsing ===
#     if which_func == 0:
#         is_valid_FEN = False

#         # 0: Valid FEN (Success)
#         if which_test == 0:
#             FEN_string = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
#             is_valid_FEN = FEN_check(FEN_string)
        
#         # 1: Invalid FEN (Fail)
#         if which_test == 1:
#             FEN_string = "This should totally fail"
#             is_valid_FEN = FEN_check(FEN_string)

#         # 2: Invalid FEN by 1 extra char (Fail)
#         if which_test == 2:
#             FEN_string = "rnbqkbnrr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
#             is_valid_FEN = FEN_check(FEN_string)

#         message = "Valid" if is_valid_FEN else "Invalid"
#         print("This inputted FEN is:", message)
        
#     # === TESTS: Eval ===
#     if which_func == 1:
        
#         # TEST: Inputting different moves ===
#         if which_test == 0:
#             FEN_string = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" # base: white starts
#             board = chess.Board(FEN_string)
#             move_list = ["g1f3", "c7c5"          # "b8a6"
#                         ]
#             for move in move_list:
#                 board.push(chess.Move.from_uci(move))

#     # === TESTS: Cheating ===
#     if which_func == 2:
#         # save old FEN
#         file_name = "saved_FEN"
#         saved_FEN = read_from(file_name)
#         is_cheating = True

#         # 0: Illegal ply/state of board (Fail)
#         if which_test == 0:
#             write_to(file_name, "r1bqkb1r/pppp1ppp/2n2n2/4p3/2B1P3/5N2/PPPP1PPP/RNBQK2R w KQkq - 1 6")  # random board state
#             FEN_string = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" # base: white starts
#             is_cheating = cheat_check(FEN_string)

#         # 1: Valid ply by human (Success)
#         if which_test == 1:
#             write_to(file_name, "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1") # base: white starts
#             FEN_string = "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1" # black's turn, one ply by white since base
#             is_cheating = cheat_check(FEN_string)

#         # 2: AI is first to move on start & saved_FEN is filled (Success)
#         if which_test == 2:
#             write_to(file_name, "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1") # base: white starts
#             FEN_string = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" # base: white starts
#             is_cheating = cheat_check(FEN_string)

#         # 3: AI is first to move on start & saved_FEN is empty (Success)
#         if which_test == 3:
#             write_to(file_name, "") # base: white starts
#             FEN_string = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" # base: white starts
#             is_cheating = cheat_check(FEN_string)

#         message = "Yes" if is_cheating else "No"
#         print("Was there cheating involved?:", message)

#         # restore old FEN
#         write_to(file_name, saved_FEN)

#     # === TESTS: Special Moves ===
#     if which_func == 3:
        
#         # 0: Castle
#         if which_test == 0:
#             is_special_move = special_move_check("r3k2r/pppppppp/8/8/8/8/PPPPPPPP/R3K2R w KQkq - 0 1", "e1g1")

#         # 1: Capture
#         if which_test == 1:
#             is_special_move = special_move_check("r1bqkbnr/pppp1ppp/2n5/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R w KQkq - 0 3", "f3e5")

#         # 2: Promotion
#         if which_test == 2:
#             is_special_move = special_move_check("8/PPPPP1PP/8/8/8/8/8/8 w - - 0 1", "a7a8q")
        
#         # 3: En Passant & Capture
#         if which_test == 3:
#             is_special_move = special_move_check("8/8/8/4pP2/4P3/8/8/8 w - e6 0 1", "f5e6")
        
#         # print messages based off what it was
#         if is_special_move[0]:
#                 print("The move results in reg move")
#         if is_special_move[1]:
#                 print("The move results in En Passant at location:", is_special_move[5][2:4])
#         if is_special_move[2]:
#                 print("The move results in Castling at location:", is_special_move[5][2:4])
#         if is_special_move[3]:
#                 print("The move results in Capture at location:", is_special_move[5][2:4])
#         if is_special_move[4]:
#                 print("The move results in Promotion at location:", is_special_move[5][2:4])

#     # === TESTS: Process ESP32 ===
#     if which_func == 4:

#         # 0: En Passant
#         if which_test == 0:
#             FEN_string = "8/8/8/4pP2/4P3/8/8/8 w - e6 0 1"
#             best_move_processed = special_move_check("8/8/8/4pP2/4P3/8/8/8 w - e6 0 1", "f5e6")
#             ESP_input = prep_info_to_ESP(FEN_string, best_move_processed)

#         # 1: Capture
#         if which_test == 1:
#             FEN_string = "r1bqkbnr/pppp1ppp/2n5/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R w KQkq - 0 3"
#             best_move_processed = is_special_move = special_move_check("r1bqkbnr/pppp1ppp/2n5/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R w KQkq - 0 3", "f3e5")
#             ESP_input = prep_info_to_ESP(FEN_string, best_move_processed)

#         # 2: Castling (king side)
#         if which_test == 2:
#             FEN_string = "r3k2r/pppppppp/8/8/8/8/PPPPPPPP/R3K2R w KQkq - 0 1"
#             best_move_processed = special_move_check("r3k2r/pppppppp/8/8/8/8/PPPPPPPP/R3K2R w KQkq - 0 1", "e1g1")
#             ESP_input = prep_info_to_ESP(FEN_string, best_move_processed)
        
#         # 3: Castling (queen side)
#         if which_test == 3:
#             FEN_string = "r3k2r/8/8/8/8/8/8/R3K2R w KQkq - 0 1"
#             best_move_processed = special_move_check("r3k2r/8/8/8/8/8/8/R3K2R w KQkq - 0 1", "e1c1")
#             ESP_input = prep_info_to_ESP(FEN_string, best_move_processed)

#         print(ESP_input)

#     # === TESTS: Entire Alg ===
#     if which_func == 5:
#         file_name = "saved_FEN"
#         saved_FEN = read_from(file_name)

#         # 0: Base game
#         if which_test == 0:
#             FEN_string = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" # base: white starts

#         if which_test == 1:
#             FEN_string = "r3k2r/8/8/8/8/8/8/R3K2R w KQkq - 0 1"  # grabbed from ESP32, Test 3
        
#         board = chess.Board(FEN_string)
#         print(board)

#         which_alg = 0
#         ret_val = chess_AI(FEN_string, which_alg)
#         print("Return value is:",ret_val)
#         string_test = ret_val[0][0] + ret_val[0][1]
#         string_test = string_test.replace(" ", "")
#         board.push(chess.Move.from_uci(string_test))
#         print(board)

#         # restore old FEN
#         write_to(file_name, saved_FEN)

# if __name__ == '__main__':  
#     # comment out these when calling with Raspberry PI 4 (make sure when you call chess_AI, you set the parameters)
#     FEN_string = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" # base: white starts
#     # FEN_string = "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1"
#     which_alg = 0

#     # Chess AI program
#     # ret_val = chess_AI(FEN_string, which_alg)
#     # print(ret_val)

#     # Tests
#     which_func = 5
#     which_test = 1
#     tests(FEN_string, which_func, which_test)