// Copyright (C) 2009 Chris Double.
// 
// 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.
// 
// THIS SOFTWARE IS PROVIDED ``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
// DEVELOPERS AND CONTRIBUTORS 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.
//
function XYRepl(id) {
  var self = this; 
  var foreign = [
    new TokenForeign(".s", function(xy) {
      self.println(xy.prettyX());
    }),
    new TokenForeign(".x", function(xy) {
      self.println(xy.prettyX());
    }),
    new TokenForeign(".y", function(xy) {
      self.println(xy.prettyY());
    }),
    new TokenForeign(".", function(xy) {
      var x = xy.x.pop();
      self.println(x.toString());
    }),
    new TokenForeign("alert", function(xy) {
      alert(xy.x.pop());
    }),
    new TokenForeign("set-timeout", function(xy) {
      var q = xy.x.pop();
      var t = xy.x.pop();
      var r = setTimeout(function() {
      var queue = new Queue();
      queue.enqueueHeads(q.value);
      var inner = new XY(xy.env, [], queue);
      while(inner.y.getSize() > 0)
        inner.eval1();
      }, t.value);
    }),
    new TokenForeign("js-global", function(xy) {
      var n = xy.x.pop();
      var o = eval(n.value);
      xy.x.push(new TokenJavaScript(o)); 
    }),
    new TokenForeign("js-call", function(xy) {
      var func = xy.x.pop().value;
      var obj  = xy.x.pop().value;
      var args = xy.x.pop().value;
      var marshalled = [];
      for(var i=0; i<args.length; ++i)
        marshalled.push(args[i].value);
      var r = func.apply(obj, marshalled);
      xy.x.push(new TokenJavaScript(r));
    }),
    new TokenForeign(">js-function", function(xy) {
      var quot = xy.x.pop().value;
      xy.x.push(new TokenJavaScript(function() {
        var queue = new Queue();
        queue.enqueueHeads(quot);
        var inner = new XY(xy.env, [], queue);
        while(inner.y.getSize() > 0)
          inner.eval1();
      }));
    })
  ];
  function tmp() {
    for(var i=0; i < foreign.length; ++i) 
      this[foreign[i].value] = new TokenList([foreign[i]]);
  }
  tmp.prototype = xyenv;
        
  this.xy = new XY(new tmp(), [], new Queue());
  this.repl = $("#" + id)[0];
  this.input = document.createElement("textarea");
  this.output = document.createElement("div");
  this.input.wrap = "off";
  this.input.rows=1;
  $(this.input).keypress(function(event) {
    return self.inputKeyPress(event);
  });
  $(this.input).bind("keyup","Ctrl+up", function() {
    if (self.histIndex >  -1)
      self.input.value = self.hist[self.histIndex--];
  });
  $(this.input).bind("keyup","Ctrl+down", function() {
    if (self.histIndex < self.hist.length-1)
      self.input.value = self.hist[++self.histIndex];
  });
  $(this.repl).bind("click", function(event) {
    return self.keepFocusInTextBox(event);
  });
  $(this.input).css({"width" : "100%",
                     "border" : "none",
                     "padding" : "0",
                     "overflow" : "auto" });
  $(this.repl).append(this.output);
  $(this.repl).append(this.input);
  this.hist = [];
  this.histindex = -1;
}

XYRepl.prototype.inputKeyPress = function(e) {
  var self = this;
  if (e.which == 13 && !e.shiftKey) { // enter
    try {
      this.evalInput();
    } 
    catch(e) {
      this.hist.push(this.input.value);
      this.histIndex = this.hist.length-1;
      this.printboldln("Error: " + e.toString());
    }
    setTimeout(function() { self.input.value = ""; }, 0);
  }
  setTimeout(function() { self.recalculateInputHeight(); }, 0);
}

XYRepl.prototype.evalInput = function() {
  this.printboldln(this.input.value);
  var ast = parse(tokenize(this.input.value));
  this.xy.y.enqueueHeads(ast);
  while(this.xy.y.getSize() > 0)
    this.xy.eval1();
  this.hist.push(this.input.value);
  this.histIndex = this.hist.length-1;
}

XYRepl.prototype.println = function(s) {
  var div = document.createElement("div");

  div.appendChild(document.createTextNode(s));
    this.output.appendChild(div);
}

XYRepl.prototype.printboldln = function(s) {
  var div = document.createElement("div");
  var bold = document.createElement("strong");
  bold.appendChild(document.createTextNode(s));
  div.appendChild(bold);
  this.output.appendChild(div);
}

XYRepl.prototype.keepFocusInTextBox = function(e) 
{
  var g = e.srcElement ? e.srcElement : e.target; // IE vs. standard
  
  while (!g.tagName)
    g = g.parentNode;
  var t = g.tagName.toUpperCase();
  if (t=="A" || t=="INPUT")
    return;
    
  if (window.getSelection) {
    // Mozilla
    if (String(window.getSelection()))
      return;
  }
  else if (document.getSelection) {
    // Opera? Netscape 4?
    if (document.getSelection())
      return;
  }
  else {
    // IE
    if ( document.selection.createRange().text )
      return;
  }
  this.refocus();
}

XYRepl.prototype.refocus = function() {
  this.input.blur();
  this.input.focus();  
}

XYRepl.prototype.recalculateInputHeight = function()
{
  var rows = this.input.value.split(/\n/).length
    + 1 // prevent scrollbar flickering in Mozilla
    + (window.opera ? 1 : 0); // leave room for scrollbar in Opera
  
  if (this.input.rows != rows) // without this check, it is impossible to select text in Opera 7.60 or Opera 8.0.
    this.input.rows = rows;
}
 
