#!/usr/bin/env python2
# I, Danny Milosavljevic, hereby place this file into the public domain.

# used by floppy drive
# this must be able to set the CPU overflow flag (no, really).

import memory
import time
import timers

#A_ORB = 0
#A_ORA = 1
#A_DDRB = 2
#A_DDRA = 3
A_TIMER_1_LOW = 4 # counter/latch
A_TIMER_1_HIGH = 5 # loader
A_TIMER_1_START_LOW = 6
A_TIMER_1_START_HIGH = 7
A_TIMER_2_LOW = 8 # T2C_L # counter/latch
A_TIMER_2_HIGH = 9 # T2C_H # loader
A_INTERRUPT_CONTROL_STATUS = 0x0D

A_SHIFT = 10
A_AUX_CONTROL = 11
A_PER_CONTROL = 12
A_INTERRUPT_FLAGS = 13
A_INTERRUPT_ENABLERS = 14
A_ORA_HANDSHAKEIGNORING = 15 # no effect on handshake

"""
peripheral:
$0   port B serial bus
$1 port A
read $1 to ack ATN Interrupt.
$2 port B data direction, default $1A
$3 port A data direction, default $FF
"""

# TODO interrupt flag register: bits 6 to 0 are latches with can be cleared using their respective data access, respectively.
"""
interrupt flags:
    bit 0: active transition of the signal on CA2. Read or write [$1] to clear.
    bit 1: active transition of the signal on CA1. Read or write [$1] to clear.
    bit 2: completion of 8 shifts. Read or write the shift register.
    bit 3: active transition of the signal on CB2. Read or write [BOUT] to clear.
    bit 4: active transition of the signal on CB1. Read or write [BOUT] to clear.
    bit 5: timeout of timer 2. Read T2 LO ctr or write T2 high to clear.
    bit 6: timeout of timer 1. Read T1 LO ctr or write T1 high to clear.
peripheral control:
    bit 0: CA1 edge to watch. 0=high-to-low, 1=low-to-high
    bit 1..3: CA2 control:
        000 input, high-to-low edge to watch. Clearable.
        001 input, high-to-low edge to watch. NOT clearable usually. Special clearing by writing 1 to the interrupt flag register bit.
        010 input, low-to-high edge to watch. Clearable.
        011 input, low-to-high edge to watch. NOT clearable usually. Special clearing by writing 1 to the interrupt flag register bit.
        100 handshake: CA2 output low on a read or write of A output. Reset to high once CA1 transitions.
        101 pulse: on read or write of A output: CA2 output low, wait a cycle, CA2 output high.
        110 manual output: CA2 output is held low.
        111 manual output: CA2 output is held high.
    bit 4: CB1 control.
    bit 5..7: CB2 control:
        000 input, high-to-low edge to watch. Clearable.
        001 input, high-to-low edge to watch. NOT clearable usually. Special clearing by writing 1 to the interrupt flag register bit.
        010 input, low-to-high edge to watch. Clearable.
        011 input, low-to-high edge to watch. NOT clearable usually. Special clearing by writing 1 to the interrupt flag register bit.
        100 handshake out: CB2 output low on a WRITE of B output. Reset to high once CB1 transitions.
        101 pulse: on read or write of B output: CB2 output low, wait a cycle, CB2 output high.
        110 manual output: CB2 output is held low.
        111 manual output: CB2 output is held high.
auxiliary control:
    bit 0: PA latch enable.
    bit 1: PB latch enable.
    bit 2..4: shift register control.
    bit 5: T2 control. 1=count CNT pin pulses.
    bit 6..7: T1 control:
        00 oneshot without output to PB7.
        01 free-running without output to PB7.
        10 oneshot with output to PB7.
        11 free-running with output to PB7.
# TODO input latching (optional) (AUX control reg bit 0 for PA, bit 1 for PB)
# TODO A_data latch when the CA1 interrupt flag is set. OUTPUT data is not specifically transferred to the latches.
# TODO B_data latch also stores OUTPUT register contents.
"""
class VIA(memory.Memory): 
    def __init__(self, peripheral):
        memory.Memory.__init__(self)
        self.peripheral = peripheral
        self.B_can_write = True # in the instance because of ShedSkin
        self.B_active = True
        self.timer_A = timers.Timer()
        self.timer_B = timers.Timer()
        self.interrupt_mask = 0
        self.A_data_direction = 0xFF
        self.B_data_direction = 0x1A
        self.A_data = 0 # FIXME initial value
        self.B_data = 0 # FIXME initial value
        self.status = 0
        t = time.time()
    def read_memory(self, address, size = 1):
        assert size == 1, "VIA.read_memory size is 1"
        address = address & 0xF
        if address == 0:
            return(self.A_data)
        elif address == 1:
            return(self.B_data)
        elif address == 2:
            return(self.B_data_direction)
        elif address == 3:
            return(self.A_data_direction)
        elif address == A_AUX_CONTROL:
            return 64 if self.timer_A.get_control_mask() & 1 else 0 # FIXME # active or not
        elif address == A_INTERRUPT_FLAGS:
            return self.get_status()
        elif address == A_INTERRUPT_ENABLERS:
            return self.interrupt_mask
        elif address == A_TIMER_1_LOW: # TODO: WTF: Read low byte or write high byte to start timer or restart timer upon underflow.
            return self.timer_A.value & 0xFF
        elif address == A_TIMER_1_HIGH:
            return (self.timer_A.value >> 8) & 0xFF
        elif address == A_TIMER_1_START_LOW:
            return self.timer_A.start_value & 0xFF
        elif address == A_TIMER_1_START_HIGH:
            return (self.timer_A.start_value >> 8) & 0xFF
        elif address == A_TIMER_2_LOW: # TODO: WTF: Read low byte or write high byte to start timer or restart timer upon underflow.
            return self.timer_B.value & 0xFF
        elif address == A_TIMER_2_HIGH:
            return (self.timer_B.value >> 8) & 0xFF
        else:
            print(hex(address))
            assert False, "VIA address is known"
    def get_status(self):
        result = self.status
        if result != 0:
            print("yes, we had an interrupt")
            result |= (1<<7)
            # FIXME if ATN IN high, set bit 1 at $180D.
        # TODO serial, alarm, flag, ... 
        return(result)
    def add_status(self, value):
        self.status |= value
    def augment_interrupt_control(self, chg):
        # bit 7=fill bit (!!!)
        value = self.interrupt_mask
        B_set = (chg & 128) != 0
        for i in range(0, 7):
            if chg & (1 << i):
               if B_set:
                   value |= (1 << i)
               else:
                   value = value &~ (1 << i)
        B_enable_ATN_interrupts = value & 2
        # bit 0=Timer A, bit 1=Timer B, bit 2=Alarm
        # TODO
        self.interrupt_mask = value
    def write_memory(self, address, value, size):
        address = address & 0xF
        if address < 2:
            self.peripheral.write_memory(address, value, size)
        elif address == 2:
            self.B_data_direction = value
            self.peripheral.write_memory(address, value, size)
        elif address == 3:
            self.A_data_direction = value
            self.peripheral.write_memory(address, value, size)
        elif address == A_TIMER_1_START_LOW:
            self.timer_A.set_start_value((self.timer_A.start_value & 0xFF00) | value)
        elif address == A_TIMER_1_START_HIGH:
            self.timer_A.set_start_value((self.timer_A.start_value & 0x00FF) | (value << 8))
        elif address == A_TIMER_1_LOW or address == A_TIMER_1_HIGH:
            # FIXME what to do with value ?
            self.timer_A.reload()
        elif address == A_TIMER_2_LOW or address == A_TIMER_2_HIGH:
            # FIXME what to do with value ?
            self.timer_A.reload()
        elif address == A_AUX_CONTROL:
            self.timer_A.set_control_mask((16|1) if (value & 64) else 0) # start timer
        elif address == A_INTERRUPT_FLAGS: # client clears flags by setting value bit 1
            self.status = self.status &~ value
        elif address == A_INTERRUPT_ENABLERS:
            self.augment_interrupt_control(value)
    def dump_state(self):
        result = []
        for i in range(16):
            result.append(self.read_memory(i, 1))
        return(result)
    def restore_state(self, state):
        for i, v in enumerate(state):
            self.write_memory(i, v, 1)
    def update_timers(self): # returns whether to cause an interrupt. Also updates flags!
        B_cause_interrupt = False
        t = time.time()
        if self.timer_A.B_active and t >= self.timer_A.firing_time:
            B_fired_any = True
            self.timer_A.fire()
            self.add_status(64)
        if self.timer_B.B_active and t >= self.timer_B.firing_time:
            B_fired_any = True
            self.timer_B.fire()
            self.add_status(64)
        return(B_cause_interrupt)
        