import random
import copy

"""7 columns, 6 rows"""
"""Line of 4 wins, vertically, horizontally, diagonally"""

BOARD_SIZE = 6, 7
LINE_LENGTH = 4
PIECE_ONE = chr(int('2B24', 16))  # Unicode for white circle
PIECE_TWO = chr(int('25EF', 16))  # Unicode for black circle
EMPTY_SQUARE = " "
PIECES = [PIECE_ONE, PIECE_TWO]


def create_board(size=BOARD_SIZE):
    """Create the board"""
    board = [[EMPTY_SQUARE for __ in range(size[1])] for _ in range(size[0])]
    return board


def print_board(board):
    for row in board:
        line = "|"
        for square in row:
            line += " " + square + " |"
        print(line)
        # print("-" * (BOARD_SIZE[1] * 4 + 1))


def get_column(board, column):
    return [row[column] for row in board]


def place_piece(board, column, player):
    """Sets value based on the player in the lowest empty square in a particular column"""
    current_col = get_column(board, column)
    move_index = current_col[::-1].index(EMPTY_SQUARE)
    board[-1 - move_index][column] = player


def player_input(board):
    """Returns the index of the column provided by the player"""
    while True:
        column = input("Choose a column to place a piece!")
        if column in [str(x) for x in range(1, (BOARD_SIZE[1] + 1))]:  # check if the value provided is within range
            col_index = int(column) - 1  # Convert player coordinate into index
            if board[0][col_index] != EMPTY_SQUARE:  # If top field is not empty, column is full
                print("This column is full!")
                continue
            return col_index
        print("Incorrect input!")


def bot_random_move(board):
    """Randomly selects and returns a valid non-full column"""
    while True:
        col_index = random.randrange(0, BOARD_SIZE[1])
        if board[0][col_index] == EMPTY_SQUARE:
            return col_index


def monte_carlo(board, column, player):
    """Plays a randomly generated game based on the current state of the board,
     and returns an evaluation of the outcome"""
    local_board = copy.deepcopy(board)
    local_player = copy.deepcopy(player)
    first_turn = True
    while True:
        if first_turn:  # On the first turn the column choice is fixed, after that it is random
            place_piece(local_board, column, local_player)
            first_turn = False
        else:
            place_piece(local_board, bot_random_move(local_board), local_player)
        if win_con(local_board):
            return 1 if local_player == player else -1
        if board_full(local_board):
            return 0
        local_player = PIECE_TWO if local_player == PIECE_ONE else PIECE_ONE


def counter_win(board, player):
    """Checks if the enemy has a winning move, and returns the first column index that would result in enemy win"""
    local_player = PIECE_TWO if player == PIECE_ONE else PIECE_ONE
    for column in range(BOARD_SIZE[1]):
        local_board = copy.deepcopy(board)
        place_piece(local_board, column, local_player)
        if win_con(local_board):
            return column
    return False


def evaluate_moves(board, player, iterations_per_column):
    """Evaluates the strength of possible moves by recording the results of a number of monte_carlo functions"""
    """Returns the index of the column with the highest value"""
    results = [0] * BOARD_SIZE[1]  # Creates list to contain the evaluations
    for column in range(BOARD_SIZE[1]):  # Iterates each possible move
        if board[0][column] == EMPTY_SQUARE:
            for __ in range(iterations_per_column):
                results[column] += monte_carlo(board, column, player)  # Adds the result to the list under the col index
        else:
            results[column] = 0
    biggest_number = results[0]
    biggest_index = 0
    for number_index in range(len(results)):
        if results[number_index] > biggest_number:
            biggest_number = results[number_index]
            biggest_index = number_index
    print(results)
    return biggest_index


def check_line(line):
    """Check if there is a sequence of four pieces of a kind within a line, and returns which piece"""
    for element in line:  # Iterate for each element
        if element in PIECES:
            line_index = line.index(element)
            slice = line[line_index: line_index + LINE_LENGTH]
            if slice.count(element) == LINE_LENGTH:
                return element


def get_diagonal(board, row_index, col_index):
    """Constructs and returns a list of the values in the diagonal line"""
    diagonal = []
    while row_index < BOARD_SIZE[0] and col_index < BOARD_SIZE[1]:  # While the incrementing indices are still in range
        diagonal.append(board[row_index][col_index])
        row_index += 1
        col_index += 1
    return diagonal


def get_all_diagonals(board):
    """Returns a list containing all the diagonals as lists"""
    flipped_board = board[::-1]  # Reverse diagonals are generated by reversing the orders of the rows
    diagonals = []
    for current_board in [board, flipped_board]:
        for first_col_diagonals in range(BOARD_SIZE[0] - (LINE_LENGTH - 1)):  # All diagonals starting from first column
            diagonals.append(get_diagonal(current_board, first_col_diagonals, 0))
        for rest_diagonals in range(1, BOARD_SIZE[1] - (LINE_LENGTH - 1)):  # Second to last diagonals starting from
            # the first row
            diagonals.append(get_diagonal(current_board, 0, rest_diagonals))
    return diagonals


def win_con(board):
    """Checks whether there are a number of the same piece in sequence"""
    all_columns = [get_column(board, col_index) for col_index in range(BOARD_SIZE[1])]
    all_diagonals = get_all_diagonals(board)
    containers = [board, all_columns, all_diagonals]
    for container in containers:  # For full board by row, column or diagonal
        for line in container:  # Isolate each line
            result = check_line(line)
            if result:
                return result
    return False


def board_full(board):
    return not (EMPTY_SQUARE in board[0])


if __name__ == "__main__":
    player = PIECE_ONE
    board = create_board()
    board[-1][2] = PIECE_TWO
    board[-1][3] = PIECE_TWO
    board[-1][4] = PIECE_TWO
    print_board(board)
    while True:
        if player == PIECE_ONE:
            place_piece(board, player_input(board), player)
        else:
            counter_move = counter_win(board, player)
            if counter_win(board, player):
                place_piece(board, counter_move, player)
            else:
                place_piece(board, evaluate_moves(board, player, 50), player)
        print_board(board)
        if board_full(board):
            print("Draw!")
            break
        if win_con(board):
            print(player, "wins!")
            break
        player = PIECE_TWO if player == PIECE_ONE else PIECE_ONE
        print("")

    # while True:
    #     place_piece(board, player_input(board), player)
    #     print_board(board)
    #     if board_full(board):
    #         print("Draw!")
    #         break
    #     if win_con(board):
    #         print(player, "wins!")
    #         break
    #     player = PIECE_TWO if player == PIECE_ONE else PIECE_ONE
