#!/usr/bin/env python

import json
import optparse
import subprocess
import time
import random
import sys

def send_to_screen(screen_name, arg0=None, command='stuff'):
    args = ['screen', '-S', screen_name, '-p', '0',
            '-X', command]
    if arg0 is not None:
        args.append(arg0)
    p = subprocess.Popen(args)
    p.communicate()

def load_as_transcript(f):
    def add_block(clearScreen=False,noReturn=False):
        # If so, add the current block after trimming leading and trailing blank
        # lines.
        while current_block and current_block[0].isspace():
            current_block.pop(0)
        while current_block and current_block[-1].isspace():
            current_block.pop()

        # Add the block if it is non-empty.
        if current_block or comment_block:
            # Form the joined script..
            script = ''.join(current_block)
            comment = ''.join(comment_block)

            # Strip off any trailing newline.
            if script.endswith('\n'):
                script = script[:-1]
            demo_script.append({ 'command' :  script,
                                 'comment' : comment,
                                 'clearScreen' : clearScreen,
                                 'noReturn' : noReturn })

            # Clear the block and comment block.
            del current_block[:]
            del comment_block[:]

    f.seek(0)

    demo_script = []
    current_block = []
    comment_block = []
    for ln in f:
        # Check if this is a block delimiter.
        if ln.strip() == '>>>':
            add_block()
            continue

        # Check if this is a block delimiter
        # but marked to clear the screen.
        if ln.strip() == '>>>CLEAR':
            add_block(clearScreen=True)
            continue

        # Check if this is a block delimiter
        # but marked to not insert a newline.
        if ln.strip() == '>>>NORETURN':
            add_block(noReturn=True)
            continue

        # Check if the line starts with a '#'
        # to indicate a prompter comment.
        if ln.startswith('#'):
            comment_block.append(ln)
            continue
        
        # Check for backspace.
        if ln.strip() == '>>>BACKSPACE':
            current_block.append('\b')
            continue

        # Otherwise, add the line to the current block.
        current_block.append(ln)

    return demo_script

def main():
    parser = optparse.OptionParser("""\
usage: %%prog [options] <demo script>

Run a command line demo script using 'screen'. The script file should be either
a JSON document listing the commands to run, as in::

  [
    { "command" : "ls" },
    { "command" : "echo Hello" }
  ]

or, alternately, it should be a text filed delimited by '>>>', as in::

  >>>
  ls

  >>>
  echo Hello

where leading and trailing blank lines around each block will be discarded.

The script requires the 'screen' session to have been started in another window,
for example::

  $ screen -S demo
""")

    # Determine up front if the terminal supports color.
    hasColor = sys.stdout.isatty()

    def prompterPrint(string):
        if hasColor:
            attr = [ '32', '1' ]
            print '\x1b[%sm%s\x1b[0m' % (';'.join(attr), string)
        else:
            print string
    
    def send(*args, **kwargs):
        return send_to_screen(opts.screen_name, *args, **kwargs)
    parser.add_option("-S", "--screen-name", dest="screen_name", metavar="NAME",
                      help="name of the screen sesison to use [%default]",
                      action="store", default="demo")

    opts, args = parser.parse_args()

    if len(args) == 1:
        path, = args
    else:
        parser.error("invalid number of arguments")

    # Read in the demo script.
    with open(path) as f:
        try:
            demo_script = json.load(f)
        except ValueError:
            demo_script = load_as_transcript(f)
        
    # Validate the entries.
    for item in demo_script:
        command = str(item.get('command'))
        if not isinstance(command, str):
            raise SystemError("error: invalid item in script: %r" % (
                    item,))

    # Notify screen that we are starting the demo.
    raw_input('press enter to begin demo...')

    # Iterate over each command, sending it and waiting for user direction to
    # continue.
    for item in demo_script:
        shouldClear = bool(item['clearScreen'])
        noReturn = bool(item['noReturn'])        
        command = str(item['command'])
        comment = str(item['comment'])
        
        if comment:
            prompterPrint(comment)

        if command:
            # Send the command slowly, as if it was typed.
            print "sending command: %r" % (command.replace('\n', '<cr>'),)
            for c in command:
                send(c)
                if c == "\n":
                  time.sleep(0.1)
                else:
                  time.sleep(random.random() * 0.03)

            if not noReturn:
              # Wait for user input, then send a return.        
              raw_input('press enter to send return...')
              send('\n')
          
        if item is not demo_script[-1]:
            raw_input('press enter to continue to next command...')
            # Send the command slowly, as if it was typed.
            if shouldClear:
                print "clearing screen"
                send(command="clear")
                send('\n')

    # Notify screen that the demo is over, after a small wait period.
#    time.sleep(0.1)
#    send('Demo session is over.', command='wall')

if __name__ == '__main__':
    main()
