/*
   This file is part of the paintmyblog.com web client
   Copyright 2007 Karl Henrik Falck <f@lck.nu>
*/

var brushes = $A(
		 [
		  null,
  {file: "paintbrush.png", w: 12, h: 12}
		  ]);

brushes.each(function(b)
	     {
	       if (!b) return;
	       b.img = new Image();
	       b.img.src = b.file;
	     });

var Painter = Class.create();
Painter.prototype = {
  PENCIL: 1,
  PEN: 2,
  MARKER: 3,
  BRUSH: 4,

  LINE: 1,
  SPRITE: 2,

  PAINTBRUSH: 1,

  NONE: 1,
  WEAK: 2,
  MEDIUM: 3,
  STRONG: 4,

  setToolFeatures:
  [
   null,
   function(p) {
     p.alpha = 0.6;
     p.minDist = 3;
     p.thickness = 4;
     p.mode = p.LINE;
     p.lineCap = "round";
     p.response = p.MEDIUM;
   },
   function(p) {
     p.alpha = 0.9;
     p.minDist = 9;
     p.thickness = 2;
     p.mode = p.LINE;
     p.lineCap = "round";
     p.response = p.WEAK;
   },
   function(p) {
     p.alpha = 0.4;
     p.minDist = 12;
     p.thickness = 17;
     p.mode = p.LINE;
     p.lineCap = "butt";
     p.response = p.STRONG;
   }, 
   function(p) {
     p.alpha = 0.5;
     p.minDist = 0;
     p.mode = p.SPRITE;
     p.brush = p.PAINTBRUSH;
     p.response = p.MEDIUM;
   }
  ],

  colors:
  [
   null,
   "#DA70D6", // orchid
   "#F400A1", // Hollywood cerise
   "#FF007F", // rose
   "#FF2400", // scarlet
   "#FF7E00", // amber
   "#800020", // Burgundy
   "#FDE910", // lemon
   "#BFFF00", // lime
   "#00FF7F", // spring green
   "#808000", // olive
   "#228b22", // forest green
   "#008080", // teal
   "#1C39BB", // Persian blue
   "#007FFF", // azure
   "#7DF9FF", // electric blue
   "#F5F5DC", // beige
   "#FBCEB1", // apricot
   "#CDB891", // ecru
   "#EEDC82", // flax
   "#734A12", // raw umber
   "#C0C0C0", // silver
   "#464646", // charcoal
   ],
  
  initialize: function(id) {
    this.id = id;
    this.actions = new Array();
    this.playidx = 0;
    this.lastpos = [0, 0];
    this.open = false;
    this.toolNames =
    {
      "pencil": this.PENCIL,
      "pen": this.PEN,
      "marker": this.MARKER,
      "brush": this.BRUSH
    };
    this.forceDirty = true;
    this.executing = false;
  },

  startLine: function(x, y) {
    this.checkDirty();
    this.open = true;
    this.curx = x;
    this.cury = y;
    this.lastx = x;
    this.lasty = y;
    this.lasta = 0;
    this.segments = 0;
    this.startTime = getTime();
  },

  continueLine: function(x, y) {
    if (!this.open)
      return;
    var int = dist(this.lastx, this.lasty, x, y)
    if (int < 1)
      return;
    var d = dist(this.curx, this.cury, x, y);
    if (this.minDist && d < this.minDist)
      return;
    var dx = x - this.lastx;
    var dy = y - this.lasty;
    var a = Math.atan2(dy, dx);
    var da = Math.abs(this.lasta - a);
    var store = true;
    if ((d < 15 || int < 3) && da * (0.9 + int) < 0.1)
      store = false;
    else if ((d >= 15 && int >= 3) && da * d < 100)
      store = false;
    if (directPaint && this == mypainter)
      {
	this.executeAll();
	this.draw(actions.DRAW_LINE, this.lastx, this.lasty, x, y);
      }
    this.lasta = a;
    this.lastx = x;
    this.lasty = y;
    if (!store) return;
    var time = getTime() - this.startTime;
    this.checkDirty();
    this.actions.push([actions.DRAW_LINE, this.curx, this.cury, x, y, time]);
    this.startTime = getTime();
    this.segments++;
    this.curx = x;
    this.cury = y;
    return true;
  },

  closeLine: function(x, y) {
    if (!this.open)
      return;
    this.open = false;
    this.checkDirty();
    if (this.segments == 0 && dist(this.curx, this.cury, x, y) < 5)
      this.actions.push([actions.DRAW_DOT, x, y]);
    else
      this.actions.push([actions.DRAW_LINE, this.curx, this.cury, x, y, getTime() - this.startTime, true]);
    this.startTime = null;
  },

  changeToolByName: function(toolName) {
    var tool = this.toolNames[toolName];
    this.changeTool(tool);
  },

  changeTool: function(tool) {
    this.dirtyTool = tool;
  },

  changeWidth: function(width) {
    if (width > 2.0) width = 2.0;
    else if (width < 0.5) width = 0.5;
    this.dirtyWidth = width;
  },

  changeSwatch: function(swatch) {
    this.dirtySwatch = swatch;
  },

  setTool: function(tool) {
    this.tool = tool;
    var fn = this.setToolFeatures[tool];
    fn(this);
  },

  setWidth: function(width) {
    this.width = width;
  },

  setSwatch: function(swatch) {
    this.swatch = swatch;
    this.color = this.colors[swatch];
  },

  setDirty: function() {
    this.dirtyTool = this.tool;
    this.dirtyWidth = this.width;
    this.dirtySwatch = this.swatch;
    this.forceDirty = true;
  },

  checkDirty: function() {
    if (this.dirtySwatch && (this.forceDirty || this.dirtySwatch != this.swatch))
      this.actions.push([actions.CHANGE_SWATCH, this.dirtySwatch]);
    if (this.dirtyTool && (this.forceDirty || this.dirtyTool != this.tool))
      this.actions.push([actions.CHANGE_TOOL, this.dirtyTool]);
    if (this.dirtyWidth && (this.forceDirty || this.dirtyWidth != this.width))
      this.actions.push([actions.CHANGE_WIDTH, this.dirtyWidth]);
    this.dirtyTool = this.dirtyWidth = this.dirtySwatch = null;
    this.forceDirty = false;
  },

  hasMore: function() {
    return (mypainter == this && this.playidx < this.actions.length) || (this.executing && this.actions.length > 0);
  },

  hasUnsent: function() {
    return !this.executing && this.actions.length > 0;
  },

  numUnsent: function() {
    return this.actions.length;
  },

  play: function(actions) {
    if (this == mypainter) alert("bug");
    this.executing = true;
    for (var i = 0; i < actions.length; i++)
      this.actions.push(actions[i]);
    setTimeout(this.scheduleExecute.bind(this), 100);
  },

  scheduleExecute: function() {
    if (!this.hasMore() || fastForward || stop) {
      this.executing = false;
      if (this.hasMore()) this.actions = [];
      return;
    }
    var a = this.actions.shift();
    this.execute(a);
    var b = this.actions[1];
    if (b && b[0] == actions.DRAW_LINE && b[5])
      var delay = Math.max(10, b[5]);
    else
      var delay = 100 + parseInt(500 * Math.random());
    setTimeout(this.scheduleExecute.bind(this), delay);
  },

  execute: function(a) {
    switch (a[0]) {
      case actions.DRAW_LINE:
      if (this != mypainter || fastForward || !directPaint)
	this.draw(actions.DRAW_LINE, a[1], a[2], a[3], a[4], a[5], a[6]);
      break;
      case actions.DRAW_DOT:
      this.draw(actions.DRAW_DOT, a[1], a[2]);
      break;
      case actions.CHANGE_TOOL:
      this.setTool(a[1]);
      break;
      case actions.CHANGE_WIDTH:
      this.setWidth(a[1]);
      break;
      case actions.CHANGE_SWATCH:
      this.setSwatch(a[1]);
      break;
      case actions.DRAW_COORDS:
      this.drawLine(actions.DRAW_COORDS, a);
      break;
    }
    return a;
  },

  executeAll: function() {
    while (this.hasMore())
      this.execute(this.actions[this.playidx++]);
  },

  draw: function(action, x1, y1, x2, y2, time, close) {
    switch (this.mode) {
      case this.LINE:
      this.drawLine(action, x1, y1, x2, y2, time, close);
      break;
      case this.BRUSH:
      this.drawBrush(action, x1, y1, x2, y2, time, close);
      break;
    }
  },

  speedFactor: function(dist, time) {
    if (this.response == this.NONE || !time)
      return 1.0;
    var speed = dist / time;
    if (speed > 0.25) {
      if (speed > 2.0) speed = 2.0;
      var d = 5;
      if (this.response == this.NORMAL)
	d = 6;
      else if (this.response == this.WEAK)
	d = 10;
      else if (this.response == this.STRONG)
	d = 4;
      return 1 - (speed - 0.25) / d;
    }
    return 1.0;
  },
  
  drawLine: function(action, x1, y1, x2, y2, time) {
    ctx.save();
    if (surface) {
	ctx.scale(width / vWidth, height / vHeight);
       	ctx.lineWidth = this.thickness * this.width * scale;
    }
    else {
      ctx.lineWidth = this.thickness * this.width;
    }
    ctx.globalAlpha = this.alpha;
    if (action == actions.DRAW_COORDS) {
      var f = 0.9;
      var coords = x1;
      var simplify = coords[1];
    }
    else if (action == actions.DRAW_DOT) {
      var f = 1.0;
      var dot = true;
    }
    else
      var f = this.speedFactor(dist(x1, y1, x2, y2), time);
    ctx.globalAlpha *= f;
    ctx.lineWidth *= f;
    ctx.strokeStyle = this.color;
    ctx.lineCap = this.lineCap;
    ctx.beginPath();
    if (coords) {
      var len = coords.length;
      if (simplify) {
	ctx.moveTo(coords[2], coords[3]);
	for (var i = 4; i < len; i += 2) {
	  var x = coords[i];
	  var y = coords[i + 1];
	  if (x == null) {
	    ctx.closePath();
	    ctx.stroke();
	    ctx.beginPath();
	    ctx.moveTo(y, coords[i + 2]);
	    ++i;
	  }
	  else
	    ctx.lineTo(x, y);
	}
	ctx.moveTo(coords[len - 2], coords[len - 1]);
      }
      else {
	for (var i = 2; i < len; i += 4) {
	  ctx.moveTo(coords[i], coords[i + 1]);
	  ctx.lineTo(coords[i + 2], coords[i + 3]);
	}
      }
    }
    else if (dot) {
      ctx.arc(x1, y1, 1, 0, 2 * Math.PI, 1);
    }
    else {
      ctx.moveTo(x1, y1);
      ctx.lineTo(x2, y2);
    }
    ctx.closePath();
    if (dot)
      ctx.fill();
    else
      ctx.stroke();
    ctx.restore();
  },

  drawBrush: function(x1, y1, x2, y2, time) {
    var brush = brushes[this.brush];
    var img = brush.img;
    var imgW = brush.w;
    var imgH = brush.h;
    if (!x2) {
      var steps = 1;
      var dx = 0;
      var dy = 0;
      var f = 1.0;
    }
    else {
      var d = dist(x1, y1, x2, y2);
      var steps = 1 + parseInt(d / 20) + parseInt(time / 500);
      var f = 1 / this.speedFactor(d, time);
      var dx = (x2 - x1) / steps;
      var dy = (y2 - y1) / steps;
    }
    if (surface) {
      dx *= xScale;
      dy *= yScale;
      x1 *= xScale;
      y1 *= yScale;
      var w = f * imgW * xScale;
      var h = f * imgH * yScale;
    }
    else {
      var w = f * imgW;
      var h = f * imgH;
    }
    for (var step = 0; step < steps; ++step) {
      var rx = f * f * (2.5 - 5 * Math.random());
      var ry = f * f * (2.5 - 5 * Math.random());
      if (surface) {
	rx *= xScale;
	ry *= yScale;
	ctx.drawImage(img, x1 + dx * step + rx, y1 + dy * step + ry, w, h);
      }
      else {
	ctx.drawImage(img, x1 + dx * step + rx, y1 + dy * step + ry, w, h);
      }
    }
  }
}
