import os

class PyBoyAdvance:

    def __init__(self, gamepak: GamePak | str | os.PathLike, bios: str | os.PathLike | None=None, skip_bios: bool=False):
        self.gamepak = GamePak.from_file(gamepak) if isinstance(gamepak, (str, os.PathLike)) else gamepak
        if bios:
            with open(bios, 'rb') as bios_file:
                bios_data = bytearray(bios_file.read())
        else:
            bios_data = b''
        self.scheduler = Scheduler()
        self.memory = Memory(self.scheduler, self.gamepak, bios_data)
        self.interrupt_controller = InterruptController(self.scheduler)
        self.dma_controller = DMAController(self.scheduler, self.memory)
        self.ppu = PPU(self.scheduler, self.interrupt_controller)
        self.keypad = Keypad(self.interrupt_controller)
        self.memory.io = IO(self.memory, self.interrupt_controller, self.dma_controller, self.ppu, self.keypad)
        self.cpu = CPU(self.scheduler, self.memory)
        self.cpu.arm_decoder = arm_decoder
        self.cpu.thumb_decoder = thumb_decoder
        if skip_bios:
            self.cpu.regs.banked_sp[int(BankIndex.BANK_SYSTEM_USER)] = 50364160
            self.cpu.regs.banked_sp[int(BankIndex.BANK_FIQ)] = 50364160
            self.cpu.regs.banked_sp[int(BankIndex.BANK_IRQ)] = 50364320
            self.cpu.regs.banked_sp[int(BankIndex.BANK_SWI)] = 50364384
            self.cpu.regs.banked_sp[int(BankIndex.BANK_ABORT)] = 50364160
            self.cpu.regs.banked_sp[int(BankIndex.BANK_UNDEFINED)] = 50364160
            self.cpu.regs.set_sp(50364160)
            self.cpu.regs.set_pc(134217728)
            self.cpu.flush_pipeline()
        else:
            self.cpu.interrupt(ExceptionVector.RESET)

    def step(self):
        if self.dma_controller.get_active():
            self.dma_controller.perform_transfers()
        else:
            self.cpu.step()
        self.scheduler.process_events()

    def frame(self):
        end_time = self.scheduler.cycles + 280896
        while self.scheduler.cycles < end_time:
            self.step()

    def run(self):
        with Window() as window:
            running = True
            while running:
                self.frame()
                for event in window.get_events():
                    if event == WindowEvent.WindowEvent_NONE:
                        continue
                    elif event == WindowEvent.QUIT:
                        running = False
                    elif event == WindowEvent.FULLSCREEN:
                        window.set_fullscreen(not window.get_fullscreen())
                    else:
                        self.keypad.process_window_event(event)
                window.render(self.ppu.get_frame_buffer_ptr())