#!/usr/bin/env python
# stepper.py - display simulator debug output in windows
#
# A part of qm - The Quadruple Machine
#    Copyright (C) 2004 Tuukka Hastrup
#
#    Redistribution and use in source and binary forms, with or without
#    modification, are permitted provided that the following conditions
#    are met:
#
#    1. Redistributions of source code must retain the above copyright
#       notice, this list of conditions and the following disclaimer.
#
#    2. Redistributions in binary form must reproduce the above
#       copyright notice, this list of conditions and the following
#       disclaimer in the documentation and/or other materials provided
#       with the distribution.
#
#    3. The name of the author may not be used to endorse or promote
#       products derived from this software without specific prior
#       written permission.
#
#    THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
#    OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
#    WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
#    ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
#    DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
#    DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
#    GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
#    INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
#    WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
#    NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
#    SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import pygtk            # you'll need Debian package python-gtk2
pygtk.require('2.0')    # you'll need Debian package python-gtk2
import gtk
import sys
from traceback import print_exc

input_channel = None
one_line = None

def start_input(one_line_setting=False):
    global input_channel, one_line
    one_line = one_line_setting
    if input_channel != None: return
    input_channel = gtk.input_add(sys.stdin, gtk.gdk.INPUT_READ,
                                  lambda file,x:handle_new_input(instructions,
                                                                 steps,
                                                                 file,
                                                                 x))

def stop_input():
    global input_channel
    if input_channel == None: return
    gtk.input_remove(input_channel)
    input_channel = None

def window(title, content, main=False):
# top-level window
    window = gtk.Window()
    window.add(content)
    window.set_title(title)
    window.set_size_request(250, 300)
# show
    window.show_all()
    return window

def column(header, id):
    column = gtk.TreeViewColumn(header)
    cell = gtk.CellRendererText()
    column.pack_start(cell, True)
    column.add_attribute(cell, 'text', id)
    column.set_sort_column_id(id)
    return column

def list_component():
# model
    model = gtk.ListStore(str, str, str)
# view
    listview = gtk.TreeView(model)

    listview.append_column(column('Machine', 0))
    listview.append_column(column('IP', 1))
    listview.append_column(column('Instruction', 2))

#    listview.set_search_column(1)
#    listview.set_reorderable(True)
# scrollbars
    scrollarea = gtk.ScrolledWindow()
    scrollarea.add(listview)
    return model, scrollarea

linebuffer = ''

def handle_new_input(code, steps, inputfile, y):
    global linebuffer, one_line
    try:
        data = inputfile.read(1)
        sys.stdout.write(data)
        if data != '' and data != '\n':
            linebuffer += data
            return
        line = linebuffer
        linebuffer = ''
        
        gtk.gdk.threads_enter()
        if one_line:
            stop_input()
        if data != '': add_step(code, steps, line)
        # without the following line, input throttles the GUI
        while gtk.events_pending(): gtk.main_iteration()
        gtk.gdk.threads_leave()
    finally:
        if data == '': # if eof
            return gtk.FALSE
        return gtk.TRUE

def add_step(code, steps, line):
        words = line.split()
        try:
            if words[0] == '>' or words[0] == '<':
                words = words[1:]
            if words[2] == "debugger>":
                words = words[3:]
            if not words[0].startswith('#'): raise IndexError
            mid = words[0][1:]
            if not words[1].startswith("0x"): raise IndexError
            if not words[1].endswith(":"): raise IndexError
            ip = words[1][:-1]
            text = ' '.join(words[2:])

            i = code.get_iter_first()
            while i != None:
                if code[i][1] >= ip: break
                i = code.iter_next(i)
            if i == None:
                code.append((mid, ip, text))
            elif code[i][1] > ip:
                code.insert_before(i, (mid, ip, text))
            else:
                pass # ip is already included
            steps.append((mid, ip, text))
            adjustment = traceview.get_vadjustment()
            if adjustment.value == adjustment.upper - adjustment.page_size:
                adjustment.value = adjustment.upper # XXX hack
        except IndexError:
            steps.append(("", "", line))
            adjustment = traceview.get_vadjustment()
            if adjustment.value == adjustment.upper - adjustment.page_size:
                adjustment.value = adjustment.upper # XXX hack
        except:
            print_exc()

if __name__ == '__main__':

    instructions, instrview = list_component()
    steps, traceview = list_component()

    window("Text segment", instrview)

    toolbar = gtk.Toolbar()
    toolbar.insert_stock(gtk.STOCK_EXECUTE,
                         "Start execution",
                         None,
                         lambda x:start_input(),
                         None,
                         -1)
    toolbar.insert_stock(gtk.STOCK_GO_FORWARD,
                         "Read next trace step",
                         None,
                         lambda x:start_input(True),
                         None,
                         -1)
    toolbar.insert_stock(gtk.STOCK_STOP,
                         "Stop execution",
                         None,
                         lambda x:stop_input(),
                         None,
                         -1)
    toolbar.insert_stock(gtk.STOCK_QUIT,
                         "Quit application",
                         None,
                         lambda x:gtk.main_quit(),
                         None,
                         -1)

    vbox = gtk.VBox()
    vbox.pack_start(toolbar, False)
    vbox.pack_start(traceview, True)
    window("Trace", vbox).connect('delete-event', lambda x,y:gtk.main_quit())


    gtk.main()

