package razorvine.ksim65.components

import razorvine.ksim65.Cpu6502

/**
 * A programmable timer. Causes an IRQ or NMI at specified 24-bits clock cycle intervals.
 *
 * reg.   value
 * ----   --------------
 *  00    control register  bit 0=enable  bit 1=nmi (instead of irq)
 *  01    24 bits interval value, bits 0-7 (lo)
 *  02    24 bits interval value, bits 8-15 (mid)
 *  03    24 bits interval value, bits 16-23  (hi)
 */
class Timer(startAddress: Address, endAddress: Address, val cpu: Cpu6502) : MemMappedComponent(startAddress, endAddress) {
    private var counter: Int = 0
    private var interval: Int = 0
    private var nmi = false
    private var enabled = false
        set(value) {
            if (value && !field) {
                // timer is set to enabled (was disabled) - reset the counter
                counter = 0
            }
            field = value
        }

    init {
        require(endAddress-startAddress+1 == 4) { "timer needs exactly 4 memory bytes" }
    }

    override fun clock() {
        if (enabled && interval > 0) {
            counter++
            if (counter == interval) {
                if (nmi) cpu.nmiAsserted = true
                else cpu.irqAsserted = true
                counter = 0
            }
        }
    }

    override fun reset() {
        counter = 0
        interval = 0
        enabled = false
        nmi = false
    }

    override operator fun get(offset: Int): UByte {
        return when (offset) {
            0x00 -> {
                var data = 0
                if (enabled) data = data or 0b00000001
                if (nmi) data = data or 0b00000010
                data.toShort()
            }
            0x01 -> (counter and 0xff).toShort()
            0x02 -> ((counter ushr 8) and 0xff).toShort()
            0x03 -> ((counter ushr 16) and 0xff).toShort()
            else -> 0xff
        }
    }

    override operator fun set(offset: Int, data: UByte) {
        when (offset) {
            0x00 -> {
                val i = data.toInt()
                enabled = (i and 0b00000001) != 0
                nmi = (i and 0b00000010) != 0
            }
            0x01 -> {
                interval = (interval and 0x7fffff00) or data.toInt()
            }
            0x02 -> {
                interval = (interval and 0x7fff00ff) or (data.toInt() shl 8)
            }
            0x03 -> {
                interval = (interval and 0x7f00ffff) or (data.toInt() shl 16)
            }
        }
    }
}
