/* sylvester_canvasviewer.js is a plugin for Sylvester 
 * to plot 3d graph using canvas (2d context)
 * Public Domain
 */

(function(Sylvester){
  if (!Sylvester) return;
  projectToXY = function(vec){vec.elements.pop(); return this;};

  var Viewer = function(canvas, opt) {
    this.canvas = canvas;
    this.width = opt.width || 500;
    this.height = opt.width || 500;
    this.magnification = opt.magnification || 1.1;
    this.pointSize = opt.pointSize || 2;
    this.pointStyle = opt.pointStyle || 'red';

    /*
     * direction : Vector from which to view the space, default $V([1,1,1])
     * angle : viewing angle as of a compass, default is 0 (ie. north)
     */
    this.direction = opt.direction || $V([5,-4,3]);
    this.angle = opt.angle || 0;
  }

  Viewer.prototype.setup = function() {
    var viewPlane =  Plane.create(Vector.Zero(3), this.direction);
    var declination = Vector.k.angleFrom(viewPlane.normal);
    //var intersection = Plane.XY.intersectionWith(viewPlane);
    var intersection = viewPlane.intersectionWith(Plane.XY);
    if (intersection) {
      var dirx = this.direction.e(1), diry = this.direction.e(2);
      var alpha = - Math.atan2(dirx, -diry) + this.angle;
    } else {
      intersection = $L(Vector.Zero(3), $V([1, -1, 0]));
      var alpha = this.angle;
    }
    var origin = Vector.Zero(2);
    this.convert = function(point) {
      var p = point.dup().rotate(declination, intersection);
      return $V([p.e(1), p.e(2)]).rotate(alpha, origin);
    }
  }

  Viewer.prototype.getBoundaries = function() {
    var 
      points = this.points;
      i = points.length,
      p = points[--i],
      xyz = p.elements;
      minX = xyz[0], maxX = xyz[0],
      minY = xyz[1], maxY = xyz[1],
      minZ = xyz[2], maxZ = xyz[2];
    while (p = points[--i]) {
      xyz = p.elements;
      if (xyz[0] < minX) minX = xyz[0]; else if (xyz[0] > maxX) maxX = xyz[0];
      if (xyz[1] < minY) minY = xyz[1]; else if (xyz[1] > maxY) maxY = xyz[1];
      if (xyz[2] < minZ) minZ = xyz[2]; else if (xyz[2] > maxZ) maxZ = xyz[2];
    }
    return this.boundaries = {minX:minX, maxX:maxX, minY:minY, maxY:maxY, minZ:minZ, maxZ:maxZ};
  }

  Viewer.prototype.getVertices = function() {
    var b = this.boundaries;
    var v = {}; // [[[[],[]],[[],[]]],[[[],[]],[[],[]]]]
    v.v000 = this.convert($V([b.minX, b.minY, b.minZ]));
    v.v100 = this.convert($V([b.maxX, b.minY, b.minZ]));
    v.v010 = this.convert($V([b.minX, b.maxY, b.minZ]));
    v.v001 = this.convert($V([b.minX, b.minY, b.maxZ]));
    v.v110 = this.convert($V([b.maxX, b.maxY, b.minZ]));
    v.v011 = this.convert($V([b.minX, b.maxY, b.maxZ]));
    v.v101 = this.convert($V([b.maxX, b.minY, b.maxZ]));
    v.v111 = this.convert($V([b.maxX, b.maxY, b.maxZ]));

    this.vertices = v;
  }

  Viewer.prototype.drawAxes = function(scaleFunc){
    var ctx = this.canvas.getContext('2d');
    var vert = this.vertices;
    ctx.save();
    ctx.beginPath();
      ctx.strokeStyle = 'black';
        ctx.moveTo.apply(ctx, scaleFunc(vert.v000));
        ctx.lineTo.apply(ctx, scaleFunc(vert.v100));
        ctx.stroke();
          ctx.closePath();
    ctx.beginPath();
      ctx.strokeStyle = 'blue';
        ctx.moveTo.apply(ctx, scaleFunc(vert.v000));
        ctx.lineTo.apply(ctx, scaleFunc(vert.v010));
        ctx.stroke();
          ctx.closePath();
    ctx.beginPath();
      ctx.strokeStyle = 'green';
        ctx.moveTo.apply(ctx, scaleFunc(vert.v000));
        ctx.lineTo.apply(ctx, scaleFunc(vert.v001));
        ctx.stroke();
          ctx.closePath();
    /*
    ctx.beginPath();
      ctx.strokeStyle = 'grey';
        ctx.moveTo.apply(ctx, scaleFunc(vert.v100));
        ctx.lineTo.apply(ctx, scaleFunc(vert.v110));
        ctx.lineTo.apply(ctx, scaleFunc(vert.v010));
        ctx.stroke();
          ctx.closePath();
    */
    ctx.restore();
  }

  Viewer.prototype.addPoints = function(points) {
    if (!this.points) return this.draw(points);
    var _drawAxes = this.drawAxes;
    this.drawAxes = function(){};
    var _points = this.points;
    this.draw(points, true);
    this.drawAxes = _drawAxes;
    this.points = _points.concat(points);
  }

  Viewer.prototype.draw = function(points, reuseSetting) {
    this.points = points;
    if (!reuseSetting) {
      this.setup();
      this.getBoundaries();
      this.getVertices();
    }
    var b = this.boundaries;
    var vert = this.vertices;

    var mid = vert.v000.dup().add(vert.v111).multiply(0.5);
    var midx = mid.e(1);
    var midy = mid.e(2);
    var distance = Math.max(
      vert.v000.dup().subtract(vert.v111).modulus(),
      vert.v100.dup().subtract(vert.v011).modulus(),
      vert.v010.dup().subtract(vert.v101).modulus(),
      vert.v001.dup().subtract(vert.v110).modulus());
    var shortside = Math.min(this.width, this.height);
    var scale = shortside / distance / this.magnification;
    var halfWidth = this.width/2;
    var halfHeight = this.height/2;
    function scaleToCanvas(p) {
      return [halfWidth + (p.e(1) - midx) * scale, halfHeight - (p.e(2) - midy) * scale];
    }

    this.drawAxes(scaleToCanvas);

    var ctx = this.canvas.getContext('2d');
    ctx.save();
    var pointSize = this.pointSize;
    ctx.fillStyle = this.pointStyle;
    var points = this.points;
    var convert = this.convert;
    for(var i=0, l=points.length; i<l; i++) {
      var p = scaleToCanvas(convert(points[i]));
      ctx.beginPath();
      ctx.arc(p[0], p[1], pointSize, 0, 7, false); //7 is any arbitral angle >= 2*PI
      ctx.closePath();
      ctx.fill();
    }
    ctx.restore();
  }
  Viewer.prototype.clear = function(){
    var ctx = this.canvas.getContext('2d');
    ctx.clearRect(0, 0, this.width, this.height);
  }

  Sylvester.CanvasViewer = Viewer;
})(window.Sylvester);
