#!/usr/bin/env python

"""
This is a dialog-based Ruis filesystem browser.

Usage: 
       ruis-dialog-browser [dir [dir...]] [--rp rp-conn-spec] [--dialog dialogprogram]

Examples:

       ruis-dialog-browser

       This will open the browser with an empty ruis
       filesystem. Choose "create" and type
       ruis.Dirtype(name="newdir") to create an empty directory object
       in it.

       ruis-dialog-browser --rp tcp://localhost:8068

       This will load the standard ruis modules (the same as
       ruis.newfs("ruis_modules")) to the ruis filesystem. That
       includes /conn/rp for forming the ruis protocol
       connections. Note that if loaded directories are explicitely
       given on the command line, --rp argument does not anymore cause
       loading the default ruis modules. For instance,

       ruis-dialog-browser ruis_modules my_dir --rp tcp://localhost:8068 --dialog Xdialog
       
       will load ruis modules first from directory ruis_modules, then
       from my_dir. A ruis protocol connection to the local host will
       be created. The user interface will be implemented with
       Xdialog.
"""

import sys
import ruis
import dialog
import pdb
import textwrap
import time

dialog_program = "dialog" # can be redefined with argument --dialog

dialog_obj = None

DIALOG_DEFAULTS = {"cancel": "Back", "backtitle": "Ruis browser / Dialog"}

fs = None

_show_menu_cache = {}

def show_menu(current_path, fs):
    """
    Presents the Ruis filesystem object in current_path of the
    filesystem fs and lets the user to choose action. Returns an
    action-argument pair which can be

    ('back', None) - return to the previous object
    
    ('follow', name of a subdir) - journey onwards to the subdir

    ('call', name of a method) - call the method of the current file
    system object

    ('nop', None) - the choice does not cause any operation

    ('quit', None) - the user wants to quit.
    """
    fso = fs[current_path]
    methodlist = [e[5:] for e in fso['/events'].ls() if e.startswith('call:')]

    paramlist = []
    for e in methodlist:
        # pdb.set_trace()
        paramlist.append(fso['/events/call:' + e].get_paramnames()[1:])

    if 'ls' in methodlist: # its contents can be listed
        contents = fso.ls()
    else:
        contents = ()

    menu_choices = []
    menu_item_count = 0
    
    if contents:
        menu_choices.append(('===', 'Contents  ==='))
        for c in contents:
            menu_item_count += 1
            menu_choices.append((str(menu_item_count), c))
            # menu_choices.append((c, c, ''))
    
    if methodlist:
        menu_choices.append(('===', 'Methods  ==='))
        for mindex, m in enumerate(methodlist):
            menu_item_count += 1
            # menu_choices.append((str(menu_item_count), m,
            #                      "%s(%s)" % (m, ', '.join(paramlist[mindex]))))
            menu_choices.append((str(menu_item_count), m))

    if menu_item_count == 0:
        menu_item_count += 1
        menu_choices.append(('===', 'No contents or methods  ==='))
        default_item = "==="
    else:
        default_item = _show_menu_cache.get(current_path, "1")
    
    if current_path == "": menu_text = "/"
    else: menu_text = current_path
    choice = dialog_obj.menu(menu_text, choices=menu_choices,
                             height=len(menu_choices)+7, menu_height=len(menu_choices)+4,
                             default_item=default_item, 
                             **DIALOG_DEFAULTS)
    try:
        chosen_item = int(choice[1])
    except:
        if choice[0] == 1:
            return 'back', None
        elif choice[0] == 2: # ESC pressed
            return 'quit', None
        elif choice[0] == 0:
            return 'nop', None
        else:
            print "Unexpected return value from dialog:", str(choice)
            return 'quit', None

    _show_menu_cache[current_path] = str(chosen_item)
    if choice[0] == 0 and chosen_item < len(contents) + 1:
        return 'follow', contents[chosen_item-1]
    elif choice[0] == 0 and chosen_item - len(contents) - 1 < len(methodlist):
        return 'call', methodlist[chosen_item - len(contents) - 1]
    elif choice[0] == 1:
        return 'back', None
    else:
        print "CHECK choice[0], choice[1]"
        pdb.set_trace()

def show_msg(title, msg):
    dialog_obj.scrollbox(msg, title=title, **DIALOG_DEFAULTS)

def ask_arguments(path, methodname, paramnames):
    text = path + '.' + methodname + "(" + ', '.join(p for p in paramnames[1:]) + ")"
    args = dialog_obj.inputbox(text, title="Call arguments", width=60, **DIALOG_DEFAULTS)
    if args[0] == 0:
        return args[1]
    else:
        return None

def main():
    global dialog_program, dialog_obj, fs
    fs = ruis.newfs()

    # Parse commandline
    sys.argv.pop(0)
    paths = [""]
    while sys.argv:
        opt = sys.argv.pop(0)
        try:
            if not opt.startswith("--"):
                ruis.load_directory(fs, opt)
            elif opt.startswith("--rp"):
                if len(fs.ls()) == 0: # if fs is empty, try loading the default modules
                    ruis.load_directory(fs, 'ruis_modules')
                if len(opt)>5 and opt[4] == "=": rp_connspec = opt[5:]
                else: rp_connspec = sys.argv.pop(0)
                conn_name = fs["/conn/rp"].create(rp_connspec)
                paths = paths + ["/conn", "/conn/rp", "/conn/rp/" + conn_name]
            elif opt.startswith("--dialog"):
                if len(opt)>9 and opt[8] == "=": dialog_program = opt[9:]
                else: dialog_program = sys.argv.pop(0)
        except Exception, e:
            print __doc__
            print "Error: %s" % (e)
            sys.exit(1)

    dialog_obj = dialog.Dialog(dialog=dialog_program)
    # The main loop
    while 1:
        # Show menu. If menu fails, drop malfunctioning path and try
        # to show another menu.
        while paths:
            cur_path = paths[-1]
            try:
                action, arg = show_menu(cur_path, fs)
                break # everything is fine, action and arg are valid
            except Exception, e:
                show_msg("Invalid path",
                         "Path %s is no more valid.\nReturning to the previous path." % (cur_path,))
                paths.pop()
        else: # while was not breaked out => paths is empty:
            action = 'quit'

        if action == 'follow':
            paths.append(paths[-1] + "/" + arg)
        elif action == 'call':
            paramnames = fs[paths[-1] + '/events/call:' +arg].get_paramnames()
            if len(paramnames) > 1:
                callargs = ask_arguments(paths[-1],arg, paramnames)
                if callargs == None: continue
            else:
                callargs = ""
            cmd = 'fs["%s"].%s(%s)' % (paths[-1], arg, callargs)
            try:
                result = eval(cmd)
                result_type = "return value of type " + result.__class__.__name__
            except Exception, e:
                result = str(e)
                result_type = "exception of type " + e.__class__.__name__

            # Wrap the resulting string representation, if it does not
            # seem to be formatted yet (that is, it does not contain
            # linefeeds).
            result = str(result)
            if not '\n' in result:
                result = '\n'.join(textwrap.wrap(str(result), width=60))
            show_msg("Result of execution", "Command:\n%s\n\nResult: (%s)\n%s" % 
                     (cmd, result_type, result))
        elif action == 'back':
            if len(paths) > 1:
                paths.pop()
        elif action == 'nop':
            pass
        elif action == 'quit':
            break

if __name__ == '__main__':
    main()
