class IO:

    def __init__(self, memory: Memory, interrupt_controller: InterruptController, dma_controller: DMAController, timers: Timers, ppu: PPU, keypad: Keypad):
        self.memory = memory
        self.interrupt_controller = interrupt_controller
        self.dma_controller = dma_controller
        self.timers = timers
        self.ppu = ppu
        self.keypad = keypad
        self.reg_soundbias = 0

    def read_32(self, address: int) -> int:
        lower_bits = self.read_16(address)
        upper_bits = self.read_16(address + 2)
        return upper_bits << 16 | lower_bits

    def read_16(self, address: int) -> int:
        if address == IOAddress.REG_DISPCNT:
            return self.ppu.display_control.reg
        elif address == IOAddress.REG_DISPSTAT:
            return self.ppu.display_status.reg
        elif address == IOAddress.REG_VCOUNT:
            return self.ppu.vcount
        elif address == IOAddress.REG_BG0CNT:
            return self.ppu.bg_control_0.reg
        elif address == IOAddress.REG_BG1CNT:
            return self.ppu.bg_control_1.reg
        elif address == IOAddress.REG_BG2CNT:
            return self.ppu.bg_control_2.reg
        elif address == IOAddress.REG_BG3CNT:
            return self.ppu.bg_control_3.reg
        elif address == IOAddress.REG_WININ:
            win_in = self.ppu.window_control_0.reg
            win_in |= self.ppu.window_control_1.reg << 8
            return win_in
        elif address == IOAddress.REG_WINOUT:
            win_out = self.ppu.window_control_out.reg
            win_out |= self.ppu.window_control_obj.reg << 8
            return win_out
        elif address == IOAddress.REG_BLDCNT:
            return self.ppu.blend_control.reg
        elif address == IOAddress.REG_BLDALPHA:
            return self.ppu.blend_alpha.reg
        elif address == IOAddress.REG_SOUNDBIAS:
            return self.reg_soundbias
        elif address == IOAddress.REG_DMA0CNT_H:
            return self.dma_controller.channel_0.get_control_reg()
        elif address == IOAddress.REG_DMA1CNT_H:
            return self.dma_controller.channel_1.get_control_reg()
        elif address == IOAddress.REG_DMA2CNT_H:
            return self.dma_controller.channel_2.get_control_reg()
        elif address == IOAddress.REG_DMA3CNT_H:
            return self.dma_controller.channel_3.get_control_reg()
        elif address == IOAddress.REG_TM0CNT_L:
            return self.timers.timer_0.get_counter()
        elif address == IOAddress.REG_TM0CNT_H:
            return self.timers.timer_0.get_control_reg()
        elif address == IOAddress.REG_TM1CNT_L:
            return self.timers.timer_1.get_counter()
        elif address == IOAddress.REG_TM1CNT_H:
            return self.timers.timer_1.get_control_reg()
        elif address == IOAddress.REG_TM2CNT_L:
            return self.timers.timer_2.get_counter()
        elif address == IOAddress.REG_TM2CNT_H:
            return self.timers.timer_2.get_control_reg()
        elif address == IOAddress.REG_TM3CNT_L:
            return self.timers.timer_3.get_counter()
        elif address == IOAddress.REG_TM3CNT_H:
            return self.timers.timer_3.get_control_reg()
        elif address == IOAddress.REG_KEYINPUT:
            return self.keypad.key_input
        elif address == IOAddress.REG_KEYCNT:
            return self.keypad.key_control.reg
        elif address == IOAddress.REG_IE:
            return self.interrupt_controller.get_interrupt_enable()
        elif address == IOAddress.REG_IF:
            return self.interrupt_controller.get_interrupt_flags()
        elif address == IOAddress.REG_WAITCNT:
            return self.memory.wait_control.reg
        elif address == IOAddress.REG_IME:
            return self.interrupt_controller.get_interrupt_master_enable()
        else:
            return 0

    def read_8(self, address: int) -> int:
        value = self.read_16(address & ~1)
        return value >> (address & 1) * 8 & 255

    def write_32(self, address: int, value: int):
        self.write_16(address, value & 65535)
        self.write_16(address + 2, value >> 16 & 65535)

    def write_16(self, address: int, value: int):
        if address == IOAddress.REG_DISPCNT:
            self.ppu.display_control.reg = value
        elif address == IOAddress.REG_DISPSTAT:
            self.ppu.display_status.reg = value
        elif address == IOAddress.REG_VCOUNT:
            self.ppu.vcount = value
        elif address == IOAddress.REG_BG0CNT:
            self.ppu.bg_control_0.reg = value
        elif address == IOAddress.REG_BG1CNT:
            self.ppu.bg_control_1.reg = value
        elif address == IOAddress.REG_BG2CNT:
            self.ppu.bg_control_2.reg = value
        elif address == IOAddress.REG_BG3CNT:
            self.ppu.bg_control_3.reg = value
        elif address == IOAddress.REG_BG0HOFS:
            self.ppu.bg_offset_h[0] = value & 1023
        elif address == IOAddress.REG_BG0VOFS:
            self.ppu.bg_offset_v[0] = value & 1023
        elif address == IOAddress.REG_BG1HOFS:
            self.ppu.bg_offset_h[1] = value & 1023
        elif address == IOAddress.REG_BG1VOFS:
            self.ppu.bg_offset_v[1] = value & 1023
        elif address == IOAddress.REG_BG2HOFS:
            self.ppu.bg_offset_h[2] = value & 1023
        elif address == IOAddress.REG_BG2VOFS:
            self.ppu.bg_offset_v[2] = value & 1023
        elif address == IOAddress.REG_BG3HOFS:
            self.ppu.bg_offset_h[3] = value & 1023
        elif address == IOAddress.REG_BG3VOFS:
            self.ppu.bg_offset_v[3] = value & 1023
        elif address == IOAddress.REG_WIN0H:
            self.ppu.window_h_min[0] = value >> 8 & 255
            self.ppu.window_h_max[0] = value & 255
        elif address == IOAddress.REG_WIN1H:
            self.ppu.window_h_min[1] = value >> 8 & 255
            self.ppu.window_h_max[1] = value & 255
        elif address == IOAddress.REG_WIN0V:
            self.ppu.window_v_min[0] = value >> 8 & 255
            self.ppu.window_v_max[0] = value & 255
        elif address == IOAddress.REG_WIN1V:
            self.ppu.window_v_min[1] = value >> 8 & 255
            self.ppu.window_v_max[1] = value & 255
        elif address == IOAddress.REG_WININ:
            self.ppu.window_control_0.reg = value & 63
            self.ppu.window_control_1.reg = value >> 8 & 63
        elif address == IOAddress.REG_WINOUT:
            self.ppu.window_control_out.reg = value & 63
            self.ppu.window_control_obj.reg = value >> 8 & 63
        elif address == IOAddress.REG_BLDCNT:
            self.ppu.blend_control.reg = value
        elif address == IOAddress.REG_BLDALPHA:
            self.ppu.blend_alpha.reg = value
        elif address == IOAddress.REG_BLDY:
            self.ppu.blend_brightness.reg = value
        elif address == IOAddress.REG_SOUNDBIAS:
            self.reg_soundbias = value
        elif address == IOAddress.REG_DMA0SAD_L:
            mask = 4294901760
            self.dma_controller.channel_0.set_src_address(self.dma_controller.channel_0.get_src_address() & mask)
            self.dma_controller.channel_0.set_src_address(self.dma_controller.channel_0.get_src_address() | value)
        elif address == IOAddress.REG_DMA0SAD_H:
            mask = 65535
            self.dma_controller.channel_0.set_src_address(self.dma_controller.channel_0.get_src_address() & mask)
            self.dma_controller.channel_0.set_src_address(self.dma_controller.channel_0.get_src_address() | value << 16)
        elif address == IOAddress.REG_DMA0DAD_L:
            mask = 4294901760
            self.dma_controller.channel_0.set_dst_address(self.dma_controller.channel_0.get_dst_address() & mask)
            self.dma_controller.channel_0.set_dst_address(self.dma_controller.channel_0.get_dst_address() | value)
        elif address == IOAddress.REG_DMA0DAD_H:
            mask = 65535
            self.dma_controller.channel_0.set_dst_address(self.dma_controller.channel_0.get_dst_address() & mask)
            self.dma_controller.channel_0.set_dst_address(self.dma_controller.channel_0.get_dst_address() | value << 16)
        elif address == IOAddress.REG_DMA1SAD_L:
            mask = 4294901760
            self.dma_controller.channel_1.set_src_address(self.dma_controller.channel_1.get_src_address() & mask)
            self.dma_controller.channel_1.set_src_address(self.dma_controller.channel_1.get_src_address() | value)
        elif address == IOAddress.REG_DMA1SAD_H:
            mask = 65535
            self.dma_controller.channel_1.set_src_address(self.dma_controller.channel_1.get_src_address() & mask)
            self.dma_controller.channel_1.set_src_address(self.dma_controller.channel_1.get_src_address() | value << 16)
        elif address == IOAddress.REG_DMA1DAD_L:
            mask = 4294901760
            self.dma_controller.channel_1.set_dst_address(self.dma_controller.channel_1.get_dst_address() & mask)
            self.dma_controller.channel_1.set_dst_address(self.dma_controller.channel_1.get_dst_address() | value)
        elif address == IOAddress.REG_DMA1DAD_H:
            mask = 65535
            self.dma_controller.channel_1.set_dst_address(self.dma_controller.channel_1.get_dst_address() & mask)
            self.dma_controller.channel_1.set_dst_address(self.dma_controller.channel_1.get_dst_address() | value << 16)
        elif address == IOAddress.REG_DMA2SAD_L:
            mask = 4294901760
            self.dma_controller.channel_2.set_src_address(self.dma_controller.channel_2.get_src_address() & mask)
            self.dma_controller.channel_2.set_src_address(self.dma_controller.channel_2.get_src_address() | value)
        elif address == IOAddress.REG_DMA2SAD_H:
            mask = 65535
            self.dma_controller.channel_2.set_src_address(self.dma_controller.channel_2.get_src_address() & mask)
            self.dma_controller.channel_2.set_src_address(self.dma_controller.channel_2.get_src_address() | value << 16)
        elif address == IOAddress.REG_DMA2DAD_L:
            mask = 4294901760
            self.dma_controller.channel_2.set_dst_address(self.dma_controller.channel_2.get_dst_address() & mask)
            self.dma_controller.channel_2.set_dst_address(self.dma_controller.channel_2.get_dst_address() | value)
        elif address == IOAddress.REG_DMA2DAD_H:
            mask = 65535
            self.dma_controller.channel_2.set_dst_address(self.dma_controller.channel_2.get_dst_address() & mask)
            self.dma_controller.channel_2.set_dst_address(self.dma_controller.channel_2.get_dst_address() | value << 16)
        elif address == IOAddress.REG_DMA3SAD_L:
            mask = 4294901760
            self.dma_controller.channel_3.set_src_address(self.dma_controller.channel_3.get_src_address() & mask)
            self.dma_controller.channel_3.set_src_address(self.dma_controller.channel_3.get_src_address() | value)
        elif address == IOAddress.REG_DMA3SAD_H:
            mask = 65535
            self.dma_controller.channel_3.set_src_address(self.dma_controller.channel_3.get_src_address() & mask)
            self.dma_controller.channel_3.set_src_address(self.dma_controller.channel_3.get_src_address() | value << 16)
        elif address == IOAddress.REG_DMA3DAD_L:
            mask = 4294901760
            self.dma_controller.channel_3.set_dst_address(self.dma_controller.channel_3.get_dst_address() & mask)
            self.dma_controller.channel_3.set_dst_address(self.dma_controller.channel_3.get_dst_address() | value)
        elif address == IOAddress.REG_DMA3DAD_H:
            mask = 65535
            self.dma_controller.channel_3.set_dst_address(self.dma_controller.channel_3.get_dst_address() & mask)
            self.dma_controller.channel_3.set_dst_address(self.dma_controller.channel_3.get_dst_address() | value << 16)
        elif address == IOAddress.REG_DMA0CNT_L:
            self.dma_controller.channel_0.set_count(value)
        elif address == IOAddress.REG_DMA0CNT_H:
            self.dma_controller.channel_0.set_control_reg(value)
        elif address == IOAddress.REG_DMA1CNT_L:
            self.dma_controller.channel_1.set_count(value)
        elif address == IOAddress.REG_DMA1CNT_H:
            self.dma_controller.channel_1.set_control_reg(value)
        elif address == IOAddress.REG_DMA2CNT_L:
            self.dma_controller.channel_2.set_count(value)
        elif address == IOAddress.REG_DMA2CNT_H:
            self.dma_controller.channel_2.set_control_reg(value)
        elif address == IOAddress.REG_DMA3CNT_L:
            self.dma_controller.channel_3.set_count(value)
        elif address == IOAddress.REG_DMA3CNT_H:
            self.dma_controller.channel_3.set_control_reg(value)
        elif address == IOAddress.REG_TM0CNT_L:
            self.timers.timer_0.set_counter(value)
        elif address == IOAddress.REG_TM0CNT_H:
            self.timers.timer_0.set_control_reg(value)
        elif address == IOAddress.REG_TM1CNT_L:
            self.timers.timer_1.set_counter(value)
        elif address == IOAddress.REG_TM1CNT_H:
            self.timers.timer_1.set_control_reg(value)
        elif address == IOAddress.REG_TM2CNT_L:
            self.timers.timer_2.set_counter(value)
        elif address == IOAddress.REG_TM2CNT_H:
            self.timers.timer_2.set_control_reg(value)
        elif address == IOAddress.REG_TM3CNT_L:
            self.timers.timer_3.set_counter(value)
        elif address == IOAddress.REG_TM3CNT_H:
            self.timers.timer_3.set_control_reg(value)
        elif address == IOAddress.REG_KEYCNT:
            self.keypad.key_control.reg = value
        elif address == IOAddress.REG_IE:
            self.interrupt_controller.set_interrupt_enable(value)
        elif address == IOAddress.REG_IF:
            self.interrupt_controller.set_interrupt_flags(value)
        elif address == IOAddress.REG_WAITCNT:
            self.memory.wait_control.reg = value
            self.memory.update_waitstates()
        elif address == IOAddress.REG_IME:
            self.interrupt_controller.set_interrupt_master_enable(value)
        elif address == IOAddress.REG_HALTCNT:
            self.interrupt_controller.power_down_mode = get_bit(value, 15) + 1

    def write_8(self, address: int, value: int):
        aligned_address = address & ~1
        old_value = self.read_16(aligned_address)
        new_value = old_value & 255 | value << 8 if get_bit(address, 0) else old_value & 65280 | value
        self.write_16(aligned_address, new_value)