/* wash.js

    LEGAL
      WASH SE, the Web Application SHell - Simplified Edition
      (C)2004,2008 psema4Technologies
      Licensed under the LGPL v2.1

    SYNOPSIS
      <html>
        <head><title>WASH: The Web Application SHell</title>
          <script type="text/javascript" src="wash.js"></script>
          <style type="text/css">.code,.data {display: none;}</style>
        </head>
        <body onload="window.Console = new WASH({show:true});">
          <div id="myFile" class="code">alert('This is myFile!');</div>
          <div id="myOtherFile" class="data">This is myOtherFile!</div>
        </body>
       </html>

    ABOUT
      WASH is a core component of the Atomic OS project.  This implementation creates a small
      (< 10Kb) console object - providing a simple command line interpreter and access to a
      single-folder filesystem simulated using JavaScript, DOM, and CSS.

      To add a WASH console to any web application simply reference this JavaScript file from the
      applications' HTML source file and create an instance of the WASH object:

        <script type="text/javascript" src="wash.js"></script>
        <script type="text/javascript">
          // Create a default panel (hidden!) ...
          window.Console = new WASH();

          // ... or show it ...
          // window.Console = new WASH({show:true});

          // ... or position and resize it
          // window.Console = new WASH({top:0, left:0, width:400, height:200});
        </script>

      By default, WASH will be hidden when it's created.  Hide & show the WASH panel with
      Console.hide() and Console.show() respectively.

      To add mini-programs to a web application, create a <DIV> in the HTML document's body with
      an ID="{your-filename}" and a CLASS="code".  Fill the <DIV> with JavaScript.

      To add other microfiles, create a <DIV> in the HTML document's body with an
      ID="{your-filename}" and a CLASS="data".  Fill the <DIV> with text content.

    AUTHOR
      Scott Elcomb (psema4-at-gmail-dot-com)

    SEE ALSO
      http://www.psema4.com/
      http://sourceforge.net/projects/atomos
*/

function WASH(optsObj) {
  this.opts = (optsObj) ? optsObj : {};

  this.init = function(optsObj) {
    var opts = (optsObj) ? optsObj : {};
    var top = (opts.top) ? opts.top : 20;
    var left = (opts.left) ? opts.left : 20;
    var width = (opts.width) ? opts.width : 800;
    var height = (opts.height) ? opts.height : 500;

    this.panel = document.createElement('div');
    this.stdout = document.createElement('textarea');
    this.stdin = document.createElement('input');

    this.panel.id = 'PANEL';
    this.panel.style.position = 'absolute';
    this.panel.style.top = top + 'px';
    this.panel.style.left = left + 'px';
    this.panel.style.width = width + 'px';
    this.panel.style.height = height + 'px';
    this.panel.style.padding = '4px';
    this.panel.style.backgroundColor = '#ddd';
    this.panel.style.border = '2px outset #ddd';

    if (opts && opts.show) {
      this.panel.style.visibility = 'visible';
    } else {
      this.panel.style.visibility = 'hidden';
    }

    this.stdout.id = 'STDOUT';
    this.stdout.style.width = '100%';
    this.stdout.style.height = (height - 40) + 'px';
    this.stdout.style.backgroundColor = '#ddd';

    this.stdin.id = 'STDIN';
    this.stdin.type = 'text';
    this.stdin.style.width = '100%';

    this.panel.appendChild(this.stdout);
    this.panel.appendChild(this.stdin);
    document.getElementsByTagName('body')[0].appendChild(this.panel);

    this.panel.component = this;
    this.stdin.onkeypress = this.panel.component.keyHandler;
    this.print("Welcome to WASH SE, the Web Application SHell - Simplified Edition\n");
    this.print("(C)2004,2008 psema4Technologies\n");
    this.print("Licensed under the LGPL v2.1\n\n");
    this.print("Type ? for help.\n\n");
    if (opts && opts.show) { this.stdin.focus(); }
  }

  this.show = function() {
    this.panel.style.visibility = 'visible';
    this.stdin.focus();
  }

  this.hide = function() {
    this.panel.style.visibility = 'hidden';
  }

  this.print = function(buf) {
    this.stdout.value += buf;
    this.stdout.scrollTop = this.stdout.scrollHeight;
  }

  this.keyHandler = function(evt) {
    // NOTE: In this routine the keyword 'this' points to the element that received the event -
    //        not the WASH object.
    var e = (evt) ? evt : window.event;

    if (e.keyCode && e.keyCode === 13) {
      var Console = this.parentNode.component;
      if (Console) {
        Console.exec(Console.stdin.value);
        Console.stdin.value = '';
        Console.print("\n");
      } else {
        alert('WASH.keyHandler(): NO CONSOLE!');
      }
    }
  }

  this.exec = function(cmdline) {
    var argv = cmdline.split(/\s+/);
    var cmd = (argv[0]) ? argv[0] : 'NOP';
    var args = '';
    for (var i=1; i<argv.length; i++) { args += argv[i] + ' '; }
    args = args.replace(/\s+$/, '');

    if (cmd.toLowerCase() === 'nop') { return; }
    this.print(cmdline + "\n");

    switch(cmd.toLowerCase()) {
      case 'cat':
        var microFile = document.getElementById(argv[1]);
        if (microFile
            && (microFile.className === 'code'
             || microFile.className === 'data')
            && microFile.childNodes[0].nodeType === 3
           ) { 
          var code = microFile.childNodes[0].data + ''
          code = code.replace(/&lt;/g, '<'); // fix html entities
          this.print(code + "\n");
        }
        break;

      case 'clear':
        this.stdout.value = '';
        break;

      case 'echo':
        this.print(args + "\n");
        break;

      case 'eval':
        var retval = eval(args+'');
        if (retval !== undefined) { this.print(retval + "\n"); }
        break;

      case 'go':
        window.Console.print("Going to '" + argv[1] + "'\n");
        window.location = argv[1];
        break;

      case '?':
      case 'help':
        this.print("WASH Builtins\n\n");
        this.print("  [filename] .............. Read and execute (if possible) a microfile\n");
        this.print("  cat [filename] .......... Dumps the contents of a microfile\n");
        this.print("  clear ................... Clears the console output\n");
        this.print("  echo [text] ............. Echoes [text] to the console output\n");
        this.print("  eval [expression] ....... Evaluates a JavaScript [expression]\n");
        this.print("  go [url] ................ Go to a new URL.  Warning: Replaces this web page!\n");
        this.print("  help, ? ................. Displays the WASH Builtins help screen\n");
        this.print("  ls ...................... Lists microfiles in this HTML document\n");
        this.print("  reboot .................. Reload the current URL.  Warning for 'go' applies here too.\n");
        break;

      case 'ls':
        var files = new Array();
        var els = document.getElementsByTagName('div');
        this.print("Listing microfiles:\n\n");

        for (var i=0; i < els.length; i++) {
          if (els[i].className === 'code' || els[i].className === 'data') { files.push(els[i]); }
        }
        for (var i=0; i < files.length; i++) {
          Console.print("  " + files[i].id + "\n");
        }
        break;

      case 'reboot':
        window.location = window.location + '';
        break;

      default:
        var microFile = document.getElementById(cmd);
        if (microFile) {
          if (microFile.className === 'code' && microFile.childNodes[0].nodeType === 3) {
            var code = microFile.childNodes[0].data + ''
            code = code.replace(/&lt;/g, '<'); // fix html entities
            var retval = eval(code);
            if (retval !== undefined) { this.print(retval + "\n"); }
          } else {
            this.print("Command not executable.\n");
          }
        } else {
          this.print("Command not found.\n");
        }
    }
  }

  this.init(this.opts);
}
