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

import memory
import time
import timers
import bcd

A_TIMER_A_LOW = 4
A_TIMER_A_HIGH = 5
A_TIMER_B_LOW = 6
A_TIMER_B_HIGH = 7
A_TIME_OF_DAY_TOS = 0x08 # in 1/10 s. BCD.
A_TIME_OF_DAY_SEC = 0x09 # seconds. BCD.
A_TIME_OF_DAY_MIN = 0x0A # minutes, BCD.
A_TIME_OF_DAY_HOUR = 0x0B # hours, BCD (& AM/PM).
A_TIMER_A_CONTROL = 0x0E
A_TIMER_B_CONTROL = 0x0F
A_INTERRUPT_CONTROL_STATUS = 0x0D
A_SERIAL_SHIFT = 0xC

# TOD clock signal = 60 Hz on C64

class CIA(memory.Memory): # CIA 6526
    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.status = 0
        t = time.time()
        self.gregorian_time = time.localtime(t) # latching as per docs!
        self.A_data_direction = 0xFF # echo chamber, not useful for anything else here.
        self.B_data_direction = 0xFF # echo chamber, not useful for anything else here.
        #self.A_data = 0xFF # FIXME initial value
        #self.B_data = 0xFF # FIXME initial value
    def read_memory(self, address, size = 1):
        assert size == 1, "CIA.read_memory size is 1"
        address = address & 0xF
        if address == 0:
            # this WON'T be used by mmu2 since it has its own weird special case for it (since the CIA 6526 mentions something to the effect).
            self.peripheral.read_memory(address, size) # make sure the IEC (or other device) get the request so it can timeout without needing an extra thread.
            return(0xFF) # self.A_data) # TODO optional | (self.peripheral.read_memory(address, size) & (self.A_data_direction ^ 0xFF)))
        elif address == 1:
            # this WON'T be used by mmu2 since it has its own weird special case for it (since the CIA 6526 mentions something to the effect).
            return(0xFF) # self.B_data) # TODO optional | (self.peripheral.read_memory(address, size) & (self.B_data_direction ^ 0xFF)))
        elif address == 2:
            return(self.A_data_direction)
        elif address == 3:
            return(self.B_data_direction)
        elif address == A_TIMER_A_CONTROL:
            return self.timer_A.get_control_mask()
        elif address == A_TIMER_B_CONTROL:
            return self.timer_B.get_control_mask()
        elif address == A_INTERRUPT_CONTROL_STATUS:
            return self.get_status()
        elif address == A_TIMER_A_LOW:
            return self.timer_A.value & 0xFF
        elif address == A_TIMER_A_HIGH:
            return (self.timer_A.value >> 8) & 0xFF
        elif address == A_TIMER_B_LOW:
            return self.timer_B.value & 0xFF
        elif address == A_TIMER_B_HIGH:
            return (self.timer_B.value >> 8) & 0xFF
        elif address >= A_TIME_OF_DAY_TOS and address < 0xC:
            if address == A_TIME_OF_DAY_SEC:
                return bcd.to_BCD(self.gregorian_time.tm_sec)
            elif address == A_TIME_OF_DAY_MIN:
                return bcd.to_BCD(self.gregorian_time.tm_min)
            elif address == A_TIME_OF_DAY_HOUR:
                t = time.time()
                self.gregorian_time = time.localtime(t) # latching as per docs!
                return bcd.to_BCD(self.gregorian_time.tm_hour)
            elif address == A_TIME_OF_DAY_TOS:
                pass # self.gregorian_time = None
        elif address == A_SERIAL_SHIFT:
            return 0xFF
        else:
            print(hex(address))
            assert False, "CIA address is known"
    def get_status(self):
        result = self.status
        self.status = 0
        return(result)
    def add_status(self, value):
        self.status |= value
        if self.status & self.interrupt_mask:
            # TODO cause interrupt, eventually
            self.status |= 128 # interrupt...
            print("yes, we had an interrupt")
    def mangle_interrupt_control(self, value):
        # bit 0=Timer A, bit 1=Timer B, bit 2=Alarm
        mod = value & 127
        if value & 128: # set stuff
            self.interrupt_mask = self.interrupt_mask | mod
        else: #clear stuff
            self.interrupt_mask = self.interrupt_mask &~ mod
    def write_memory(self, address, value, size):
        #print("CIA#1 $%X := %r" % (address, value))
        address = address & 0xF
        if address < 2:
            self.peripheral.write_memory(address, value, size)
        elif address == 2:
            self.A_data_direction = value
            self.peripheral.write_memory(address, value, size)
        elif address == 3:
            self.B_data_direction = value
            self.peripheral.write_memory(address, value, size)
        elif address == A_TIMER_A_LOW:
            self.timer_A.set_start_value((self.timer_A.start_value & 0xFF00) | value)
        elif address == A_TIMER_A_HIGH:
            self.timer_A.set_start_value((self.timer_A.start_value & 0x00FF) | (value << 8))
        elif address == A_TIMER_B_LOW:
            self.timer_B.set_start_value((self.timer_B.start_value & 0xFF00) | value)
        elif address == A_TIMER_B_HIGH:
            self.timer_B.set_start_value((self.timer_B.start_value & 0x00FF) | (value << 8))
        elif address == A_TIMER_A_CONTROL:
            self.timer_A.set_control_mask(value)
        elif address == A_TIMER_B_CONTROL:
            self.timer_B.set_control_mask(value)
        elif address == A_INTERRUPT_CONTROL_STATUS:
            self.mangle_interrupt_control(value)
        # TODO setting time of day (process probably has no permission to do that anyway)
        # TODO setting alarm time of day (!)
    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 twiddle_portB_bit7(self):
        pass
        #p = self.peripheral
        #v = p.read_memory(1, 1)
        #if self.timer_A.B_noninvert_underflow_B:
        #    p.write_memory(1, v|128, 1)
        #else:
        #    p.write_memory(1, v^128, 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(1)
            # TODO what if both timers twiddle the flag at the same time ?
            if self.timer_A.B_indicate_underflow_B:
                self.twiddle_portB_bit7()
            if self.interrupt_mask & 1: # Timer A
                B_cause_interrupt = True
        if self.timer_B.B_active and t >= self.timer_B.firing_time:
            B_fired_any = True
            self.timer_B.fire()
            self.add_status(2)
            # TODO what if both timers twiddle the flag at the same time ?
            if self.timer_A.B_indicate_underflow_B:
                self.twiddle_portB_bit7()
            if self.interrupt_mask & 2: # Timer B
                B_cause_interrupt = True
        return(B_cause_interrupt)

"""
status: TODO:
            # FIXME add bit 2: was alarm time reached?
            # FIXME add bit 3: did the serial shift register finish a byte?
            # FIXME add bit 4: was a signal sent on the flag line?
"""
        