"""
A module containing the BizHawkClient base class and metaclass
"""

from __future__ import annotations

import abc
from typing import TYPE_CHECKING, Any, ClassVar, Dict, Optional, Tuple, Union

from worlds.LauncherComponents import Component, SuffixIdentifier, Type, components, launch_subprocess

if TYPE_CHECKING:
    from .context import BizHawkClientContext


def launch_client(*args) -> None:
    from .context import launch
    launch_subprocess(launch, name="BizHawkClient", args=args)


component = Component("BizHawk Client", "BizHawkClient", component_type=Type.CLIENT, func=launch_client,
                      file_identifier=SuffixIdentifier())
components.append(component)


class AutoBizHawkClientRegister(abc.ABCMeta):
    game_handlers: ClassVar[Dict[Tuple[str, ...], Dict[str, BizHawkClient]]] = {}

    def __new__(cls, name: str, bases: Tuple[type, ...], namespace: Dict[str, Any]) -> AutoBizHawkClientRegister:
        new_class = super().__new__(cls, name, bases, namespace)

        # Register handler
        if "system" in namespace:
            systems = (namespace["system"],) if type(namespace["system"]) is str else tuple(sorted(namespace["system"]))
            if systems not in AutoBizHawkClientRegister.game_handlers:
                AutoBizHawkClientRegister.game_handlers[systems] = {}

            if "game" in namespace:
                AutoBizHawkClientRegister.game_handlers[systems][namespace["game"]] = new_class()

        # Update launcher component's suffixes
        if "patch_suffix" in namespace:
            if namespace["patch_suffix"] is not None:
                existing_identifier: SuffixIdentifier = component.file_identifier
                new_suffixes = [*existing_identifier.suffixes]

                if type(namespace["patch_suffix"]) is str:
                    new_suffixes.append(namespace["patch_suffix"])
                else:
                    new_suffixes.extend(namespace["patch_suffix"])

                component.file_identifier = SuffixIdentifier(*new_suffixes)

        return new_class

    @staticmethod
    async def get_handler(ctx: "BizHawkClientContext", system: str) -> Optional[BizHawkClient]:
        for systems, handlers in AutoBizHawkClientRegister.game_handlers.items():
            if system in systems:
                for handler in handlers.values():
                    if await handler.validate_rom(ctx):
                        return handler

        return None


class BizHawkClient(abc.ABC, metaclass=AutoBizHawkClientRegister):
    system: ClassVar[Union[str, Tuple[str, ...]]]
    """The system(s) that the game this client is for runs on"""

    game: ClassVar[str]
    """The game this client is for"""

    patch_suffix: ClassVar[Optional[Union[str, Tuple[str, ...]]]]
    """The file extension(s) this client is meant to open and patch (e.g. ".apz3")"""

    @abc.abstractmethod
    async def validate_rom(self, ctx: "BizHawkClientContext") -> bool:
        """Should return whether the currently loaded ROM should be handled by this client. You might read the game name
        from the ROM header, for example. This function will only be asked to validate ROMs from the system set by the
        client class, so you do not need to check the system yourself.

        Once this function has determined that the ROM should be handled by this client, it should also modify `ctx`
        as necessary (such as setting `ctx.game = self.game`, modifying `ctx.items_handling`, etc...)."""
        ...

    async def set_auth(self, ctx: "BizHawkClientContext") -> None:
        """Should set ctx.auth in anticipation of sending a `Connected` packet. You may override this if you store slot
        name in your patched ROM. If ctx.auth is not set after calling, the player will be prompted to enter their
        username."""
        pass

    @abc.abstractmethod
    async def game_watcher(self, ctx: "BizHawkClientContext") -> None:
        """Runs on a loop with the approximate interval `ctx.watcher_timeout`. The currently loaded ROM is guaranteed
        to have passed your validator when this function is called, and the emulator is very likely to be connected."""
        ...

    def on_package(self, ctx: "BizHawkClientContext", cmd: str, args: dict) -> None:
        """For handling packages from the server. Called from `BizHawkClientContext.on_package`."""
        pass