from NetUtils import ClientStatus, color
from worlds.AutoSNIClient import SNIClient
from .Regions import offset
import logging

snes_logger = logging.getLogger("SNES")

ROM_NAME = (0x7FC0, 0x7FD4 + 1 - 0x7FC0)

READ_DATA_START = 0xF50EA8
READ_DATA_END = 0xF50FE7 + 1

GAME_FLAGS = (0xF50EA8, 64)
COMPLETED_GAME = (0xF50F22, 1)
BATTLEFIELD_DATA = (0xF50FD4, 20)

RECEIVED_DATA = (0xE01FF0, 3)

ITEM_CODE_START = 0x420000

IN_GAME_FLAG = (4 * 8) + 2

NPC_CHECKS = {
    4325676: ((6 * 8) + 4, False),  # Old Man Level Forest
    4325677: ((3 * 8) + 6, True),  # Kaeli Level Forest
    4325678: ((25 * 8) + 1, True),  # Tristam
    4325680: ((26 * 8) + 0, True),  # Aquaria Vendor Girl
    4325681: ((29 * 8) + 2, True),  # Phoebe Wintry Cave
    4325682: ((25 * 8) + 6, False),  # Mysterious Man (Life Temple)
    4325683: ((29 * 8) + 3, True),  # Reuben Mine
    4325684: ((29 * 8) + 7, True),  # Spencer
    4325685: ((29 * 8) + 6, False),  # Venus Chest
    4325686: ((29 * 8) + 1, True),  # Fireburg Tristam
    4325687: ((26 * 8) + 1, True),  # Fireburg Vendor Girl
    4325688: ((14 * 8) + 4, True),  # MegaGrenade Dude
    4325689: ((29 * 8) + 5, False),  # Tristam's Chest
    4325690: ((29 * 8) + 4, True),  # Arion
    4325691: ((29 * 8) + 0, True),  # Windia Kaeli
    4325692: ((26 * 8) + 2, True),  # Windia Vendor Girl

}


def get_flag(data, flag):
    byte = int(flag / 8)
    bit = int(0x80 / (2 ** (flag % 8)))
    return (data[byte] & bit) > 0


class FFMQClient(SNIClient):
    game = "Final Fantasy Mystic Quest"

    async def validate_rom(self, ctx):
        from SNIClient import snes_read
        rom_name = await snes_read(ctx, *ROM_NAME)
        if rom_name is None:
            return False
        if rom_name[:2] != b"MQ":
            return False

        ctx.rom = rom_name
        ctx.game = self.game
        ctx.items_handling = 0b001
        return True

    async def game_watcher(self, ctx):
        from SNIClient import snes_buffered_write, snes_flush_writes, snes_read

        check_1 = await snes_read(ctx, 0xF53749, 1)
        received = await snes_read(ctx, RECEIVED_DATA[0], RECEIVED_DATA[1])
        data = await snes_read(ctx, READ_DATA_START, READ_DATA_END - READ_DATA_START)
        check_2 = await snes_read(ctx, 0xF53749, 1)
        if check_1 in (b'\x00', b'\x55') or check_2 in (b'\x00', b'\x55'):
            return

        def get_range(data_range):
            return data[data_range[0] - READ_DATA_START:data_range[0] + data_range[1] - READ_DATA_START]
        completed_game = get_range(COMPLETED_GAME)
        battlefield_data = get_range(BATTLEFIELD_DATA)
        game_flags = get_range(GAME_FLAGS)

        if game_flags is None:
            return
        if not get_flag(game_flags, IN_GAME_FLAG):
            return

        if not ctx.finished_game:
            if completed_game[0] & 0x80 and game_flags[30] & 0x18:
                await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}])
                ctx.finished_game = True

        old_locations_checked = ctx.locations_checked.copy()

        for container in range(256):
            if get_flag(game_flags, (0x20 * 8) + container):
                ctx.locations_checked.add(offset["Chest"] + container)

        for location, data in NPC_CHECKS.items():
            if get_flag(game_flags, data[0]) is data[1]:
                ctx.locations_checked.add(location)

        for battlefield in range(20):
            if battlefield_data[battlefield] == 0:
                ctx.locations_checked.add(offset["BattlefieldItem"] + battlefield + 1)

        if old_locations_checked != ctx.locations_checked:
            await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": ctx.locations_checked}])

        if received[0] == 0:
            received_index = int.from_bytes(received[1:], "big")
            if received_index < len(ctx.items_received):
                item = ctx.items_received[received_index]
                received_index += 1
                code = (item.item - ITEM_CODE_START) + 1
                if code > 256:
                    code -= 256
                snes_buffered_write(ctx, RECEIVED_DATA[0], bytes([code, *received_index.to_bytes(2, "big")]))
        await snes_flush_writes(ctx)