# version 2.0 27/1/19
# Lots of bugs corrected, see update2.0.pdf

from tkinter import *
import tkinter.filedialog
#from tkFileDialog import *
import os

# As far as possible functions are used to mediate between the the widget 
# variables and the main program; this imprroves intelligibility.  In a few
# cases, where is is important, the widgets are accessed directly.

def create_crt():
    "creates crt widget"
    global crt_exists, crt, crt_display
    crt_display=Toplevel()
    crt=Canvas(crt_display, height=384, width=384, bg='grey20')
    crt.pack()
    crt.create_oval(7,7,380,380, fill='grey40')
    crt_exists=True

def brighten_spot(x,y):
    "maps from edsac crt coordinates to canvas corrdinates"
    x=int(x//4) + 192; y=192 - int(y//4)
    crt.create_rectangle(x, y, x+1, y-1, outline='green')

def get_input_tape_file1():
    global input_tape_name, new_input_tape
    filename=tkinter.filedialog.askopenfilename(initialdir=".", \
             filetypes=[("code","*.txt"), ("code","*.edsc")])
    intape_file1.set(os.path.split(filename)[1])
    new_input_tape[0]=True
    input_tape_name[0]=filename

def get_input_tape_file2():
    global input_tape_name, new_input_tape
    filename=tkinter.filedialog.askopenfilename(initialdir=".", \
             filetypes=[("code","*.txt"), ("code","*.edsc")])
    intape_file2.set(os.path.split(filename)[1])
    new_input_tape[1]=True
    input_tape_name[1]=filename

def display_address(m):
    "displays address neons on control panel"
    n=(bin(m)[2:]).zfill(11)
    for i in range(0,11):
        if n[i]=='1': neon[i]["selectcolor"]="red"
        if n[i]=='0': neon[i]["selectcolor"]="gray"

def lamp_show(n, x):
    "turns on or off coloured lamp on control panel"
# wait_light=0; stop_light=1; manual_wait_light=2; optional_stop_light=3
# report_light=4; input_busy_light=5; output_busy_light=6;
# magnetic_tape_light=9
    if x: lamp[n]["background"]=lamp_colour_on[n]
    else: lamp[n]["background"]=lamp_colour_off[n]

def set_run_switch(x):
    "moves 'run' switch on control panel up or down"
    run_switch.set(x)

def stopstop():
    "used to break out of loops etc"
    global stop
    stop=True

def step_go():
    "step one machine order"
    global go
    go=True

def step_on():
    "turn on single step mode"
    global step, go
    step=True
    go=False
    singlestep_on["background"]="cyan"
    singlestep_go["background"]="cyan"
    singlestep_off["background"]="cyan"

def step_off():
    "turn off single step mode"
    global step, go
    step=False
    go=True
    singlestep_on["background"]="gray85"
    singlestep_go["background"]="gray85"
    singlestep_off["background"]="gray85"

def set_store_neon(s):
    "sets store 'neon' on control panel"
    if s==free:
        free_neon["selectcolor"]="red"
        reserved_neon["selectcolor"]="gray"
    else:
        reserved_neon["selectcolor"]="red"
        free_neon["selectcolor"]="gray"

def set_emulator_run_light(x):
    "'run' light on the emulator control panel shows if it is running"
    if x: running_b["background"]="green"
    else: running_b["background"]="grey"

def read_manual_register():
    "reads the 'manual register on the control panel"
    M=0
    for switch in manreg: M=(M << 1) + switch.get()
    return M

def read_optstop_keys():
    "reads 'optional stop' keys, returns integer 0-7"
    return 4*optstopval[2].get()+2*optstopval[1].get()+optstopval[0].get()

def reset_pressed():
    "the 'reset' button has been pressed"
    global reset
    reset=True

def get_debug_setting():
    return debug_r.get() + 2*debug_f.get()

def set_default_debug(x):
    debug_r.set(x & 1)
    debug_f.set((x & 2) >> 1)

def get_output_setting():
    return out_to_term.get() + 2*out_to_file.get()

def set_default_output(x):
    out_to_term.set(x & 1)
    out_to_file.set((x & 2) >> 1)

def set_cause_of_report(x):
    cause.set(x)

def get_main():
    return (mainstore_on_off.get()==1, choose_assembler.get()==1)

def main_store_button():
    "called by button selecting store, sets external assembler for main store"
    "programs by default, although this can be overwritten"
    choose_assembler.set(mainstore_on_off.get())

def save_magnetic_tape():
    return keep_magnetic_tape.get()==1

# The panel colour is taken from the photographs of the real one!
panel_colour="#%02x%02x%02x" % (62,40,42)
gui=Tk()
control_panel=Frame(gui)
emulator=Frame(gui)

control_panel["background"]=panel_colour
leftside=Frame(control_panel)
middle=Frame(control_panel)
rightside=Frame(control_panel)

cp_title=Label(control_panel, text="EDSAC II CONTROL PANEL", font="helvetica 14")
cp_title.pack(side="top")

# LEFT HAND SIDE
leftside["background"]=panel_colour
# manual register
manual_register=Frame(leftside)
manual_register["background"]=panel_colour
# mr contains the widgets
mr=[]
# manreg contains the switch settings
manreg=[]
for i in range(0,10):
# create row i
    mr.append([])
    mr[i].append(Label(manual_register, text=str(i)))
    mr[i][0].grid(row=i, column=0)
    for j in range(1,5):
        manreg.append(IntVar())
        mr[i].append(Checkbutton(manual_register, \
                     variable=manreg[-1], width=1, height=2))
        mr[i][j]["background"]=panel_colour
        mr[i][j].grid(row=i, column=j)
manual_register.pack(side="top", padx=5)

# start switches
startswitches=Frame(leftside)
startswitches["background"]=panel_colour
sc_l=Label(startswitches, text="SET START AND\nCLEAR TO ONES", font="helvetica 8")
ss_l =Label(startswitches, text="SET START", font="helvetica 8")
sc_b=Button(startswitches, command=clear_start)
ss_b =Button(startswitches, command=set_start)
sc_l.grid(row=0, column=0, pady=4)
ss_l.grid(row=0, column=2)
sc_b.grid(row=1, column=0)
ss_b.grid(row=1, column=2)
run_on_off=Frame(startswitches)
run_switch_l=Label(startswitches, text="  Run  ", foreground="red")
run_switch=IntVar()
run_u=Radiobutton(run_on_off, height=2, text="up",   variable=run_switch, value=0)
run_d=Radiobutton(run_on_off, height=2, text="down", variable=run_switch, value=1)
run_switch.set(0)
run_u.pack(side="top", anchor="w")
run_d.pack(side="top", anchor="w")
run_switch_l.grid(row=2, column=0)
run_on_off.grid(row=2, column=1, pady=15)
startswitches.pack(side="bottom", pady=8)
leftside.pack(side="left")

# MIDDLE PANEL
middle["background"]=panel_colour
neons=Frame(middle)
neons["background"]=panel_colour
neon=[]; bits=[]
for i in range(0,11):
    bits.append(Label(neons, width=5, text=str(2**(10-i))))
    neon.append(Checkbutton(neons))
    neon[i]["selectcolor"]="gray"
    bits[i].grid(row=i, column=0)
    neon[i].grid(row=i, column=1)
neons.pack(side="top", pady=10)

opt_stop_switches=Frame(middle)
opt_stop_switches["background"]=panel_colour
optstop_sw=[]; optstop_l=[]
optstopval=[IntVar(), IntVar(), IntVar()]
for (i,j) in enumerate([1,2,4]):
    optstop_l.append(Label(opt_stop_switches, height=3, text="optional\nstop\n"+str(j)))
    optstop_sw.append(Checkbutton(opt_stop_switches, variable=optstopval[i]))
    optstop_l[i].grid(row=0, column=i+1)
    optstop_sw[i].grid(row=1, column=i+1)
reset_l=Label(opt_stop_switches, height=3, text="reset\n")
reset_b=Button(opt_stop_switches, height=1, command=reset_pressed)
reset_l.grid(row=0, column=0)
reset_b.grid(row=1, column=0)
opt_stop_switches.pack(side="top", pady=10)
# store lights
store_lights=Frame(middle)
free_l=Label(store_lights, text="free")
free_neon=Checkbutton(store_lights)
store_lights_l=Label(store_lights, text="store")
reserved_neon=Checkbutton(store_lights)
reserved_l=Label(store_lights, text="reserved")
free_l.pack(side="top")
free_neon.pack(side="top")
store_lights_l.pack(side="top")
reserved_neon.pack(side="top")
reserved_l.pack(side="top")
store_lights.pack(side="right", padx=20, pady=20)
# trace switch, does nothing
trace_switch=Frame(middle)
trace_switch_l=Label(trace_switch, text="Trace")
trace_switch_b=Checkbutton(trace_switch)
trace_switch_l.pack(side="top")
trace_switch_b.pack(side="top")
trace_switch.pack(side="left", padx=20)

middle.pack(side="left", anchor="n", padx=8)

# RIGHT HAND SIDE
rightside["background"]=panel_colour
# lights and labels
lights=Frame(rightside)
lights["background"]=panel_colour
wait_light=0; stop_light=1; manual_wait_light=2; optional_stop_light=3
report_light=4; input_busy_light=5; output_busy_light=6; magtape_light=9
lamp_colour_on=[]; lamp_colour_off=[]
state=[]; lamp=[]; label=[]; i=0
for (name, colour_on, colour_off) in \
                      [("wait",           "orange1", "orange4"),         \
                       ("stop",           "red1", "red4"),               \
                       ("manual\nwait",   "green1", "green4"),           \
                       ("optional\nstop", "RoyalBlue1", "RoyalBlue4"),   \
                       ("report",         "red1", "red4"),               \
                       ("input\nbusy",    "bisque1", "bisque4"),         \
                       ("output\nbusy",   "bisque1", "bisque4"),         \
                       ("not\nwriting",   "orange4", "orange4"),         \
                       ("control",        "red4", "red4"),               \
                       ("magnetic\ntape", "bisque1", "bisque4"),         \
                       ("margins",        "", "orange4"),                \
                       ("eng.key",        "", "red4")]:

    label.append(Label(lights, text=str(i+1)))
    state.append(Label(lights, text=name))
    lamp.append(Button(lights, width=1, height=2))
    lamp[i]["background"]=colour_off
    lamp_colour_off.append(colour_off)
    lamp_colour_on.append(colour_on)
    label[i].grid(row=i, column=0)
    lamp[i].grid(row=i,  column=1)
    state[i].grid(row=i, column=2)
    i=i+1
lights.pack(side="right")
rightside.pack(side="right", padx=10)
#
# EMULATOR CONTROL PANEL
#
em_title=Label(emulator, text="EMULATOR CONFIGURATION", font="helvetica 14")
em_title.pack(side="top", pady=20)
# main store switch and assembler selection
mainstore=Frame(emulator)
mainstore_on_off=IntVar()
mainstore_l=Label(mainstore, text="Emulate with main store")
mainstore_off=Radiobutton(mainstore, text="off", variable=mainstore_on_off, value=0, command=main_store_button)
mainstore_on =Radiobutton(mainstore, text="on",  variable=mainstore_on_off, value=1, command=main_store_button)
mainstore_on_off.set(0)
mainstore_l.pack(side="left")
mainstore_off.pack(side="left")
mainstore_on.pack(side="left")
mainstore.pack(side="top", pady=8)

assembler_choice=Frame(emulator)
choose_assembler=IntVar()
assembler_choice_l=Label(assembler_choice, text="Select which assembler to use")
assembler_choice_in= Radiobutton(assembler_choice, text="Built-In", variable=choose_assembler, value=0)
assembler_choice_out=Radiobutton(assembler_choice, text="External", variable=choose_assembler, value=1)
assembler_choice_l.pack(side="left")
assembler_choice_in.pack(side="left")
assembler_choice_out.pack(side="left")
assembler_choice.pack(side="top", pady=10)

# Tape readers
intape=Frame(emulator)
intape1=Frame(intape)
intape_l1=Label(intape1, text="Tape Reader 1  ", font="sans 10")
intape_file1=StringVar()
intape_file1.set(input_tape_name[0])
intape_e1=Entry(intape1, width=25, textvariable=intape_file1, font='sans 12')
intape_b1=Button(intape1, text="Browse", command=get_input_tape_file1)
intape_l1.pack(side="left", padx=20, anchor="w")
intape_e1.pack(side="left", padx=20)
intape_b1.pack(side="left", padx=20)
intape1.pack(side="top")
intape2=Frame(intape)
intape_l2=Label(intape2, text="Tape Reader 2  ", font="sans 10")
intape_file2=StringVar()
intape_file2.set(input_tape_name[1])
intape_e2=Entry(intape2, width=25, textvariable=intape_file2, font='sans 12')
intape_b2=Button(intape2, text="Browse", command=get_input_tape_file2)
intape_l2.pack(side="left", padx=20, anchor="w")
intape_e2.pack(side="left", padx=20)
intape_b2.pack(side="left", padx=20)
intape2.pack(side="top")
intape.pack(side="top", pady=8)

# debug information
debug_switches=Frame(emulator)
debug_label=Label(debug_switches, text="Debug information")
debug_f=IntVar()
debug_free=Checkbutton(debug_switches, variable=debug_f, text="Orders in Free Store")
debug_r=IntVar()
debug_reserved=Checkbutton(debug_switches, variable=debug_r, text="Orders in Reserved Store")
debug_label.pack(side="left", padx=10, anchor="w")
debug_reserved.pack(side="left", padx=10)
debug_free.pack(side="left", padx=10)
debug_switches.pack(side="top", pady=8)

# single step
singlestep=Frame(emulator)
singlestep_l=Label(singlestep, text="Step Single Order")
singlestep_on=Button(singlestep, text="On", width=2, height=2, command=step_on)
singlestep_go=Button(singlestep, text="Step", width=2, height=2, command=step_go)
singlestep_off=Button(singlestep, text="Off", width=2, height=2, command=step_off)
singlestep_l.pack(side="left", padx=20)
singlestep_on.pack(side="left", padx=20)
singlestep_go.pack(side="left", padx=20)
singlestep_off.pack(side="left", padx=20)
singlestep.pack(side="top", pady=10)

# running indicator and emergency stop
state=Frame(emulator)
running_l=Label(state, text="Running")
running_b=Button(state, width=2, height=2)
emergency_stop=Frame(state)
emergency_stop_l=Label(state, text="Emergency\nStop")
emergency_stop_b=Button(state, width=2, height=2, command=stopstop)
emergency_stop_b["background"]="red"
running_l.pack(side="left", padx=10)
running_b.pack(side="left", padx=10)
emergency_stop_l.pack(side="left", padx=10)
emergency_stop_b.pack(side="left", padx=10)
state.pack(side="top", pady=10)

# error message
error_cause=Frame(emulator)
cause=StringVar()
error_cause_l=Label(error_cause, text="Cause of Error")
error_cause_b=Entry(error_cause, width=30, textvariable=cause)
error_cause_l.pack(side="left", padx=10)
error_cause_b.pack(side="left", padx=10)
error_cause.pack(side="top", pady=10)

# printing of output tapes
print_tape=Frame(emulator)
out_to_term=IntVar()
print_tape_l=Label(print_tape, text="Print Output Tape to Terminal")
print_tape_b=Checkbutton(print_tape, text="", variable=out_to_term)
print_tape_l.pack(side="left", padx=10)
print_tape_b.pack(side="left", padx=10)

out_to_file=IntVar()
print_tape_l=Label(print_tape, text="Print Output Tape to File")
print_tape_b=Checkbutton(print_tape, text="", variable=out_to_file)
print_tape_l.pack(side="left", padx=10)
print_tape_b.pack(side="left", padx=10)
print_tape.pack(side="top", pady=10)

# save magnetic tape
save_mag_tape=Frame(emulator)
keep_magnetic_tape=IntVar()
save_mag_tape_l=Label(save_mag_tape, text="Save Magnetic Tape")
save_mag_tape_b=Checkbutton(save_mag_tape, text="", variable=keep_magnetic_tape)
save_mag_tape_l.pack(side="left", padx=10)
save_mag_tape_b.pack(side="left", padx=10)
save_mag_tape.pack(side="top", pady=10)
keep_magnetic_tape.set(0)

# comment out these lines to disable showing gui for development
emulator.pack(side="left")
control_panel.pack(side="right")

