/// @bug SISC-only
function lispEscapeStr (s) {
  s = s.replace (/\\/g, "\\\\");
  s = s.replace (/\"/g, '\\"');
  s = s.replace (/\ /g, '\\u0020');
  return '"' + s + '"';
}

function lispUnescapeStr (s) {
  return eval (s);
}

Object.extend (jsGraphics.prototype, {
  setJGOptions: function (opt) {
    this.setStroke (opt.width);
    this.setColor (opt.color);
  },

  draw_rect: function (x1, y1, x2, y2, opt) {
    this.setJGOptions (opt);
    var w = x2 - x1, h = y2 - y1;
    if (opt.fill)
      this.fillRect (x1, y1, w, h);
    else
      this.drawRect (x1, y1, w, h);
  },

  draw_line: function (x1, y1, x2, y2, opt) {
    this.setJGOptions (opt);
    this.drawLine (x1, y1, x2, y2);
  },

  draw_ellipse: function (x1, y1, x2, y2, opt) {
    this.setJGOptions (opt);
    var w = x2 - x1, h = y2 - y1;
    if (opt.fill)
      this.fillEllipse (x1, y1, w, h);
    else
      this.drawEllipse (x1, y1, w, h);
  }
});

var Tool = Class.create();
Tool.byName = new Object ();

Tool.prototype = {
  initialize: function (dp) {
    this.dp = dp;
    this.pts = [];
  },

  initFromCmds: function (cmds) {},

  getName: function () {},
  getCmdStr: function (opt) {},

  onmousedown: function (p) {},
  onmousemove: function (p) {},
  onmouseup: function (x, y) {},

  draw: function (jg, opt) {},
  start: function () { this.pts = []; },

  time_q: function () { return 50; },

  moveLastPoint: function (p) { this.pts.pop (); this.pts.push (p); },
  optToStr: function (opt) {
    return (opt.fill ? "#t" : "#f")
      + " " + opt.width + " " + lispEscapeStr (opt.color);
  }
}

SimpleTool = Class.create (); Object.extend (SimpleTool.prototype, Tool.prototype);
Object.extend (SimpleTool.prototype, {
  initFromCmds: function (cmds) {
    this.start ();
    this.pts.push ([parseInt (cmds [1]), parseInt (cmds [2])]);
    this.pts.push ([parseInt (cmds [3]), parseInt (cmds [4])]);
  },

  getCmdStr: function (opt) {
    var pts = this.pts;
    var cmd = "(" + this.getName () + " "
      + pts [0] [0] + " " + pts [0] [1] + " "
      + pts [1] [0] + " " + pts [1] [1] + " "
      + this.optToStr (opt) + ")";
    return cmd;
  },

  onmousedown: function (p) {
    this.start ();
    this.pts.push (p);
    this.pts.push (p);
  },

  onmousemove: function (p) {
    this.moveLastPoint (p);
    this.dp.jgn.clear ();
    this.draw (this.dp.jgn, this.dp.getOptions ());
    this.dp.jgn.paint ();
  },

  onmouseup: function (p) {
    this.moveLastPoint (p);
    this.draw (this.dp.jg, this.dp.getOptions ());
    this.dp.jgn.clear ();
    this.dp.jg.paint ();
  },

  draw: function (jg, opt) {
    var pts = this.pts;
    eval ("jsGraphics.prototype.draw_" + this.getName ())
      .call (jg, pts [0] [0], pts [0] [1], pts [1] [0], pts [1] [1], opt);
  }
});

FreeTool = Class.create (); Object.extend (FreeTool.prototype, Tool.prototype);
Object.extend (FreeTool.prototype, {
  getName: function () { return "freehand" },

  getCmdStr: function (opt) {
    var pts = this.pts;
    var cmd = "(" + "polyline" + " ";
    for (var i = 0; i < this.pts.length; i++)
      cmd += this.pts [i] [0] + " " + this.pts [i] [1] + " ";
    cmd += this.optToStr (opt) + ")";
    return cmd;
  },

  initFromCmds: function (cmds) {
    this.start ();
    for (var i = 1; i < cmds.length - 3; i += 2)
      this.pts.push ([parseInt (cmds [i]), parseInt (cmds [i + 1])]);
  },

  onmousedown: function (p) { this.start (); this.pts.push (p); },

  onmousemove: function (p) {
    var last = this.pts [this.pts.length - 1];
    this.dp.jg.draw_line (
      last [0], last [1], p [0], p [1], this.dp.getOptions ());
    this.pts.push (p);
    this.dp.jg.paint ();
  },

  onmouseup: function (p) {
    this.onmousemove (p);
  },

  draw: function (jg, opt) {
    var last = this.pts [0];
    for (var i = 1; i < this.pts.length; i++) {
      this.dp.jg.draw_line (this.pts [i - 1] [0], this.pts [i - 1] [1], this.pts [i] [0], this.pts [i] [1], opt);
    }
  },

  time_q: function () { return 0; }
});

TextTool = Class.create ();
Object.extend (TextTool.prototype, SimpleTool.prototype);
Object.extend (TextTool.prototype, {
  getName: function () { return "text"; },
  topt: {},

  initFromCmds: function (cmds) {
    this.start ();
    this.pts.push ([parseInt (cmds [1]), parseInt (cmds [2])]);
    this.topt.text = lispUnescapeStr (cmds [3]);
    this.topt.font = lispUnescapeStr (cmds [4]);
    this.topt.fontSize = cmds [5];
  },

  getCmdStr: function (opt) {
    var pts = this.pts;
    var topt = this.topt;
    var cmd = "(" + this.getName () + " "
      + pts [0] [0] + " " + pts [0] [1] + " "
      + lispEscapeStr (topt.text) + " " + lispEscapeStr (topt.font) + " "
      + topt.fontSize + " "
      + this.optToStr (opt) + ")";
    return cmd;
  },

  onmousedown: function (p) {
    this.start ();
    this.pts.push (p);
    this.topt = this.dp.getTextOptions ();
    this.onmousemove (p);
  },

  time_q: function () { return 0; },

  draw: function (jg, opt) {
    var pt = this.pts [0], topt = this.topt;
    jg.setJGOptions (opt);
    jg.setFont (topt.font, topt.fontSize + "px", Font.PLAIN);
    jg.drawString (this.topt.text, pt [0], pt [1]);
  }

});

var Drawpad = Class.create();
Drawpad.prototype = {
  initialize: function (elem, graphics, newShape, ctl, cmdHist0) {
    this.elem = $(elem);
    Element.makePositioned(this.elem);

    this.graphics = $(graphics);
    this.jg = new jsGraphics (graphics);
    this.newShape = $(newShape);
    this.jgn = new jsGraphics (newShape);
    this.controls     = $(ctl);
    this.active       = false;
    this.dragging     = false;
    this.newShapePaintTimer = null;

    this.cmdHist = cmdHist0 ? cmdHist0 : [];
    this.redoHist = [];

    this.toolSelButtons = Form.getInputs (this.controls, "radio", "tool");
    this.initTools ();

    this.registerEvents();

    this.execCmdStrArray (this.cmdHist);
    this.jg.paint ();
    this.enableUnRedo ();

    this.modified = false;
  },

  registerEvents: function() {
    var __this = this;

    var doBind = function (ev) {
      Event.observe (__this.elem, ev,
                     __this ['on' + ev].bindAsEventListener (__this));
    };

    doBind ("mouseup"); doBind ("mousemove"); doBind ("mousedown");

    var tsb = this.toolSelButtons;
    var clickHnd = {
      hnd: function (event) {
        __this.selectTool (Event.element (event).value);
      }
    };
    for (var i = 0; i < tsb.length; i++) {
      Event.observe (tsb [i], "click",
        clickHnd.hnd.bindAsEventListener (clickHnd));
      if (tsb [i].checked) {
        this.selectTool (tsb [i].value);
      }
    }

    this.controls.onreset = function () {
      __this.jg.clear ();
      while (__this.cmdHist.length > 0)
        __this.redoHist.push (__this.cmdHist.pop ());
      __this.enableUnRedo ();
      return false; 
    }
    this.controls.onsubmit = function () {
      __this.controls.commandsSum.value = __this.cmdHist.join ('\n');
      return true;
    }
    this.controls.undoButton.onclick = function () {
      if (__this.cmdHist.length > 0) {
        __this.redoHist.push (__this.cmdHist.pop ());
        // double buffering
        var t = __this.jg; __this.jg = __this.jgn; __this.jgn = t;
        __this.execCmdStrArray (__this.cmdHist);
        __this.jg.paint ();
        __this.jgn.clear ();
        __this.enableUnRedo ();
      }
      return false;
    }
    this.controls.redoButton.onclick = function () {
      var l = __this.redoHist.length;
      if (l > 0) {
        var cmd = __this.redoHist.pop ();
        __this.execCmdStr (cmd);
        __this.recordCmd (cmd, true);
        __this.jg.paint ();
      }
      return false;
    }
  },

  initTools: function () {
    var __this = this;
    ['line', 'rect', 'ellipse'].map (
      function (name) {
        var className = name.charAt (0).toUpperCase ()
          + name.substr (1, name.length - 1) + "Tool";
        newClass = eval (className + " = Class.create ();");
        Object.extend (newClass.prototype, SimpleTool.prototype);
        newClass.prototype.getName = function () { return name };
        Tool.byName [name] = new newClass (__this);
      });
    Tool.byName ['freehand'] = Tool.byName ['polyline'] = new FreeTool (this);
    Tool.byName ['text'] = new TextTool (this);
  },

  destroy: function() {
  },

  execCmdStr: function (cmdStr) {
    this.modified = true;
    cmdStr = cmdStr.substr (1, cmdStr.length - 2);
    var cmds = cmdStr.split (" ");
    var l = cmds.length;
    var tool = Tool.byName [cmds [0]];
    if (! tool) {
      alert ("No tool found for " + cmds [0]);
      return;
    }
    tool.initFromCmds (cmds);
    tool.draw (this.jg, {
      fill: (cmds [l - 3] == "#t"),
      width: parseInt (cmds [l - 2]),
      color: eval (cmds [l - 1]) });
  },

  execCmdStrArray: function (cmds) {
    for (i = 0; i < cmds.length; i++)
      this.execCmdStr (cmds [i]);
  },

  recordCmd: function (cmd, isRedo) {
    this.cmdHist.push (cmd);
    if (! isRedo)
      this.redoHist = [];
    this.enableUnRedo ();
  },

  enableUndo: function () { this.controls.undoButton.disabled = (this.cmdHist.length == 0); },
  enableRedo: function () { this.controls.redoButton.disabled = (this.redoHist.length == 0); },
  enableUnRedo: function () { this.enableRedo (); this.enableUndo (); },

  currentLeft: function() {
    return parseInt(this.elem.style.left || '0');
  },
  currentTop: function() {
    return parseInt(this.elem.style.top || '0')
  },

  computeCoords: function (event) {
    var pointer = [Event.pointerX(event), Event.pointerY(event)];
    var offsets = Position.cumulativeOffset(this.elem);
    // offsets[0] -= this.currentLeft();
    // offsets[1] -= this.currentTop();
    
    return [pointer[0] - offsets[0], pointer[1] - offsets[1]];
  },

  getOptions: function () {
    return {
      fill: this.controls.fillChkBox.checked,
      width: parseInt (this.controls.widthField.value),
      color: this.controls.colorField.value
    };
  },

  getTextOptions: function () {
    return {
      font: this.controls.fontField.value,
      fontSize: parseInt (this.controls.fontSizeField.value),
      text: this.controls.textField.value      
    };
  },

  updateShape: function (pt) {
    this.newShapePaintTimer = null;
    if (this.dragging) {
      this.toolObj.onmousemove (pt);
     }
  },

  finishDrag: function(event, success) {
    clearTimeout (this.newShapePaintTimer);
    this.newShapePaintTimer = null;

    var pt = this.computeCoords (event);
    var opt = this.getOptions ();
    this.toolObj.onmouseup (pt);
    this.modified = true;
    this.recordCmd (this.toolObj.getCmdStr (opt));
  },

  onmousedown: function(event) {
    if (this.active && this.dragging) return;
    // abort on form elements, fixes a Firefox issue
    var src = Event.element(event);
    var t = src.tagName;
    if(t && (t=='INPUT' || t=='SELECT' || t=='BUTTON' || t=='TEXTAREA'))
      return;
    
    // this.registerEvents();
    this.active = true;
    var pt = this.computeCoords (event);
    this.lastPoint = pt;
    this.toolObj.onmousedown (pt);
    Event.stop(event);
  },

  onmousemove: function(event) {
   if(this.active) {
     if(!this.dragging)
       this.dragging = true;

     var pt = this.computeCoords (event);
     var __this = this;
     var d = Math.abs (pt [0] - this.lastPoint [0])
       + Math.abs (pt [1] - this.lastPoint [1]);
     this.lastPoint = pt;
     if (d < 2) return;
     var t = this.toolObj.time_q ();
     if (this.newShapePaintTimer != null) {
       clearTimeout (this.newShapePaintTimer);
       this.newShapePaintTimer = null;
     }
     if (t > 0)
       this.newShapePaintTimer = setTimeout (
        function () { __this.updateShape (pt); }, t);
     else
       this.updateShape (pt, t);
     Event.stop(event);
   }
  },

  onmouseup: function(event) {
    if(this.active) {
      this.finishDrag(event, true);
      Event.stop(event);
    }
    this.active = false;
    this.dragging = false;
  },

  // turned out useless
  onclick: function(event) {
    this.onmousedown (event); this.onmouseup (event);
  },

  selectTool: function(name) {
    this.tool = name;
    this.toolObj = Tool.byName [name];
  }
}
