class DMAControlRegister:

    def __init__(self):
        self.reg = 0

    def get_dst_address_adjustment(self) -> DMAAddressAdjustment | int:
        return get_bits(self.reg, 5, 6)

    def get_src_address_adjustment(self) -> DMAAddressAdjustment | int:
        return get_bits(self.reg, 7, 8)

    def get_repeat(self) -> bint:
        return get_bit(self.reg, 9)

    def get_transfer_size(self) -> DMATransferSize | int:
        return get_bit(self.reg, 10)

    def get_start_timing(self) -> DMAStartTiming | int:
        return get_bits(self.reg, 12, 13)

    def get_irq_when_done(self) -> bint:
        return get_bit(self.reg, 14)

    def get_transfer_enabled(self) -> bint:
        return get_bit(self.reg, 15)

    def set_transfer_enabled(self, enable: bint):
        self.reg = set_bit(self.reg, 15, enable)

class DMAChannel:

    def __init__(self, channel_id: int, scheduler: Scheduler, memory: Memory):
        self.SRC_MASK = [134217727, 268435455, 268435455, 268435455]
        self.DST_MASK = [134217727, 134217727, 134217727, 268435455]
        self.COUNT_MASK = [16383, 16383, 16383, 65535]
        self.INTERRUPT = [Interrupt.DMA_0, Interrupt.DMA_1, Interrupt.DMA_2, Interrupt.DMA_3]
        self.TRANSFER_DELAY = 2
        self.channel_id = channel_id
        self.scheduler = scheduler
        self.memory = memory
        self.fifo = False
        self.pending = False
        self._control_reg = DMAControlRegister()
        self._count = 0
        self._src = 0
        self._dst = 0
        self._internal_count = 0
        self._internal_src = 0
        self._internal_dst = 0
        self._event = None

    def get_control_reg(self) -> int:
        return self._control_reg.reg

    def set_control_reg(self, value: int) -> None:
        old_enable = self._control_reg.get_transfer_enabled()
        self._control_reg.reg = value
        if not old_enable and self._control_reg.get_transfer_enabled():
            self.fifo = self._control_reg.get_start_timing() == DMAStartTiming.SPECIAL and (self.channel_id == 1 or self.channel_id == 2) and (self._dst == IOAddress.REG_FIFO_A or self._dst == IOAddress.REG_FIFO_B)
            self._internal_src = self._src
            self._internal_dst = self._dst
            self._internal_count = self._count
            if self._internal_count == 0:
                self._internal_count = self.COUNT_MASK[self.channel_id] + 1
            if self._control_reg.get_start_timing() == DMAStartTiming.DMAStartTiming_IMMEDIATELY:
                self._event = self.scheduler.schedule(self.activate, self.TRANSFER_DELAY, EventTrigger.IMMEDIATELY)
            elif self._control_reg.get_start_timing() == DMAStartTiming.DMAStartTiming_VBLANK:
                self._event = self.scheduler.schedule(self.activate, self.TRANSFER_DELAY, EventTrigger.EventTrigger_VBLANK)
            elif self._control_reg.get_start_timing() == DMAStartTiming.DMAStartTiming_HBLANK:
                self._event = self.scheduler.schedule(self.activate, self.TRANSFER_DELAY, EventTrigger.EventTrigger_HBLANK)
        elif old_enable and (not self._control_reg.get_transfer_enabled()):
            if self._event:
                self._event.cancelled = False
                self._event = None
            self.pending = False

    def get_count(self) -> int:
        return self._count

    def set_count(self, value: int) -> None:
        self._count = value & self.COUNT_MASK[self.channel_id]

    def get_src_address(self) -> int:
        return self._src

    def set_src_address(self, value: int):
        self._src = value & self.SRC_MASK[self.channel_id]

    def get_dst_address(self) -> int:
        return self._dst

    def set_dst_address(self, value: int):
        self._dst = value & self.DST_MASK[self.channel_id]

    def transfer(self):
        if not self.pending:
            return
        transfer_size = self._control_reg.get_transfer_size()
        transfer_size_bytes = 4 if transfer_size == DMATransferSize.WORD else 2
        address_alignment = ~3 if transfer_size == DMATransferSize.WORD else ~1
        self._internal_src &= address_alignment
        self._internal_dst &= address_alignment
        src_adj = self._control_reg.get_src_address_adjustment()
        if src_adj == DMAAddressAdjustment.INCREMENT:
            src_step = transfer_size_bytes
        elif src_adj == DMAAddressAdjustment.DECREMENT:
            src_step = -transfer_size_bytes
        elif src_adj == DMAAddressAdjustment.LEAVE_UNCHANGED:
            src_step = 0
        dst_adj = self._control_reg.get_dst_address_adjustment()
        if self.fifo:
            dst_step = 0
        elif dst_adj == DMAAddressAdjustment.INCREMENT:
            dst_step = transfer_size_bytes
        elif dst_adj == DMAAddressAdjustment.DECREMENT:
            dst_step = -transfer_size_bytes
        elif dst_adj == DMAAddressAdjustment.LEAVE_UNCHANGED:
            dst_step = 0
        elif dst_adj == DMAAddressAdjustment.INCREMENT_RELOAD:
            dst_step = transfer_size_bytes
        access = MemoryAccess.NON_SEQUENTIAL
        for _ in range(self._internal_count):
            if transfer_size == DMATransferSize.WORD:
                value = self.memory.read_32(self._internal_src, access)
                self.memory.write_32(self._internal_dst, value, access)
            else:
                value = self.memory.read_16(self._internal_src, access)
                self.memory.write_16(self._internal_dst, value, access)
            self._internal_src += src_step
            self._internal_dst += dst_step
            access = MemoryAccess.SEQUENTIAL
        if self._control_reg.get_irq_when_done():
            self.memory.io.interrupt_controller.signal(self.INTERRUPT[self.channel_id])
        self.pending = False
        if self._control_reg.get_repeat():
            if dst_adj == DMAAddressAdjustment.INCREMENT_RELOAD:
                self._internal_dst = self._dst
            if self._control_reg.get_start_timing() == DMAStartTiming.DMAStartTiming_VBLANK:
                self._event = self.scheduler.schedule(self.activate, self.TRANSFER_DELAY, EventTrigger.EventTrigger_VBLANK)
            elif self._control_reg.get_start_timing() == DMAStartTiming.DMAStartTiming_HBLANK:
                self._event = self.scheduler.schedule(self.activate, self.TRANSFER_DELAY, EventTrigger.EventTrigger_HBLANK)
        else:
            self._control_reg.set_transfer_enabled(False)

    def activate(self):
        if self._control_reg.get_transfer_enabled():
            self.pending = True

class DMAController:

    def __init__(self, scheduler: Scheduler, memory: Memory):
        self.channel_0 = DMAChannel(0, scheduler, memory)
        self.channel_1 = DMAChannel(1, scheduler, memory)
        self.channel_2 = DMAChannel(2, scheduler, memory)
        self.channel_3 = DMAChannel(3, scheduler, memory)

    def get_active(self) -> bool:
        return self.channel_0.pending or self.channel_1.pending or self.channel_2.pending or self.channel_3.pending

    def perform_transfers(self):
        self.channel_0.transfer()
        self.channel_1.transfer()
        self.channel_2.transfer()
        self.channel_3.transfer()