"""Contains function to iterate different kinds of possible actions"""
from typing import Iterator, List, Tuple

from .. import board
from . import board_actions


def possible_huakill_action(
    search_board: board.Board,
) -> Iterator[board_actions.HuaKillAction]:
    """Check if the flowercard can be eliminated"""
    for index, stack in enumerate(search_board.field):
        if stack and stack[-1] == board.SpecialCard.Hua:
            yield board_actions.HuaKillAction(
                source_field_id=index, source_field_row_index=len(stack) - 1
            )


def possible_dragonkill_actions(
    search_board: board.Board,
) -> Iterator[board_actions.DragonKillAction]:
    """Enumerate all possible dragon kills"""
    possible_dragons = [
        board.SpecialCard.Zhong,
        board.SpecialCard.Fa,
        board.SpecialCard.Bai,
    ]
    if not any(x is None for x in search_board.bunker):
        new_possible_dragons = []
        for dragon in possible_dragons:
            if any(x == dragon for x in search_board.bunker):
                new_possible_dragons.append(dragon)
        possible_dragons = new_possible_dragons

    for dragon in possible_dragons:
        bunker_dragons = [i for i, d in enumerate(search_board.bunker) if d == dragon]
        field_dragons = [
            i for i, f in enumerate(search_board.field) if f if f[-1] == dragon
        ]
        if len(bunker_dragons) + len(field_dragons) != 4:
            continue
        destination_bunker_id = 0
        if bunker_dragons:
            destination_bunker_id = bunker_dragons[0]
        else:
            destination_bunker_id = [
                i for i, x in enumerate(search_board.bunker) if x is None
            ][0]

        source_stacks = [(board.Position.Bunker, i) for i in bunker_dragons]
        source_stacks.extend([(board.Position.Field, i) for i in field_dragons])

        yield board_actions.DragonKillAction(
            dragon=dragon,
            source_stacks=source_stacks,
            destination_bunker_id=destination_bunker_id,
        )


def possible_bunkerize_actions(
    search_board: board.Board,
) -> Iterator[board_actions.BunkerizeAction]:
    """Enumerates all possible card moves from the field to the bunker"""
    open_bunker_list = [i for i, x in enumerate(search_board.bunker) if x is None]

    if not open_bunker_list:
        return

    open_bunker = open_bunker_list[0]
    for index, stack in enumerate(search_board.field):
        if not stack:
            continue
        yield board_actions.BunkerizeAction(
            card=stack[-1],
            field_id=index,
            field_row_index=len(stack) - 1,
            bunker_id=open_bunker,
            to_bunker=True,
        )


def possible_debunkerize_actions(
    search_board: board.Board,
) -> Iterator[board_actions.BunkerizeAction]:
    """Enumerates all possible card moves from the bunker to the field"""
    bunker_number_cards = [
        (i, x)
        for i, x in enumerate(search_board.bunker)
        if isinstance(x, board.NumberCard)
    ]
    for index, card in bunker_number_cards:
        for other_index, other_stack in enumerate(search_board.field):
            if not other_stack:
                continue
            if not isinstance(other_stack[-1], board.NumberCard):
                continue
            if other_stack[-1].suit == card.suit:
                continue
            if other_stack[-1].number != card.number + 1:
                continue
            yield board_actions.BunkerizeAction(
                card=card,
                bunker_id=index,
                field_id=other_index,
                field_row_index=len(other_stack),
                to_bunker=False,
            )


def possible_goal_move_actions(
    search_board: board.Board,
) -> Iterator[board_actions.GoalAction]:
    """Enumerates all possible moves from anywhere to the goal"""
    field_cards = [
        (board.Position.Field, index, stack[-1])
        for index, stack in enumerate(search_board.field)
        if stack
    ]
    bunker_cards = [
        (board.Position.Bunker, index, card)
        for index, card in enumerate(search_board.bunker)
    ]
    top_cards = [
        x for x in field_cards + bunker_cards if isinstance(x[2], board.NumberCard)
    ]
    top_cards = [
        x for x in top_cards if x[2].number == search_board.getGoal(x[2].suit) + 1
    ]

    result = []
    for source, index, card in top_cards:
        obvious = all(
            search_board.getGoal(other_suit) >= card.number - 2
            for other_suit in set(board.NumberCard.Suit) - {card.suit}
        )
        result.append(
            board_actions.GoalAction(
                card=card,
                source_id=index,
                source_row_index=len(search_board.field[index]) - 1
                if source == board.Position.Field
                else None,
                source_position=source,
                goal_id=search_board.getGoalId(card.suit),
                obvious=obvious,
            )
        )
        break
    yield from sorted(result, key=lambda x: x.card.number)


def _can_stack(bottom: board.Card, top: board.Card) -> bool:
    if not isinstance(bottom, board.NumberCard):
        return False
    if not isinstance(top, board.NumberCard):
        return False
    if bottom.suit == top.suit:
        return False
    if bottom.number != top.number + 1:
        return False
    return True


def _get_cardstacks(search_board: board.Board) -> List[List[board.Card]]:
    """Returns all cards on one stack that can be moved at once"""
    result: List[List[board.Card]] = []
    for stack in search_board.field:
        result.append([])
        if not stack:
            continue
        result[-1].append(stack[-1])
        for card in stack[-2::-1]:
            if not _can_stack(card, result[-1][0]):
                break
            if not isinstance(card, board.NumberCard):
                break
            result[-1].insert(0, card)
    return result


def possible_field_move_actions(
    search_board: board.Board,
) -> Iterator[board_actions.MoveAction]:
    """Enumerate all possible move actions
    from one field stack to another field stack"""
    first_empty_field_id = -1
    cardstacks = [x for x in enumerate(_get_cardstacks(search_board)) if x[1]]
    cardstacks = sorted(cardstacks, key=lambda x: len(x[1]))
    substacks: List[Tuple[int, List[board.Card]]] = []

    for index, stack in cardstacks:
        substacks.extend(
            (index, substack) for substack in (stack[i:] for i in range(len(stack)))
        )

    for source_index, source_substack in substacks:
        for destination_index, destination_stack in enumerate(search_board.field):
            if source_index == destination_index:
                continue
            if destination_stack:
                if not _can_stack(destination_stack[-1], source_substack[0]):
                    continue
            elif len(source_substack) == len(search_board.field[source_index]):
                continue
            elif first_empty_field_id == -1:
                first_empty_field_id = destination_index
            elif destination_index != first_empty_field_id:
                continue
            yield board_actions.MoveAction(
                cards=source_substack,
                source_id=source_index,
                source_row_index=len(search_board.field[source_index])
                - len(source_substack),
                destination_id=destination_index,
                destination_row_index=len(destination_stack),
            )


def possible_actions(search_board: board.Board) -> List[board_actions.Action]:
    """Enumerate all possible actions on the current search_board"""
    result: List[board_actions.Action] = [
        *list(possible_huakill_action(search_board)),
        *list(possible_dragonkill_actions(search_board)),
        *list(possible_goal_move_actions(search_board)),
        *list(possible_debunkerize_actions(search_board)),
        *list(possible_field_move_actions(search_board)),
        *list(possible_bunkerize_actions(search_board)),
    ]

    for action in result:
        if action.automatic():
            return [action]

    return result
