#!/usr/bin/env python
# I, Danny Milosavljevic, hereby place this file into the public domain.
# This is a more bare-metal version of iec2
import sys
import time
import select
from chips.cpu import BRKHandler
from chips.symbols import S_X

"""non-1541 lines:
        bit 3 = ATN, bit 4 = CLOCK, bit 5 = DATA
"""
M_ATN = 2**3
M_CLOCK = 2**4
M_DATA = 2**5

""" represents one bus member """
class IECMember(BRKHandler): # since multiple inheritance isn't supported in Shedskin, have to do it like this...
    def __init__(self, IECBus, bus_address):
        self.lines = 0
        self.bus_address = bus_address
        self.IECBus = IECBus
    def read(self):
        pass
    def get_reader_fileno(self):
        return(-1)
    def set_lines(self, value):
        self.lines = value
        self.IECBus.emit_lines_changed()
    def write(self, lines):
        # for proxies: called whenever global lines changed. 
        # Note that probably self.lines != lines
        # self.lines is the wish by this device.
        # lines is the real (global) value currently on the lines.
        pass
    def get_child_stdout_fileno(self):
        return(-1)

empty_il = [42]
del empty_il[0]
        
""" represents the bus, that is, the wires """
class IECBus(object):
    def __init__(self):
        global BUS
        BUS = self
        self.devices = []
        self.lines = 0
        #self.computer_device = ComputerDevice(self, 30)
        #self.B_EOI = False
        #self.clock_fall_time = 0
        #self.add_device(self.computer_device)
    def get_lines(self):
        result = 0
        for device in self.devices:
            result |= device.lines
        return(result)
    def poke(self):
        """ If there's something to read, read and handle it. Otherwise return (without blocking). """
        print "iec3: poking..."
        FDs = []
        for device in self.devices:
            FD = device.get_reader_fileno()
            if FD != -1:
              FDs.append(FD)
        rFDs, wFDs, xFDs = select.select(FDs, empty_il, FDs, 0)
        for device in self.devices:
            FD = device.get_reader_fileno()
            if FD != -1 and FD in rFDs:
                print "iec3: READING!"
                device.read()
        print "iec3: done"
    def emit_lines_changed(self):
        lines = self.get_lines()
        if lines == self.lines:
            return
        self.lines = lines
        for device in self.devices:
            device.write(lines)
    def add_device(self, device):
        self.devices.append(device)
    def get_device(self, address):
        for device in self.devices:
            if device.bus_address == address:
                return(device)
        return(None)
        
""" the actual computer side of the IEC """
class ComputerDevice(IECMember):
    def __init__(self, IECBus, bus_address, CPU, base_address):
        IECMember.__init__(self, IECBus, bus_address)
        BRKHandler.__init__(self, CPU)
        #self.CPU = CPU
        self.MMU = CPU.MMU
        self.base_address = base_address
        #print("IEC: setting control mask to %r" % value)
        self.beginning_time = time.time()
    def update_IO_memory(self, lines):
        MMU = self.MMU
        lines = lines & 0xFF
        # TODO why are there two copies of DATA and CLOCK to begin with?
        if lines & M_CLOCK:
          lines = lines &~ 64
        else:
          lines |= 64
        if lines & M_DATA:
          lines = lines &~ 128
        else:
          lines |= 128
        MMU.IO_overlay_write_byte(self.base_address, lines)
    def write(self, lines): # for proxies: called whenever global lines changed.
        self.update_IO_memory(lines)
    def set_control_mask(self, value):
        # bit 3 = ATN, bit 4 = CLOCK, bit 5 = DATA
        self.set_lines(value & 0xFF)
    def get_points(self):
        return([0xEEB4, 0xEEB6])
    def poke(self):
        self.IECBus.poke()
    def call_hook(self, PC):
        CPU = self.CPU
        memory = self.CPU.MMU
        if PC == 0xEEB4: # beginning of wait 1ms
            self.beginning_time = time.time()
            # simulate LDX
            X = memory.read_memory(PC + 1)
            print "beginning of wait 1ms, X", X
            CPU.write_register(S_X, X) # latter usually $B8
        elif PC == 0xEEB6: # loop body of wait 1ms # this was a DEX instruction.
            # if timer not running, start timer, (1/$B8)*X ms
            # once timer fired, make sure to set X to the value 0.
            # otherwise don't decrement X (ugh).
            # TODO start timer, 
            t = time.time()
            print "looooop", t, self.beginning_time
            self.poke() # make sure to handle I/O in time! (note: see c64_main.py for the normal case)
            #if t - self.beginning_time <= CPU.read_register(S_X)*1E-3/0xB8:
            if t - self.beginning_time <= CPU.read_register(S_X)*1.0/0xB8:
              CPU.set_PC(0xEEB6) # loop ON the DEX instruction, magical :-)
            else:
              print "mini timer done"
              CPU.write_register(S_X, 0) # make sure to look sane from the outside.
              CPU.set_PC(0xEEB7) # just after the DEX instruction
            return(True)
        elif PC == 0xEE56:
            CPU.set_PC(0xEE76)
            return(True)
        elif PC == 0xEE07:
            CPU.RTS()
            #CPU.set_PC(0xEE0C)
            return(True)
        else:
            return(False)

""" the actual 1541 computer side of the IEC """
class D1541ComputerDevice(IECMember):
    def __init__(self, IECBus, bus_address, CPU, base_address):
        IECMember.__init__(self, IECBus, bus_address)
        BRKHandler.__init__(self, CPU)
        #self.CPU = CPU
        self.MMU = CPU.MMU
        self.base_address = base_address
        #print("IEC: setting control mask to %r" % value)
        self.beginning_time = time.time()
    def get_points(self):
        result = []
        result.append(42)
        del result[0]
        return(result) # 0xEE07, 0xEE56])
    def poke(self):
        self.IECBus.poke()
    def update_IO_memory(self, lines):
        MMU = self.MMU
        lines = lines & 0xFF
        # VIA bits are:
        #   bit 0: DATA IN
        #   bit 1: DATA (very unusual!); should have been 5.
        #   bit 2: CLOCK IN (1=held down, 0=released high)
        #   bit 3: CLOCK (very unusual!); should have been 4.
        #   bit 4: ACK ATN ???
        #   bit 7: ATN IN (clear = gone)
        # map these from VIA to the usual:
        #   bit 3 = ATN, bit 4 = CLOCK, bit 5 = DATA
        result = 0
        if lines & M_ATN:
            result |= 128 # ATN IN
        if lines & M_CLOCK:
            result |= 4 # CLOCK IN
        if lines & M_DATA:
            result |= 1 # DATA IN
        # TODO other bits
        MMU.IO_overlay_write_byte(self.base_address, result)
    def write(self, lines): # for proxies: called whenever global lines changed.
        self.update_IO_memory(lines)
    def call_hook(self, PC):
        CPU = self.CPU
        memory = self.CPU.MMU
        if PC == 0xEC9B: # idle loop
            CPU.set_PC(0xEBFF)
            return(True)
        elif PC == 0xEAD9: # idle loop 1551
            CPU.set_PC(0xEABD)
            return(True)
        elif PC == 0xDAEE:
            CPU.set_PC(0xDAF6)
            return(True)
        else:
            return(False)
    def set_control_mask(self, value):
        # VIA bits are:
        #   bit 0: DATA IN
        #   bit 1: DATA (very unusual!); should have been 5.
        #   bit 2: CLOCK IN (1=held down, 0=released high)
        #   bit 3: CLOCK (very unusual!); should have been 4.
        #   bit 4: ACK ATN ???
        #   bit 7: ATN IN (clear = gone)
        # map these to the usual:
        #   bit 3 = ATN, bit 4 = CLOCK, bit 5 = DATA
        result = 0
        #if value & M_ATN:
        #    result |= 16
        if value & M_CLOCK:
            result |= 8
        if value & M_DATA:
            result |= 2
        # TODO the others
        self.set_lines(result)
