/* ----------------------------------------------------------------------
 * File:        webmud.js
 * Description: Javascript part of the UNItopia Webmud Client.
 *              Depends on the jQuery library and AJAX (www.jquery.com)
 * Author:      Andreas Klauer (Andreas.Klauer@metamorpher.de)
 * Version:     0.2 (2007-08-11)
 * License:     GPL
 * Changes:
 * ---- 2007-08-11 -- Andreas Klauer ----
 *   Internet Explorer eats whitespace in pre-tags, so we have to &nbsp;
 * ----------------------------------------------------------------------
 * This file is part of Webmud Client.
 * 
 * Webmud Client is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Webmud Client is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Webmud Client; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 * ----------------------------------------------------------------------
 */

/*
 * TODO:  Remove jQuery dependency. I like jQuery, it makes development
 * TODO:: and trying stuff out easy, but now that this thing is up and 
 * TODO:: running, we use hardly any of its features (Ajax and Append only)
 * TODO:: This program is only ~10k or so, but jQuery+Ajax adds another 70k.
 */

var callback;

/* 
 * The ANSI Object takes care of terminal output conversion to HTML.
 */
function ANSI()
{
  /* set ANSI color back to normal: */
  this.norm = function() {
    this.foreground = "d";
    this.background = "d";
    this.bold = false;
    this.faint = "";
    this.italic = false;
    this.underlined = false;
    this.blink = false;
    this.reverse = false;
    this.concealed = false;
    this.sub = false;
    this.sup = false;
    this.changed = true;
  };

  /* Initialize this object in the normal state: */
  this.norm();

  /*
   * The following will be converted:
   *   - ANSI Color Code to <span>-Tag with CSS id.
   *     (unsupported ANSI escape codes will be ignored)
   *   - HTML characters < > & to &lt; &gt; &amp;
   *   - Ignore / Filter unwanted nonprintable characters.
   */

  this.pattern = /([\s\S]*?)(\x1B\[[0-9;]*[a-zA-Z]|\x1B.|\r*\n|\r|[\x07<>&]| )|([\s\S]+)/gm;
  this.pattern_number = /([0-9]+)/gm;
  this.colors = "srgybmcw";
  this.buffer = "";
  this.spanopen = false;

  this.add = function(txt) {
    if(this.spanopen && this.changed)
    {
      /* close spantag */
      this.buffer += "</span>";
      this.spanopen = false;
    }

    if(!this.spanopen)
    {
      var fg, bg;

      if(this.reverse)
      {
        /* TODO: Some terminals do some special treatment for b/b w/w reverse.
         *       Maybe this should be optionally added here (you'd better fix 
         *       broken color codes of your MUD, though)
         */
        fg = (this.background == "d" ? "dr" : this.background);
        bg = (this.foreground == "d" ? "dr" : this.foreground);
      }

      else
      {
        fg = this.foreground;
        bg = this.background;
      }

      if(this.concealed)
      {
        bg = fg;
      }

      /* open spantag */
      this.buffer += "<span class=\"" 
             + fg + this.faint
      + " x" + bg + this.faint
      + (this.bold       ? " bo" : "")
      + (this.italic     ? " it" : "")
      + (this.underlined ? " un" : "")
      + (this.blink      ? " bl" : "")
      + (this.sub        ? " sb" : "")
      + (this.sup        ? " sp" : "")
      + "\">";
      this.spanopen = true;
      this.changed = false;
    }

    this.buffer += txt;
  };

  this.addansi = function(txt) {
    var ergebnis;

    switch(txt.charAt(txt.length - 1))
    {
      case "\r":
      case "\n":
        /* Take care of \r\n MSIE madness. */
        this.add("&nbsp;<br\u000A>");
        break;

      case "m":
        this.pattern_number.lastIndex = 0;
        this.changed = true;

        /* graphics parameters */
        while(ergebnis = this.pattern_number.exec(txt))
        {
          var i = parseInt(ergebnis[1]);

          if(i >= 30 && i <= 37)
          {
            this.foreground = "srgybmcw".charAt(i-30);
          }

          else if(i >= 40 && i <= 47)
          {
            this.background = "srgybmcw".charAt(i-40);
          }

          else 
          {
            switch(i)
            {
              case 0:
                this.norm();
                break;
              case 1:
                this.bold = true;
                break;
              case 2:
                this.faint = "f";
                break;
              case 3:
                this.italic = true;
                break;
              case 4:
                this.underlined = true;
                break;
              case 5:
              case 6:
                this.blink = true;
                break;
              case 7:
                this.reverse = true;
                break;
              case 8:
                this.concealed = true;
                break;
              case 48:
                this.sub = true;
                break;
              case 49:
                this.sup = true;
                break;

              /* Fake ANSI */
              case 200:
                /* receive request */
                $.get("webmud.py/receive", 0, callback);
                break;

              case 251:
                /* will echo request */
                try { document.forms["control"].elements["command"].type = "password"; }
                catch(e) { /* Internet Explorer does not support this command. */ }
                this.echo = false;
                break;

              case 252:
                /* wont echo request */
                try { document.forms["control"].elements["command"].type = "text"; }
                catch(e) { /* Internet Explorer really sucks. Use Firefox or Opera. */ }
                this.echo = true;
                break;
            }
          }
        }

        break;

      case "c":
        /* reset */
        this.norm();
    }
  };

  this.convert = function(txt) {
    var ergebnis;

    // Reuse Regexp object from before:
    this.pattern.lastIndex = 0;

    while(ergebnis = this.pattern.exec(txt))
    {
      /* IE returns empty string, Firefox/Opera 'undefined'. */
      if(ergebnis[3] && ergebnis[3].length)
      {
        this.add(ergebnis[3]);
      }

      else
      {
        /* this is harmless and can just be added: */
        if(ergebnis[1].length)
        {
          this.add(ergebnis[1]);
        }

        /* now we have something special: */
        switch(ergebnis[2])
        {
          case "\x07":
            /* ignore bell char for now */;
            break;

          case "<":
            this.add("&lt;");
            break;

          case ">":
            this.add("&gt;");
            break;

          case "&":
            this.add("&amp;");
            break;

          case " ":
            this.add("&nbsp;");
            break;

          default:
            /* ANSI Escape or Carriage Return / Newline Sequence */
            this.addansi(ergebnis[2]);
            break;
        }
      }
    }

    if(this.spanopen)
    {
      this.buffer += "</span>";
      this.spanopen = false;
    }

    return true;
  };

  /* 
   * Fake ANSI for ECHO telnet negotiation: 
   *
   * I would like to send the actual telnet negotiation data, 
   * but JavaScript thinks it's binary and bails out. Due to
   * lack of another solution, we send IAC WILL ECHO as 251m 
   * and IAC WONT ECHO as 252m fake ANSI color code.
   */

  this.echo = true;

  /*
   *   flush buffer into element & autoscroll function
   */
  this.flush = function(el) {
    /* Determine Autoscroll */
    var scroll = el.scrollTop;
    el.scrollTop = el.scrollHeight;

    if(el.scrollTop - scroll < 10)
    {
      /* that's almost the same. scroll to bottom later. */
      scroll = true;
    }

    else
    {
      /* undo and then let the browser decide what to do */
      el.scrollTop = scroll;
      scroll = false;
    }

    /* Insert the Text */
    $(el).append(this.buffer);
    this.buffer = "";

    /* Scroll to bottom if necessary. */
    if(scroll)
    {
      el.scrollTop = el.scrollHeight;
    }
  };
}

var terminal = new ANSI();

callback = function(txt)
{
  if(txt && txt.length && terminal.convert(txt))
  {
    /* Okay, we got a valid reply, show it. */
    terminal.flush(document.getElementById("terminal"));
  }
}


function webmud_send(cmd)
{
  if(terminal.echo)
  {
    /* local echo hack */
    terminal.buffer += "<span class=\"cmd\">";
    terminal.convert(cmd+"\n");
    terminal.buffer += "</span>";
    terminal.flush(document.getElementById("terminal"));
  }

  $.post("webmud.py/send", { 'command' : cmd }, callback);
  return true;
}

function webmud_connect()
{
  $.get("webmud.py/connect", 0, callback);
  return true;
}

function webmud_disconnect()
{
  $.get("webmud.py/disconnect", 0, callback);
  return true;
}

function webmud_submit()
{
  webmud_send(document.forms["control"].elements["command"].value);
  document.forms["control"].elements["command"].value = "";

  /* Abuse submit function. */
  return false;
}

/* --- End of file. --- */
