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

# These do not fire() on their own, the CIA drives them.

import time

def timeout_add(callback):
    #callback.fire()
    return(42)
def timeout_remove(ID):
    pass

frequency = 1.022727E6#Hz
# _value == duration / (1/frequency)
# _value == duration * frequency

""" In order to relax the host system timers, this will fake the current timer value. 
    However, the timer will fire eventually, whether its value is read or not. 
    Note that although this class has an attribute "B_interrupt_pending" it's NOT clear that it actually will generate an interrupt. See CIA for that. """
class Timer(object):
    def __init__(self):
        self.ID = 0
        self.B_had_underflow = False
        self.B_active = False
        self.B_indicate_underflow_B = False
        self.B_noninvert_underflow_B = False
        self.B_stop_upon_underflow = False
        self.B_load_start_value = False
        self.B_count_CNT = False # as opposed to system cycles.
        self.B_serial_out = False
        self.B_TOD = False # 0=60Hz, 1=50Hz
        self._value = 0
        self.start_value = 0
        self.firing_time = 0
        self.B_interrupt_pending = False
    def set_start_value(self, value):
        self.start_value = value
        if not self.B_active:
            self.load_start_value()
    def load_start_value(self):
        self._value = self.start_value
        self.firing_time = time.time() + self._value / frequency
    def had_underflow_P(self):
        return(self.B_had_underflow)
    def get_control_mask(self):
        # LOAD code: 16|8|1
        return (1 if self.B_active else 0) + \
               (2 if self.B_indicate_underflow_B else 0) + \
               (4 if self.B_noninvert_underflow_B else 0) + \
               (8 if self.B_stop_upon_underflow else 0) + \
               (16 if self.B_load_start_value else 0) + \
               (32 if self.B_count_CNT else 0) + \
               (64 if self.B_serial_out else 0) + \
               (128 if self.B_TOD else 0)
    def set_control_mask(self, value):
        B_load_start_value = (value & 16) != 0
        B_active = (value & 1) != 0
        self.B_indicate_underflow_B = (value & 2) != 0 # then in bit 6 of port B. Note that the normal value is hidden, the direction is ignored and it's basically one big hack.
        self.B_noninvert_underflow_B = (value & 4) != 0 # 1=toggle, 0=pulse for one cycle
        self.B_stop_upon_underflow = (value & 8) != 0
        self.B_count_CNT = (value & 32) != 0 # should be 0
        self.B_serial_out = (value & 64) != 0 # ($DC0C), not sure what that's doing here
        self.B_TOD = (value & 128) != 0 # 1=external clock source, 0=60 Hz
        assert not self.B_count_CNT, "CIA timer system cycle counting"
        if B_load_start_value:
            self.load_start_value()
        if self.B_active != B_active: # or B_load_start_value:
            if B_active:
                self.ID = timeout_add(self)
            else:
                timeout_remove(self.ID)
                self.ID = 0
            self.B_active = B_active
    def fire(self):
        print("Timer: fired.")
        self.B_had_underflow = True
        if True: # FIXME more detail
            self.B_interrupt_pending = True
        # TODO cause interrupt.
        if not self.B_stop_upon_underflow:
            self.reload()
            return(True)
        else:
            self.B_active = False
            self.load_start_value()
            # TODO timeout_remove
            return(False)
    def reload(self):
        self.load_start_value()
        self.B_active = True
    @property
    def value(self):
        if False: # self.B_active:
            t = time.time()
            if self.B_active and t >= self.firing_time:
                # FIXME update CIA port B, if needed
                self._value = 0
                print("Timer: UNDERFLOW")
                if not self.fire(): # this is just a "just to be safe" branch, don't rely on it.
                    timeout_remove(self.ID)
                    self.ID = 0
            else:
                duration = self.firing_time - t
                self._value = int(duration * frequency)
                print("Timer: %d" % (self._value, ))
        return(self._value)
