#! /usr/bin/env python3

import argparse
from sys import path
from pathlib import Path
import sys
from typing import Tuple

import yaml

sys.path.append(str(Path(__file__).parent.parent))
sys.path.append(str(Path(__file__).parent.parent.parent))
sys.path.append(str(Path(__file__).parent.parent.parent))
sys.path.append(str(Path(__file__).parent.parent.parent / "splat"))
from common import get_asset_path

# allow importing splat_ext
path.append(str(Path(__file__).parent.parent.parent))

from splat_ext.pm_sbn import SBN, BufferEntry, InitSongEntry, SBNFile


# Sorts a list of dicts by their "id" key. If "id" is "auto" or missing, it is sorted to the end.
def sort_by_id_or_auto(list):
    def get_id(item):
        id = item.get("id")
        return 0xFFFF if id == "auto" else id

    list.sort(key=get_id)


def from_yaml(yaml_path: Path, asset_stack: Tuple[Path, ...]) -> SBN:
    sbn = SBN()

    with yaml_path.open("r") as f:
        config = yaml.safe_load(f)

    unknown_bin_path = get_asset_path("audio/unknown.bin", asset_stack)
    with open(unknown_bin_path, "rb") as f:
        sbn.unknown_data = f.read()

    files = config.get("files", {})
    songs = config.get("songs", [])
    mseqs = config.get("mseqs", [])
    banks = config.get("banks", [])

    sort_by_id_or_auto(files)
    sort_by_id_or_auto(songs)
    sort_by_id_or_auto(mseqs)
    sort_by_id_or_auto(banks)

    # Read files
    for file_dict in files:
        id = file_dict.get("id", "auto")
        if id == "auto":
            id = len(sbn.files)
        assert type(id) == int

        filename = file_dict.get("file")
        assert type(filename) == str

        fakesize = file_dict.get("fakesize")
        assert type(fakesize) == int or fakesize is None

        sbn_file = SBNFile()
        sbn_file_path = get_asset_path(f"audio/{filename}", asset_stack)
        sbn_file.read(sbn_file_path)

        if fakesize is not None:
            sbn_file.fakesize = fakesize
        else:
            sbn_file.fakesize = sbn_file.size

        # Replace sbn.files[id]
        if id < len(sbn.files):
            print("Overwriting file ID {:02X}", id)
            sbn.files[id] = sbn_file
        elif id == len(sbn.files):
            sbn.files.append(sbn_file)
        else:
            raise Exception(f"Invalid file ID: 0x{id:02X} - cannot have gaps")

    # Read INIT songs
    for song in songs:
        id = song.get("id", "auto")
        if id == "auto":
            id = len(sbn.init.song_entries)
        assert type(id) == int

        # Lookup file ID
        file = song.get("file")
        assert type(file) == str
        file_id = sbn.lookup_file_id(file)

        # Lookup BK file IDs
        bk_files = song.get("bk_files", [])
        assert type(bk_files) == list
        bk_file_ids = []
        for bk_file in bk_files:
            if bk_file is None:
                bk_file_ids.append(0)
            else:
                assert type(bk_file) == str
                bk_file_ids.append(sbn.lookup_file_id(bk_file))

        init_song_entry = InitSongEntry(file_id, bk_file_ids[0], bk_file_ids[1], bk_file_ids[2])

        # Replace sbn.init.song_entries[id]
        if id < len(sbn.init.song_entries):
            print(f"Overwriting song ID {id:02X}")
            sbn.init.song_entries[id] = init_song_entry
        elif id == len(sbn.init.song_entries):
            sbn.init.song_entries.append(init_song_entry)
        else:
            raise Exception(f"Invalid song ID: 0x{id:02X} - cannot have gaps")

    # Read INIT mseqs
    for mseq in mseqs:
        id = mseq.get("id", "auto")
        if id == "auto":
            id = len(sbn.init.mseq_entries)
        assert type(id) == int

        # Lookup file ID
        file = mseq.get("file")
        assert type(file) == str
        file_id = sbn.lookup_file_id(file)

        # Replace sbn.init.mseq_entries[id]
        if id < len(sbn.init.mseq_entries):
            print(f"Overwriting MSEQ ID {id:02X}")
            sbn.init.mseq_entries[id] = file_id
        elif id == len(sbn.init.mseq_entries):
            sbn.init.mseq_entries.append(file_id)
        else:
            raise Exception(f"Invalid MSEQ ID: 0x{id:02X} - cannot have gaps")

    # Read INIT banks
    for bank in banks:
        id = bank.get("id", "auto")
        if id == "auto":
            id = len(sbn.init.bk_entries)
        assert type(id) == int

        file = bank.get("file")
        assert type(file) == str
        file_id = sbn.lookup_file_id(file)

        bank_index = bank.get("bank_index")
        assert type(bank_index) == int

        bank_group = bank.get("bank_group")
        assert type(bank_group) == int

        entry = BufferEntry(file_id, bank_index, bank_group)

        # Replace sbn.init.bk_entries[id]
        if id < len(sbn.init.bk_entries):
            print(f"Overwriting bank ID {id:02X}")
            sbn.init.bk_entries[id] = entry
        elif id == len(sbn.init.bk_entries):
            sbn.init.bk_entries.append(entry)
        else:
            raise Exception(f"Invalid bank ID: {id:02X} - cannot have gaps")
    return sbn


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Builds SBN")
    parser.add_argument("out")
    parser.add_argument("asset_stack")
    args = parser.parse_args()

    out_path = Path(args.out)
    asset_stack = tuple(Path(d) for d in args.asset_stack.split(","))

    yaml_path = get_asset_path(Path("audio/sbn.yaml"), asset_stack)
    sbn = from_yaml(yaml_path, asset_stack)

    data = sbn.encode()

    with open(out_path, "wb") as f:
        f.write(data)
