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

import sys
import time
from chips.cpu import BRKHandler

# used by cpu in a really silly direct way, not a chip.

# high voltage = False condition
# low voltage = True condition
# lines can be pulled down to ground (i.e. True) by anyone.

"""
            RQS ACTS     sent     ACK
             .   .        .        .
  TalkerD 0|0|0|0|0    0|D|D|0  e 0|0
ListenerD 1|1|1|0|0 r  0|0|0|0  n 0|1
        D 1|1|1|0|0 e  0|D|D|0  d 0|1
  TalkerC ?|1|0|0|1 p  1|1|0|1  r 1|1
ListenerC ?|0|0|0|0 e  0|0|0|0  e 0|0
        C 1|1|0|0|1 a  1|1|0|1  p 1|1
  TalkerA 0|1|1|1|1 t  1|1|1|1  e 1|1
           ^   ^        ^   ^
          ATN CTS     put  removed
          set
RQS ... request to send received
CTS ... clear to send received
ACTS... acknowledge of clear to send received
put... bit was put on bus
sent...bit was marked sent by the talker
removed...bit was removed by the talker
ACK....byte(!!!!) was acknowledged by the listener

after a long time without traffic, timeout is reached and then everyone sets DATA to False and CLOCK to False.
"""
"""
[$DD00] layout:
76543210
|||||||+- VIC bank lo
||||||+-- VIC bank hi
|||||+--- RS232 TX, set to 1 if it doubt.
||||+---- ATN OUT
|||+----- CLOCK OUT
||+------ DATA OUT
|+------- CLOCK IN, inverted
+-------- DATA IN, inverted (!)
"""

class IECMember(object):
    def __init__(self, IEC, bus_address):
        self.B_clock = False
        #self.clock_fall_time = 0
        self.B_data = True
        self.B_ATN = False
        self.bus_address = bus_address
        self.IEC = IEC
        self.B_talker = False # effect not immediate
        self.B_switch_talker = False
        self.received_count = 0
        self.received_value = 0
    def process_received_byte(self, item, B_ATN, B_EOI):
        if B_ATN:
            if item == 0x40|self.bus_address and item < 0x50: # TALK
                self.become_talker()
            elif item == 0x5F: # UNTALK
                self.quit_talker()
            #print("received $%X while ATN was True and EOI was %r" % (item, B_EOI))
        else:
            #print("received $%X while ATN was False and EOI was %r" % (item, B_EOI))
            pass
    def fetch_value(self): # called when we are a talker only.
        return(-1)
    def mark_EOF(self):
        self.set_clock(False)
        self.set_data(False)
    def set_ATN(self, value):
        if self.B_ATN == value:
            return
        self.B_ATN = value
        print("IEC dev %d ATN changed to %r" % (self.bus_address, value))
        self.IEC.emit_ATN_changed()
    def set_data(self, value):
        if self.B_data == value:
            return
        self.B_data = value
        print("IEC dev %d DATA changed to %r" % (self.bus_address, value))
        self.IEC.emit_DATA_changed()
    def set_clock(self, value):
        if self.B_clock == value:
            return
        self.B_clock = value
        print("IEC dev %d CLOCK changed to %r" % (self.bus_address, value))
        self.IEC.emit_CLOCK_changed()
    def get_data(self):
        return(self.B_data)
    def get_clock(self):
        return(self.B_clock)
    def set_image_name(self, name, type): # dummy for C++
        pass
    def ATN_changed(self, value):
        if value:
            #print("IEC: ATN was raised, so getting ready to listen")
            self.set_clock(False)
            self.set_data(True) # ready to listen
        else:
            #print("IEC: ATN was lowered.")
            self.switch_talker()
    def release(self): # at the very end.
        self.received_count = 0
        self.received_value = 0
        print("IEC: released")
    def switch_talker(self):
        if self.B_switch_talker and self.B_talker and self.IEC.get_ATN() == False and self.IEC.get_CLOCK() == False:
            self.B_switch_talker = False
            print("IECMember: ready to talk.")
            self.set_data(False) # will be held down by computer
            self.set_clock(True)
            return True
        else:
           return False
    def CLOCK_changed(self, value):
        if value == False:
            if self.switch_talker(): 
                return
            if self.received_count == 0:
                print("IECMember: ready to listen.")
                self.set_data(False) # ready to listen.
            #self.clock_fall_time = time.time()
            if not self.IEC.get_DATA():
                self.received_value |= (1 << self.received_count)
            self.received_count += 1
            if self.received_count >= 9: # done
                r_value = self.received_value >> 1
                print("IEC: got a byte: $%X" % r_value)
                self.received_count = 0
                self.received_value = 0
                self.process_received_byte(r_value, self.IEC.get_ATN(), self.IEC.get_EOI())
                #for device in self.devices:
                self.set_data(True) # ACK
            elif self.received_count == 1: # just started
                print("IEC: just started")
                # lowest bit is always 1.
                pass
    def DATA_changed(self, value):
        #if self.B_talker and not self.B_switch_talker:
        #    if value: # very simple version since we fake reading anyway.
        #        self.set_clock(False)
        #    else:
        #        self.set_clock(True)
        pass
    def become_talker(self):
        #print("IEC device $%X: become talker." % self.bus_address)
        if self.B_talker:
           print("IEC device: duplicate request.")
           return
        self.B_talker = True # then data will be held by the computer and clock will drop.
        self.B_switch_talker = True
    def quit_talker(self):
        #print("IEC device $%X: quit talker." % self.bus_address)
        if not self.B_talker:
           print("IEC device: duplicate request.")
           return
        self.B_talker = False # then data will be held by the computer and clock will drop.
        self.B_switch_talker = True

class ComputerDevice(IECMember):
    def __init__(self, IEC, bus_address):
        IECMember.__init__(self, IEC, bus_address)
        self.B_talker = True
        self.B_data = False
    def ATN_changed(self, value):
        # done by the KERNAL: self.set_clock(True)
        # done by the KERNAL: self.set_data(False)
        pass
        # when it wants to talk, it releases the clock line to False again.
    def DATA_changed(self, value):
        pass
    def CLOCK_changed(self, value):
        pass
    def set_control_mask(self, value):
        #print("IEC: setting control mask to %r" % value)
        self.control_mask = value #& 0x3C
        computer = self.computer_device
        computer.set_ATN((value & 8) != 0)
        computer.set_data((value & 32) != 0) # verified
        computer.set_clock((value & 16) != 0) # verified
        # note: initial value set by kernel is 7.
SCALE = 1000.0
class IECBus(object):
    def __init__(self, MMU, base_address):
        global BUS
        BUS = self
        self.MMU = MMU
        self.base_address = base_address
        self.devices = []
        self.control_mask = 0
        self.B_CLOCK = False
        self.B_ATN = False
        self.B_DATA = False
        self.reset()
        self.B_EOI = False
        self.clock_fall_time = 0
    def fetch_value(self):
        for device in self.devices[1:]:
            if device.B_talker and not device.B_switch_talker:
                return(device.fetch_value())
        return(-1)
    def mark_EOF(self):
        for device in self.devices[1:]:
            if device.B_talker and not device.B_switch_talker:
                device.mark_EOF()
    def reset(self):
        pass
    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)
    def get_CLOCK(self):
        for device in self.devices:
            if device.B_clock:
                return(True)
        return(False)
    def get_DATA(self):
        for device in self.devices:
            if device.B_data:
                return(True)
        return(False)
    def get_ATN(self):
        for device in self.devices:
            if device.B_ATN:
                return(True)
        return(False)
    def emit_DATA_changed(self):
        #print("DATA flags are %r" % [device.B_data for device in self.devices])
        DATA = self.get_DATA()
        if self.B_DATA == DATA:
            return
        self.B_DATA = DATA
        #print("IEC: DATA changed to %r, computer DATA is %r" % (DATA, self.computer_device.B_data))
        for device in self.devices:
            device.DATA_changed(DATA)
        self.update_IO_memory()
    def emit_CLOCK_changed(self):
        #print("CLOCK flags are %r" % [device.B_clock for device in self.devices])
        CLOCK = self.get_CLOCK()
        if self.B_CLOCK == CLOCK:
            return
        self.B_CLOCK = CLOCK
        #print("IEC: CLOCK changed to %r, computer CLOCK is %r" % (CLOCK, self.computer_device.B_clock))
        for device in self.devices:
            device.CLOCK_changed(CLOCK)
        if not CLOCK:
            self.clock_fall_time = time.time()
        self.update_IO_memory()
    def get_EOI(self):
        return(self.B_EOI)
    def emit_ATN_changed(self):
        #print("ATN flags are %r" % [device.B_ATN for device in self.devices])
        ATN = self.get_ATN()
        if ATN == self.B_ATN:
            return
        self.B_ATN = ATN
        #print("IEC: ATN changed to %r, computer ATN is %r" % (ATN, self.computer_device.B_ATN))
        for device in self.devices:
            device.ATN_changed(ATN)
        self.update_IO_memory()
    def update_IO_memory(self):
        memory = self.MMU
	memory.IO_overlay_write_byte(self.base_address, self.get_control_mask())
    def release(self):
        for device in self.devices:
            device.release()
    def get_control_mask(self):
        if not self.B_CLOCK:
            t = time.time()
            if t > self.clock_fall_time and t - self.clock_fall_time >= 200E-6*SCALE:
                # timeout, i.e. EOI
                state = (t - self.clock_fall_time) < (200E-6 + 60E-6) * SCALE
                #print("EOI state %r" % state)
                # acknowledge on behalf of the devices (that isn't nice).
                for device in self.devices:
                    device.set_data(state)
        result = 0xFF
        if self.get_CLOCK():
            result &= ~64
        if self.get_DATA():
            result &= ~128
        # TODO ATN
        """return (self.control_mask & 4) | \
               (8 if self.get_ATN() else 0) | \
               (self.control_mask & 16) | \
               (self.control_mask & 32) | \
               (64 if self.get_CLOCK() else 0) | \
               (128 if self.get_DATA() else 0)"""
        return(result)
    def process_received_byte(self, value, B_ATN, B_EOI):
        print("IEC: received a byte $%X while ATN was %r and EOI was %r" % (value, B_ATN, B_EOI))
        for device in self.devices:
            device.process_received_byte(value, B_ATN, B_EOI)
    def poke(self):
        # make sure we timeout
        self.get_control_mask()

"""
listening:
1) set bit 3 of $DD00 (-ATN).
2) set bit 4 of $DD00 (CLK).
3) transmit device byte as usual.
4) transmit channel byte as usual.
5) clear bit 3 of $DD00 (-ATN).
6) send file name
"""
class IEC(BRKHandler):
  def __init__(self, CPU, BUS):
    self.BUS = BUS
    BRKHandler.__init__(self, CPU)
    self.computer_device = ComputerDevice(self, 30)
    self.BUS.add_device(self.computer_device)
  def poke(self):
    self.BUS.poke()
  def get_points(self):
    return([0xEE07, 0xEE56])
  def call_hook(self, PC):
    BUS = self.BUS
    CPU = self.CPU
    memory = self.CPU.MMU
    if PC == 0xEE56:
        result = BUS.fetch_value()
        memory.write_memory(0xA5, 0, 1) # remainder counter
        if result == -1: # EOI, one value to go.
            # as far as the guest knows, we are reading bytes just fine.
            # if there was an EOF, we fix up all the things the guest should have done (urgh).
            memory.write_memory(0x90, memory.read_memory(0x90) | 0x40, 1) # TODO just let the natural part at $EE47 handle this?
            print("IEC: marking EOF")
            BUS.mark_EOF()
            result = BUS.fetch_value()
        memory.write_memory(0xA4, result, 1)
        CPU.set_PC(0xEE76)
        return(True)
    elif PC == 0xEE07:
        # releases the bus after some timeout.
        # unfortunately there's no docs on how long the timeout must be (very short?), so a hook is used to release the bus manually.
        # If we didn't do that at all, CLOCK OUT would be cleared which the listeners take as "we have a bit, and it is X"
        # hence, the next time the listener read something it would read garbage.
        # Therefore, we prevent it from releasing CLOCK (I don't really see why they did it, the communication graph is fine without it even after transferral of many bytes)
        #global BUS
        #BUS.release()
        print("IEC: bus released.")
        CPU.RTS()
        #CPU.set_PC(0xEE0C)
        return(True)
    else:
        return(False)
    