import os
from collections import defaultdict
from gettext import gettext as _
from typing import Any

from lutris import settings
from lutris.runners.runner import Runner
from lutris.util import system
from lutris.util.display import DISPLAY_MANAGER

AMIGAS: dict[str, Any] = {
    "A500": {
        "name": _("Amiga 500"),
        "bios_sha1": [
            "891e9a547772fe0c6c19b610baf8bc4ea7fcb785",
            "c39bd9094d4e5f4e28c1411f3086950406062e87",
            "90933936cce43ca9bc6bf375662c076b27e3c458",
        ],
    },
    "A500+": {"name": _("Amiga 500+"), "bios_sha1": ["c5839f5cb98a7a8947065c3ed2f14f5f42e334a1"]},
    "A600": {"name": _("Amiga 600"), "bios_sha1": ["02843c4253bbd29aba535b0aa3bd9a85034ecde4"]},
    "A1200": {"name": _("Amiga 1200"), "bios_sha1": ["e21545723fe8374e91342617604f1b3d703094f1"]},
    "A3000": {"name": _("Amiga 3000"), "bios_sha1": ["f8e210d72b4c4853e0c9b85d223ba20e3d1b36ee"]},
    "A4000": {
        "name": _("Amiga 4000"),
        "bios_sha1": ["5fe04842d04a489720f0f4bb0e46948199406f49", "c3c481160866e60d085e436a24db3617ff60b5f9"],
    },
    "A1000": {"name": _("Amiga 1000"), "bios_sha1": ["11f9e62cf299f72184835b7b2a70a16333fc0d88"]},
    "CD32": {
        "name": _("Amiga CD32"),
        "bios_sha1": ["3525be8887f79b5929e017b42380a79edfee542d"],
        "bios_ext_sha1": ["5bef3d628ce59cc02a66e6e4ae0da48f60e78f7f"],
    },
    "CDTV": {
        "name": _("Commodore CDTV"),
        "bios_sha1": [
            "891e9a547772fe0c6c19b610baf8bc4ea7fcb785",
            "c39bd9094d4e5f4e28c1411f3086950406062e87",
            "90933936cce43ca9bc6bf375662c076b27e3c458",
        ],
        "bios_ext_sha1": ["7ba40ffa17e500ed9fed041f3424bd81d9c907be"],
    },
}


def get_bios_hashes():
    """Return mappings of sha1 hashes to Amiga models
    The first mapping contains the kickstarts and the second one, the extensions (for CD32/CDTV)
    """
    hashes = defaultdict(list)
    ext_hashes = defaultdict(list)
    for model, model_def in AMIGAS.items():
        for sha1_hash in model_def["bios_sha1"]:
            hashes[sha1_hash].append(model)
        if "bios_ext_sha1" in model_def:
            for sha1_hash in model_def["bios_ext_sha1"]:
                ext_hashes[sha1_hash].append(model)
    return hashes, ext_hashes


def scan_dir_for_bios(path):
    """Return a tuple of mappings of Amiga models and their corresponding kickstart file.

    Kickstart files must reside in `path`
    The first mapping contains the kickstarts and the second one, the extensions (for CD32/CDTV)
    """
    bios_sizes = [262144, 524288]
    hashes, ext_hashes = get_bios_hashes()
    found_bios = {}
    found_ext = {}
    incomplete_bios = []
    for file_name in os.listdir(path):
        abs_path = os.path.join(path, file_name)
        file_size = os.path.getsize(abs_path)
        if file_size not in bios_sizes:
            continue
        checksum = system.get_file_checksum(abs_path, "sha1")
        if checksum in hashes:
            for model in hashes[checksum]:
                found_bios[model] = abs_path
        if checksum in ext_hashes:
            for model in ext_hashes[checksum]:
                found_ext[model] = abs_path
    for model in found_bios:
        if "bios_ext_sha1" in AMIGAS[model] and model not in found_ext:
            incomplete_bios.append(model)
    found_bios = {k: v for k, v in found_bios.items() if k not in incomplete_bios}
    return found_bios, found_ext


class fsuae(Runner):
    human_name = _("FS-UAE")
    description = _("Amiga emulator")
    flatpak_id = "net.fsuae.FS-UAE"
    platform_dict = {model["name"]: key for key, model in AMIGAS.items()}

    cpumodel_choices = [
        (_("68000"), "68000"),
        (_("68010"), "68010"),
        (_("68020 with 24-bit addressing"), "68EC020"),
        (_("68020"), "68020"),
        (_("68030 without internal MMU"), "68EC030"),
        (_("68030"), "68030"),
        (_("68040 without internal FPU and MMU"), "68EC040"),
        (_("68040 without internal FPU"), "68LC040"),
        (_("68040 without internal MMU"), "68040-NOMMU"),
        (_("68040"), "68040"),
        (_("68060 without internal FPU and MMU"), "68EC060"),
        (_("68060 without internal FPU"), "68LC060"),
        (_("68060 without internal MMU"), "68060-NOMMU"),
        (_("68060"), "68060"),
        (_("Auto"), "auto"),
    ]
    memory_choices = [
        (_("0"), "0"),
        (_("1 MB"), "1024"),
        (_("2 MB"), "2048"),
        (_("4 MB"), "4096"),
        (_("8 MB"), "8192"),
    ]
    zorroiii_choices = [
        (_("0"), "0"),
        (_("1 MB"), "1024"),
        (_("2 MB"), "2048"),
        (_("4 MB"), "4096"),
        (_("8 MB"), "8192"),
        (_("16 MB"), "16384"),
        (_("32 MB"), "32768"),
        (_("64 MB"), "65536"),
        (_("128 MB"), "131072"),
        (_("256 MB"), "262144"),
        (_("384 MB"), "393216"),
        (_("512 MB"), "524288"),
        (_("768 MB"), "786432"),
        (_("1 GB"), "1048576"),
    ]
    flsound_choices = [
        ("0", "0"),
        ("25", "25"),
        ("50", "50"),
        ("75", "75"),
        ("100", "100"),
    ]
    gpucard_choices = [
        ("None", "None"),
        ("UAEGFX", "uaegfx"),
        ("UAEGFX Zorro II", "uaegfx-z2"),
        ("UAEGFX Zorro III", "uaegfx-z3"),
        ("Picasso II Zorro II", "picasso-ii"),
        ("Picasso II+ Zorro II", "picasso-ii+"),
        ("Picasso IV", "picasso-iv"),
        ("Picasso IV Zorro II", "picasso-iv-z2"),
        ("Picasso IV Zorro III", "picasso-iv-z3"),
    ]
    gpumem_choices = [
        (_("0"), "0"),
        (_("1 MB"), "1024"),
        (_("2 MB"), "2048"),
        (_("4 MB"), "4096"),
        (_("8 MB"), "8192"),
        (_("16 MB"), "16384"),
        (_("32 MB"), "32768"),
        (_("64 MB"), "65536"),
        (_("128 MB"), "131072"),
        (_("256 MB"), "262144"),
    ]
    flspeed_choices = [
        (_("Turbo"), "0"),
        ("100%", "100"),
        ("200%", "200"),
        ("400%", "400"),
        ("800%", "800"),
    ]
    runner_executable = "fs-uae/fs-uae"
    game_options = [
        {
            "option": "main_file",
            "type": "file",
            "label": _("Boot disk"),
            "help": _(
                "The main floppy disk file with the game data. \n"
                "FS-UAE supports floppy images in multiple file formats: "
                "ADF, IPF, DMS are the most common. ADZ (compressed ADF) "
                "and ADFs in zip files are a also supported.\n"
                "Files ending in .hdf will be mounted as hard drives and "
                "ISOs can be used for Amiga CD32 and CDTV models."
            ),
        },
        {
            "option": "disks",
            "section": _("Media"),
            "type": "multiple_file",
            "label": _("Additional floppies"),
            "help": _("The additional floppy disk image(s)."),
        },
        {
            "option": "cdrom_image",
            "section": _("Media"),
            "label": _("CD-ROM image"),
            "type": "file",
            "help": _("CD-ROM image to use on non CD32/CDTV models"),
        },
    ]

    runner_options = [
        {
            "option": "platform",
            "label": _("Amiga model"),
            "type": "choice",
            "choices": platform_dict,
            "default": next(iter(platform_dict.values())),
            "help": _("Specify the Amiga model you want to emulate."),
        },
        {
            "option": "kickstart_file",
            "section": _("Kickstart"),
            "label": _("Kickstart ROMs location"),
            "type": "file",
            "help": _(
                "Choose the folder containing original Amiga Kickstart "
                "ROMs. Refer to FS-UAE documentation to find how to "
                "acquire them. Without these, FS-UAE uses a bundled "
                "replacement ROM which is less compatible with Amiga "
                "software."
            ),
        },
        {
            "option": "kickstart_ext_file",
            "section": _("Kickstart"),
            "label": _("Extended Kickstart location"),
            "type": "file",
            "help": _("Location of extended Kickstart used for CD32"),
        },
        {
            "option": "gfx_fullscreen_amiga",
            "section": _("Graphics"),
            "label": _("Fullscreen (F12 + F to switch)"),
            "type": "bool",
            "default": False,
        },
        {
            "option": "scanlines",
            "section": _("Graphics"),
            "label": _("Scanlines display style"),
            "type": "bool",
            "default": False,
            "help": _("Activates a display filter adding scanlines to imitate the displays of yesteryear."),
        },
        {
            "option": "grafixcard",
            "section": _("Graphics"),
            "label": _("Graphics Card"),
            "type": "choice",
            "choices": gpucard_choices,
            "default": "None",
            "advanced": True,
            "help": _(
                "Use this option to enable a graphics card. This option is none by default, in "
                "which case only chipset graphics (OCS/ECS/AGA) support is available."
            ),
        },
        {
            "option": "grafixmemory",
            "section": _("Graphics"),
            "label": _("Graphics Card RAM"),
            "type": "choice",
            "choices": gpumem_choices,
            "default": "0",
            "advanced": True,
            "help": _(
                "Override the amount of graphics memory on the graphics card. The 0 MB option is "
                "not really valid, but exists for user interface reasons."
            ),
        },
        {
            "option": "cpumodel",
            "label": _("CPU"),
            "type": "choice",
            "choices": cpumodel_choices,
            "default": "auto",
            "advanced": True,
            "help": _(
                "Use this option to override the CPU model in the emulated Amiga. All Amiga "
                "models imply a default CPU model, so you only need to use this option if you "
                "want to use another CPU."
            ),
        },
        {
            "option": "fmemory",
            "label": _("Fast Memory"),
            "type": "choice",
            "choices": memory_choices,
            "default": "0",
            "advanced": True,
            "help": _("Specify how much Fast Memory the Amiga model should have."),
        },
        {
            "option": "ziiimem",
            "label": _("Zorro III RAM"),
            "type": "choice",
            "choices": zorroiii_choices,
            "default": "0",
            "advanced": True,
            "help": _(
                "Override the amount of Zorro III Fast memory, specified in KB. Must be a "
                "multiple of 1024. The default value depends on [amiga_model]. Requires a "
                "processor with 32-bit address bus, (use for example the A1200/020 model)."
            ),
        },
        {
            "option": "fdvolume",
            "section": _("Media"),
            "label": _("Floppy Drive Volume"),
            "type": "choice",
            "choices": flsound_choices,
            "default": "0",
            "advanced": True,
            "help": _("Set volume to 0 to disable floppy drive clicks when the drive is empty. Max volume is 100."),
        },
        {
            "option": "fdspeed",
            "section": _("Media"),
            "label": _("Floppy Drive Speed"),
            "type": "choice",
            "choices": flspeed_choices,
            "default": "100",
            "advanced": True,
            "help": _(
                "Set the speed of the emulated floppy drives, in percent. "
                "For example, you can specify 800 to get an 8x increase in "
                "speed. Use 0 to specify turbo mode. Turbo mode means that "
                "all floppy operations complete immediately. The default is 100 for most models."
            ),
        },
        {
            "option": "jitcompiler",
            "label": _("JIT Compiler"),
            "type": "bool",
            "default": False,
            "advanced": True,
        },
        {
            "option": "gamemode",
            "label": _("Feral GameMode"),
            "type": "bool",
            "default": False,
            "advanced": True,
            "help": _("Automatically uses Feral GameMode daemon if available. Set to true to disable the feature."),
        },
        {
            "option": "govwarning",
            "label": _("CPU governor warning"),
            "type": "bool",
            "default": False,
            "advanced": True,
            "help": _(
                "Warn if running with a CPU governor other than performance. Set to true to disable the warning."
            ),
        },
        {
            "option": "bsdsocket",
            "label": _("UAE bsdsocket.library"),
            "type": "bool",
            "default": False,
            "advanced": True,
        },
    ]

    @property
    def directory(self):
        return os.path.join(settings.RUNNER_DIR, "fs-uae")

    def get_absolute_path(self, path):
        """Return the absolute path for a file"""
        return path if os.path.isabs(path) else os.path.join(self.game_path, path)

    def insert_floppies(self):
        disks = []
        main_disk = self.game_config.get("main_file")
        if main_disk:
            disks.append(main_disk)

        game_disks = self.game_config.get("disks") or []
        for disk in game_disks:
            if disk not in disks:
                disks.append(disk)
        # Make all paths absolute
        disks = [self.get_absolute_path(disk) for disk in disks]
        drives = []
        floppy_images = []
        for drive, disk_path in enumerate(disks):
            disk_param = self.get_disk_param(disk_path)
            drives.append("--%s_%d=%s" % (disk_param, drive, disk_path))
            if disk_param == "floppy_drive":
                floppy_images.append("--floppy_image_%d=%s" % (drive, disk_path))
        cdrom_image = self.game_config.get("cdrom_image")
        if cdrom_image:
            drives.append("--cdrom_drive_0=%s" % self.get_absolute_path(cdrom_image))
        return drives + floppy_images

    def get_disk_param(self, disk_path):
        amiga_model = self.runner_config.get("platform") or self.runner_config.get("model")
        if amiga_model in ("CD32", "CDTV"):
            return "cdrom_drive"
        if disk_path.lower().endswith(".hdf"):
            return "hard_drive"
        return "floppy_drive"

    def get_params(self):  # pylint: disable=too-many-branches
        params = []
        option_params = {
            "kickstart_file": "--kickstart_file=%s",
            "kickstart_ext_file": "--kickstart_ext_file=%s",
            "platform": "--amiga_model=%s",
            "cpumodel": "--cpu=%s",
            "fmemory": "--fast_memory=%s",
            "ziiimem": "--zorro_iii_memory=%s",
            "fdvolume": "--floppy_drive_volume=%s",
            "fdspeed": "--floppy_drive_speed=%s",
            "grafixcard": "--graphics_card=%s",
            "grafixmemory": "--graphics_memory=%s",
        }
        for option, param in option_params.items():
            option_value = self.runner_config.get(option)
            if option_value:
                params.append(param % option_value)

        if self.runner_config.get("gfx_fullscreen_amiga"):
            width = DISPLAY_MANAGER.get_current_resolution()[0]
            params.append("--fullscreen")
            # params.append("--fullscreen_mode=fullscreen-window")
            params.append("--fullscreen_mode=fullscreen")
            params.append("--fullscreen_width=%s" % width)
        if self.runner_config.get("jitcompiler"):
            params.append("--jit_compiler=1")
        if self.runner_config.get("bsdsocket"):
            params.append("--bsdsocket_library=1")
        if self.runner_config.get("gamemode"):
            params.append("--game_mode=0")
        if self.runner_config.get("govwarning"):
            params.append("--governor_warning=0")
        if self.runner_config.get("scanlines"):
            params.append("--scanlines=1")
        return params

    def play(self):
        return {"command": self.get_command() + self.get_params() + self.insert_floppies()}
