from sys import stderr
import time
import math
from typing import Any, Callable
import Goban


def _next_color(color):
    return Goban.Board._BLACK if color == Goban.Board._WHITE else Goban.Board._WHITE

# Returns heuristic, move
def _alphabeta(
    board: Goban.Board,
    heuristic: Callable[[Goban.Board, Any], float],
    color,
    move,
    alpha=-math.inf,
    beta=math.inf,
    depth: int = 3,
    shouldStop = lambda: False
) -> tuple[float, Any]:

    wantMax = (board.next_player == color)
    if depth == 0 or board.is_game_over():
        return heuristic(board, color), move

    if wantMax:
        acc = -math.inf, None
        for move in board.generate_legal_moves():
            if Goban.Board.flat_to_name(move) == "PASS":
                continue

            board.push(move)
            value = (
                _alphabeta(
                    board,
                    alpha=alpha,
                    beta=beta,
                    move=move,
                    heuristic=heuristic,
                    color=_next_color(color),
                    depth=depth - 1,
                )[0],
                move,
            )
            acc = max(
                acc,
                value,
                key=lambda t: t[0],
            )
            board.pop()

            if shouldStop() or acc[0] >= beta:
                break  # beta cutoff
            alpha = max(alpha, acc[0])

    else:
        acc = math.inf, None
        for move in board.generate_legal_moves():

            board.push(move)
            value = (
                _alphabeta(
                    board,
                    alpha=alpha,
                    beta=beta,
                    move=move,
                    heuristic=heuristic,
                    color=_next_color(color),
                    depth=depth - 1,
                )[0],
                move,
            )
            acc = min(
                acc,
                value,
                key=lambda t: t[0],
            )
            board.pop()

            if shouldStop() or acc[0] <= alpha:
                break  # alpha cutoff
            beta = min(beta, acc[0])

    return acc


def alphabeta(
    board: Goban.Board,
    heuristic: Callable[[Goban.Board, Any], float],
    color,
    depth: int = 3,
):
    _, move = _alphabeta(board, move=-1, heuristic=heuristic, color=color, depth=depth)
    return move


def IDDFS(board: Goban.Board, heuristic, color, duration: float, maxdepth=42):
    st = time.time()
    shouldStop = (lambda: time.time() - st > duration)
    depth = 0
    move = -1
    score = -1

    while not shouldStop() and depth <= maxdepth:
        if depth % 2 == 0:
            score, move = _alphabeta(
                    board, heuristic, color, move=move, alpha=-math.inf, beta=math.inf, depth=depth, shouldStop=shouldStop
            )

            if score == math.inf:
                return move, score

        else:
            score, move = _alphabeta(
                board, heuristic, color, move=move, alpha=-math.inf, beta=math.inf, depth=depth, shouldStop=shouldStop
            )

            if score == -math.inf:
                return move, score

        print("depth:", depth, time.time() - st, score,  file=stderr)
        depth += 1

    print(time.time() - st, duration, depth, file=stderr)
    return move, score
