#!/usr/bin/env python
# -*- coding: utf-8 -*-
# I, Danny Milosavljevic, hereby place this file into the public domain.

"""
this gets a stream of data from the bus.
Each communication message is 1 byte, with the following contents:
        bit 3 = ATN
        bit 4 = CLOCK
        bit 5 = DATA

Note that currently, it is assumed that there's just one peripheral device per process on the bus.
"""

import sys
import select
import os
import time

TIMEOUT = 0x1000
M_ATN = 2**3
M_CLOCK = 2**4
M_DATA = 2**5

C_LISTEN = 0x20
C_UNLISTEN = 0x3F
C_TALK = 0x40
C_UNTALK = 0x5F

inFD = 0
outFD = 1

# ================================= lowlevel =====================================
def parse_input():
  while True:
    rFDs, wFDs, xFDs = select.select([inFD], [], [inFD], 0.94)
    if len(rFDs) == 0 and len(wFDs) == 0 and len(xFDs) == 0: # timeout, error
        print >>sys.stderr, "timeout"
        time.sleep(0.2)
        yield TIMEOUT
    else:
        chunk = os.read(inFD, 1)
        if len(chunk) == 0: # EOF
            print >>sys.stderr, "EOF, oops"
            sys.exit(1)
        else:
            xlines = ord(chunk)
            print >>sys.stderr, "LL got %s%s%s" % ("A" if xlines&M_ATN else "-", "C" if xlines&M_CLOCK else "-", "D" if xlines&M_DATA else "-")
            yield xlines
            print >>sys.stderr, "back"
def await_response(P):
    """ given a predicate P, uses it to check whether it's already satisfied. If not, reads from the pipe, checks this etcetc (until timeout or satisfaction). 
        Returns: whether the predicate was satisfied (in the end) (regardless of timeout!) """
    global lines
    if not P(lines):
      w = stream.next()
      while w != TIMEOUT:
        lines = w
        if P(lines):
          break
        w = stream.next()
    return P(lines)
def emit_line(B_pull, lineID):
    global lines
    # make sure we don't dead-lock. FIXME only read if there's stuff available.
    await_response(lambda v: False)
    if B_pull:
        lines = lines | lineID
    else:
        lines = lines&~lineID
    os.write(outFD, chr(lines&0xFF))
    time.sleep(0.00001) # make sure the C64 has a chance to see it.
    #os.write(outFD, chr(lines&0xFF)) # most C64 routines check whether the value stays the same, so make sure it stays the same for a little "while".
# ============================================== higher level =======================================
def spin_wait_DATA_released():
    return await_response(lambda lines: lines&~M_DATA)
def spin_wait_DATA_pulled():
    return await_response(lambda lines: lines&M_DATA)
def spin_wait_CLOCK_released():
    return await_response(lambda lines: lines&~M_CLOCK)
def spin_wait_CLOCK_pulled():
    return await_response(lambda lines: lines&M_CLOCK)
def spin_wait_ATN_pulled():
    return await_response(lambda lines: lines&M_ATN)
def spin_wait_ATN_released():
    return await_response(lambda lines: lines&~M_ATN)
def release_DATA():
    emit_line(False, M_DATA)
def pull_DATA():
    emit_line(True, M_DATA)
def release_CLOCK():
    emit_line(False, M_CLOCK)
def pull_CLOCK():
    emit_line(True, M_CLOCK)
# ================================================== still higher level =============================
def wait_bit():
    while not spin_wait_CLOCK_released():
      print >>sys.stderr, "waiting for bit..."
      time.sleep(0.1)
def wait_CLOCK_pulled(timeout_us):
    while not spin_wait_CLOCK_pulled():
      print >>sys.stderr, "waiting for CLOCK pull..."
      time.sleep(0.1)
    return(True)
def wait_CLOCK_released():
    while not spin_wait_CLOCK_pulled():
      print >>sys.stderr, "waiting for CLOCK pull..."
      time.sleep(0.1)
def wait_DATA_pulled(timeout_us):
    while not spin_wait_DATA_pulled():
      # TODO timeout.
      print >>sys.stderr, "waiting for DATA pull..."
      time.sleep(0.1)
    return(True)
def wait_DATA_released():
    while not spin_wait_DATA_released():
      # TODO timeout.
      print >>sys.stderr, "waiting for DATA release..."
      time.sleep(0.1)
def wait_ATN_released():
    while not spin_wait_ATN_released():
      # TODO timeout.
      print >>sys.stderr, "waiting for ATN release..."
      time.sleep(0.1)
def wait_unbit():
    return wait_CLOCK_pulled(2000) # TODO proper timeout
def read_bits():
    global lines
    result = 0
    mask = 1
    while mask < 0x100:
        wait_bit()
        if lines&M_DATA:
            result |= mask
        wait_unbit()
        mask <<= 1
    return(result)
def wait_ACK():
    global lines
    print >>sys.stderr, "waiting for 'ACK'"
    assert(spin_wait_CLOCK_pulled()) # should already be!
    assert(lines&~M_DATA)
    B_ok = wait_DATA_pulled(1000)
    if not B_ok:
        print >>sys.stderr, "error: byte was not acknowledged by listener."
        # TODO more error
    assert(lines&M_CLOCK)
    assert(lines&M_DATA)
def write_bits(value):
    global lines
    # both clock and data controlled by talker.
    assert(spin_wait_CLOCK_pulled()) # should already be!
    assert(lines&~M_DATA)
    for i in range(8):
        if value&1:
            pull_DATA()
        else:
            release_DATA()
        release_CLOCK()
        #release_DATA()
        time.sleep(0.0001)
        pull_CLOCK()
        release_DATA()
        value >>= 1
    wait_ACK()
def wait_CTS():
    global lines
    print >>sys.stderr, "waiting for 'clear to send'"
    while True: # even when timeouting, try again and again. We are so chatty.
        if wait_DATA_released():
          break
        time.sleep(0.1)
def CTS():
    release_DATA()
def wants_to_send_P():
    print >>sys.stderr, "waiting for 'ready to send' some tiny while"
    return spin_wait_CLOCK_released()
# =================================================== very high level ===================================
def read_byte():
    global lines
    #assert(lines & M_CLOCK) # maybe already released!
    assert(lines&M_DATA) # WE are holding the DATA line.
    while not wants_to_send_P():
      print >>sys.stderr, "talker doesn't want to send..."
      time.sleep(0.1)
    CTS() # release_DATA()
    if not wait_CLOCK_pulled(200): # EOI
        pull_DATA()
        time.sleep(0.0060) # FIXME wait 60 μs instead
        release_DATA()
        # within 60 μs, CLOCK will be true again.
    assert(spin_wait_CLOCK_pulled()) # should already be
    result = read_bits()
    assert(spin_wait_CLOCK_pulled()) # by talker
    assert(lines&~M_DATA)
    # the above may take max. 1 ms
    pull_DATA()
def write_byte(B_EOI, value):
    global lines
    # we are the talker.
    # clock (by talker) and data (by listener) are already set
    assert(lines&M_CLOCK) # by us
    assert(spin_wait_DATA_pulled()) # should already be.
    release_CLOCK()
    wait_CTS()
    if B_EOI:
        time.sleep(0.2) # actually should be 200 μs instead
        # listener will pull data line to true for at least 60 μs then release data line.
        wait_DATA_pulled(60)
        wait_DATA_released() # TODO timeout
        # talker will go on as normal 
    pull_CLOCK()
    write_bits(value)
def stop_caring():
    global lines
    release_DATA()
    release_CLOCK()
# ====================================================== main ===========================================
stream = None

MODE_NONE = 0
MODE_TALKER = 1
MODE_LISTENER = 2
mode = MODE_NONE

def handle_ATN(value):
    global mode
    print >>sys.stderr, "handle ATN", value
    if value & (iec.C_TALK|device_address):
        mode = MODE_TALKER
    elif value == iec.C_UNTALK:
        assert(mode == MODE_TALKER or mode == MODE_NONE)
        mode = MODE_NONE
    elif value == iec.C_LISTEN|device_address:
        assert(mode == MODE_NONE)
        mode = MODE_LISTENER
    elif value == iec.C_UNLISTEN:
        assert(mode == MODE_LISTENER or mode == MODE_NONE)
        mode = MODE_NONE
        return(False)
    elif mode == MODE_LISTENER or mode == MODE_TALKER: # secondary channel, maybe
        return(False)
    return(True)

"""
C64:
def turnaround1():
    pull_DATA()
    release_ATN()
    release_CLOCK()
    wait_CLOCK_change() => clear bit 6
"""    
def wait_turnaround():
    global lines
    # for TALK, after the channel is sent, 
    wait_ATN_released()
    # here, device is holding down DATA line and computer is holding down CLOCK line.
    #assert(spin_wait_DATA_pulled())
    #assert(spin_wait_DATA_pulled()) # that's ourselves doing it, duh
    wait_CLOCK_released()
    # computer is holding down DATA line too now and released the CLOCK line.
    release_DATA()
    assert(lines&M_DATA) # should still be there.
    pull_CLOCK()
    # now, talker (device) is holding the CLOCK down, listener (computer) holding the DATA line down.
lines = 0
def run(ATN_handler):
    global stream
    stream = parse_input()
    await_response(lambda v: False)
    while True:
        if spin_wait_ATN_pulled(): # "SELECT" mode
            print >>sys.stderr, "got ATN"
            release_CLOCK() # other side holds CLOCK down anyway
            # DATA was released by other side
            pull_DATA()
            # FIXME what if it doesn't want to send immediately?
            while not wants_to_send_P():
              time.sleep(0.00001)
              print >>sys.stderr, "waiting for RTS..."
            if wants_to_send_P():
                data = read_byte()
                assert(lines & M_ATN)
                print >>sys.stderr, "A", data
                if not handle_ATN(data):
                    ATN_handler(data, mode)
                    if mode == MODE_TALKER:
                        iec.wait_turnaround()
        else:
            pass
            #if wants_to_send_P():
            #    data = read_byte()
            #    assert(lines &~ M_ATN)
            #    print >>sys.stderr, "ignoring blahblah", data
            #    #handler(False, data)
"""
on LISTEN, the computer does:

release DATA.
pull ATN.
pull CLOCK.
release DATA.
delay 1ms.
send byte:
    release DATA.
    if DATA still pulled, error.
    release CLOCK.
    if EOI:
        wait until DATA released.
        wait until DATA down.
    wait until DATA released.
    pull CLOCK.
    repeat:
      wait until settled.
      if DATA still down, handle error. (ED6F)
      pull or release DATA depending on bit.
      release CLOCK.
      wait tiny bit.
      set CLOCK.
      release DATA.
    until all bits sent.
    set timer.
    wait until DATA set (or timeout).
    on timeout, set status 3.
"""
# TODO $EEB3 delay 1ms
