from .rom import ROM
from .pointerTable import PointerTable
from .assembler import ASM


class Texts(PointerTable):
    END_OF_DATA = (0xfe, 0xff)

    def __init__(self, rom):
        super().__init__(rom, {
            "count": 0x2B0,
            "pointers_addr": 1,
            "pointers_bank": 0x1C,
            "banks_addr": 0x741,
            "banks_bank": 0x1C,
            "expand_to_end_of_bank": {0x09}
        })


class Entities(PointerTable):
    def __init__(self, rom):
        super().__init__(rom, {
            "count": 0x320,
            "pointers_addr": 0,
            "pointers_bank": 0x16,
            "data_bank": 0x16,
        })

class RoomsTable(PointerTable):
    HEADER = 2

    def _readData(self, rom, bank_nr, pointer):
        bank = rom.banks[bank_nr]
        start = pointer
        pointer += self.HEADER
        while bank[pointer] != 0xFE:
            obj_type = (bank[pointer] & 0xF0)
            if obj_type == 0xE0:
                pointer += 5
            elif obj_type == 0xC0 or obj_type == 0x80:
                pointer += 3
            else:
                pointer += 2
        pointer += 1
        self._addStorage(bank_nr, start, pointer)
        return bank[start:pointer]


class RoomsOverworldTop(RoomsTable):
    def __init__(self, rom):
        super().__init__(rom, {
            "count": 0x080,
            "pointers_addr": 0x000,
            "pointers_bank": 0x09,
            "data_bank": 0x09,
            "alt_pointers": {
                "Alt06": (0x00, 0x31FD),
                "Alt0E": (0x00, 0x31CD),
                "Alt1B": (0x00, 0x320D),
                "Alt2B": (0x00, 0x321D),
                "Alt79": (0x00, 0x31ED),
            }
        })


class RoomsOverworldBottom(RoomsTable):
    def __init__(self, rom):
        super().__init__(rom, {
            "count": 0x080,
            "pointers_addr": 0x100,
            "pointers_bank": 0x09,
            "data_bank": 0x1A,
            "alt_pointers": {
                "Alt8C": (0x00, 0x31DD),
            }
        })


class RoomsIndoorA(RoomsTable):
    # TODO: The color dungeon tables are in the same bank, but the pointer table is after the room data.
    def __init__(self, rom):
        super().__init__(rom, {
            "count": 0x100,
            "pointers_addr": 0x000,
            "pointers_bank": 0x0A,
            "data_bank": 0x0A,
            "alt_pointers": {
                "Alt1F5": (0x00, 0x31A1),
            }
        })


class RoomsIndoorB(RoomsTable):
    # Most likely, this table can be expanded all the way to the end of the bank,
    # giving a few 100 extra bytes to work with.
    def __init__(self, rom):
        super().__init__(rom, {
            "count": 0x0FF,
            "pointers_addr": 0x000,
            "pointers_bank": 0x0B,
            "data_bank": 0x0B,
        })


class RoomsColorDungeon(RoomsTable):
    def __init__(self, rom):
        super().__init__(rom, {
            "count": 0x016,
            "pointers_addr": 0x3B77,
            "pointers_bank": 0x0A,
            "data_bank": 0x0A,
            "expand_to_end_of_bank": True
        })


class BackgroundTable(PointerTable):
    def _readData(self, rom, bank_nr, pointer):
        bank = rom.banks[bank_nr]
        start = pointer
        while bank[pointer] != 0x00:
            addr = bank[pointer] << 8 | bank[pointer + 1]
            amount = (bank[pointer + 2] & 0x3F) + 1
            repeat = (bank[pointer + 2] & 0x40) == 0x40
            vertical = (bank[pointer + 2] & 0x80) == 0x80
            pointer += 3
            if not repeat:
                pointer += amount
            if repeat:
                pointer += 1
        pointer += 1
        self._addStorage(bank_nr, start, pointer)
        return bank[start:pointer]


class BackgroundTilesTable(BackgroundTable):
    def __init__(self, rom):
        super().__init__(rom, {
            "count": 0x26,
            "pointers_addr": 0x052B,
            "pointers_bank": 0x20,
            "data_bank": 0x08,
            "expand_to_end_of_bank": True
        })


class BackgroundAttributeTable(BackgroundTable):
    def __init__(self, rom):
        super().__init__(rom, {
            "count": 0x26,
            "pointers_addr": 0x1C4B,
            "pointers_bank": 0x24,
            "data_bank": 0x24,
            "expand_to_end_of_bank": True
        })


class OverworldRoomSpriteData(PointerTable):
    def __init__(self, rom):
        super().__init__(rom, {
            "count": 0x100,
            "pointers_addr": 0x30D3,
            "pointers_bank": 0x20,
            "data_bank": 0x20,
            "data_addr": 0x33F3,
            "data_size": 4,
            "claim_storage_gaps": True,
        })


class IndoorRoomSpriteData(PointerTable):
    def __init__(self, rom):
        super().__init__(rom, {
            "count": 0x220,
            "pointers_addr": 0x31D3,
            "pointers_bank": 0x20,
            "data_bank": 0x20,
            "data_addr": 0x363B,
            "data_size": 4,
            "claim_storage_gaps": True,
        })


class ROMWithTables(ROM):
    def __init__(self, data, patches=None):
        super().__init__(data, patches)

        # Ability to patch any text in the game with different text
        self.texts = Texts(self)

        # Ability to modify rooms
        self.entities = Entities(self)
        self.rooms_overworld_top = RoomsOverworldTop(self)
        self.rooms_overworld_bottom = RoomsOverworldBottom(self)
        self.rooms_indoor_a = RoomsIndoorA(self)
        self.rooms_indoor_b = RoomsIndoorB(self)
        self.rooms_color_dungeon = RoomsColorDungeon(self)
        self.room_sprite_data_overworld = OverworldRoomSpriteData(self)
        self.room_sprite_data_indoor = IndoorRoomSpriteData(self)

        # Backgrounds for things like the title screen.
        self.background_tiles = BackgroundTilesTable(self)
        self.background_attributes = BackgroundAttributeTable(self)

        self.itemNames = {}

    def save(self):
        # Assert special handling of bank 9 expansion is fine
        for i in range(0x3d42, 0x4000):
            assert self.banks[9][i] == 0, self.banks[9][i]
        self.texts.store(self)
        self.entities.store(self)
        self.rooms_overworld_top.store(self)
        self.rooms_overworld_bottom.store(self)
        self.rooms_indoor_a.store(self)
        self.rooms_indoor_b.store(self)
        self.rooms_color_dungeon.store(self)
        leftover_storage = self.room_sprite_data_overworld.store(self)
        self.room_sprite_data_indoor.addStorage(leftover_storage)
        self.patch(0x00, 0x0DFA, ASM("ld hl, $763B"), ASM("ld hl, $%04x" % (leftover_storage[0]["start"] | 0x4000)))
        self.room_sprite_data_indoor.adjustDataStart(leftover_storage[0]["start"])
        self.room_sprite_data_indoor.store(self)
        self.background_tiles.store(self)
        self.background_attributes.store(self)
        return super().save()
