// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

'use strict';

base.requireStylesheet('ui.quad_view');

base.require('base.color');
base.require('base.events');
base.require('base.raf');
base.require('ui');
base.require('ui.quad_view_viewport');

base.exportTo('ui', function() {
  // FIXME(pdr): Remove this extra scaling so our rasters are pixel-perfect.
  //             https://code.google.com/p/trace-viewer/issues/detail?id=228
  // FIXME(jjb): simplify until we have the camera working (or 228 happens ;-)
  var RASTER_SCALE = 1.0; // Adjust the resolution of our backing canvases.

  // Care of bckenney@ via
  // http://extremelysatisfactorytotalitarianism.com/blog/?p=2120
  function drawTexturedTriangle(
      ctx,
      img, x0, y0, x1, y1, x2, y2,
      u0, v0, u1, v1, u2, v2) {

    ctx.beginPath();
    ctx.moveTo(x0, y0);
    ctx.lineTo(x1, y1);
    ctx.lineTo(x2, y2);
    ctx.closePath();

    x1 -= x0;
    y1 -= y0;
    x2 -= x0;
    y2 -= y0;

    u1 -= u0;
    v1 -= v0;
    u2 -= u0;
    v2 -= v0;

    var det = 1 / (u1 * v2 - u2 * v1),

        // linear transformation
        a = (v2 * x1 - v1 * x2) * det,
        b = (v2 * y1 - v1 * y2) * det,
        c = (u1 * x2 - u2 * x1) * det,
        d = (u1 * y2 - u2 * y1) * det,

        // translation
        e = x0 - a * u0 - c * v0,
        f = y0 - b * u0 - d * v0;

    ctx.save();
    ctx.transform(a, b, c, d, e, f);
    ctx.clip();
    ctx.drawImage(img, 0, 0);
    ctx.restore();
  }

  var QuadView = ui.define('quad-view');

  QuadView.prototype = {
    __proto__: HTMLUnknownElement.prototype,

    decorate: function() {
      base.EventTargetHelper.decorate(this);

      this.quads_ = undefined;
      this.viewport_ = undefined;
      this.canvas_ = document.createElement('canvas');

      this.appendChild(this.canvas_);

      this.onViewportChanged_ = this.onViewportChanged_.bind(this);

      this.onMouseDown_ = this.onMouseDown_.bind(this);
      this.onMouseMove_ = this.onMouseMove_.bind(this);
      this.onMouseUp_ = this.onMouseUp_.bind(this);
      this.canvas_.addEventListener('mousedown', this.onMouseDown_);

      this.canvas_.addEventListener('focus', this.redrawCanvas_.bind(this));
      this.canvas_.addEventListener('blur', this.redrawCanvas_.bind(this));
      this.canvas_.tabIndex = 0;
    },

    get viewport() {
      return this.viewport_;
    },

    set viewport(viewport) {
      if (this.viewport_)
        this.viewport_.removeEventListener('change', this.onViewportChanged_);
      this.viewport_ = viewport;
      if (this.viewport_)
        this.viewport_.addEventListener('change', this.onViewportChanged_);
      this.updateChildren_();
    },

    onViewportChanged_: function() {
      if (!this.hasRequiredProprties_)
        return;
      this.redrawCanvas_();
    },

    get quads() {
      return this.quads_;
    },

    set quads(quads) {
      this.quads_ = quads;
      if (!this.quads_) {
        this.updateChildren_();
        return;
      }
      this.viewport_ = this.viewport_ ||
          this.createViewportFromQuads_(this.quads_);
      this.updateChildren_();
    },

    get hasRequiredProprties_() {
      return this.quads_ &&
          this.viewport_;
    },

    updateChildren_: function() {
      var canvas = this.canvas_;
      if (!this.hasRequiredProprties_) {
        canvas.width = 0;
        canvas.height = 0;
        return;
      }

      this.scheduleRedrawCanvas_();
    },

    scheduleRedrawCanvas_: function() {
      if (this.redrawScheduled_)
        return false;
      this.redrawScheduled_ = true;
      base.requestAnimationFrameInThisFrameIfPossible(
          this.redrawCanvas_, this);
    },

    redrawCanvas_: function() {
      this.redrawScheduled_ = false;

      var resizedCanvas = this.viewport_.updateBoxSize(this.canvas_);

      var ctx = this.canvas_.getContext('2d');

      var vp = this.viewport_;

      if (!resizedCanvas) // Canvas resizing automatically clears the context.
        ctx.clearRect(0, 0, this.canvas_.width, this.canvas_.height);

      ctx.save();
      ctx.scale(ui.RASTER_SCALE, ui.RASTER_SCALE);

      // The quads are in the world coordinate system. We are drawing
      // into a canvas with 0,0 in the top left corner. Tell the canvas to
      // transform drawing ops from world to canvas coordinates.
      vp.applyTransformToContext(ctx);
      ctx.lineWidth = vp.getDeviceLineWidthAssumingTransformIsApplied(1.0);

      var quads = this.quads_ || [];

      // Background colors.
      for (var i = 0; i < quads.length; i++) {
        var quad = quads[i];
        if (quad.canvas) {
          if (quad.isRectangle()) {
            var bounds = quad.boundingRect();
            ctx.drawImage(quad.canvas, 0, 0,
                quad.canvas.width, quad.canvas.height,
                bounds.x, bounds.y, bounds.width, bounds.height);
          } else {
            ctx.save();
            var quadBBox = new base.BBox2();
            quadBBox.addQuad(quad);
            var iw = quad.canvas.width;
            var ih = quad.canvas.height;
            drawTexturedTriangle(
                ctx, quad.canvas,
                quad.p1[0], quad.p1[1],
                quad.p2[0], quad.p2[1],
                quad.p4[0], quad.p4[1],
                0, 0, iw, 0, 0, ih);
            drawTexturedTriangle(
                ctx, quad.canvas,
                quad.p2[0], quad.p2[1],
                quad.p3[0], quad.p3[1],
                quad.p4[0], quad.p4[1],
                iw, 0, iw, ih, 0, ih);
            ctx.restore();
          }
        }

        if (quad.backgroundColor) {
          ctx.fillStyle = quad.backgroundColor;
          ctx.beginPath();
          ctx.moveTo(quad.p1[0], quad.p1[1]);
          ctx.lineTo(quad.p2[0], quad.p2[1]);
          ctx.lineTo(quad.p3[0], quad.p3[1]);
          ctx.lineTo(quad.p4[0], quad.p4[1]);
          ctx.closePath();
          ctx.fill();
        }
      }

      // Outlines.
      for (var i = 0; i < quads.length; i++) {
        var quad = quads[i];
        ctx.beginPath();
        ctx.moveTo(quad.p1[0], quad.p1[1]);
        ctx.lineTo(quad.p2[0], quad.p2[1]);
        ctx.lineTo(quad.p3[0], quad.p3[1]);
        ctx.lineTo(quad.p4[0], quad.p4[1]);
        ctx.closePath();
        if (quad.borderColor)
          ctx.strokeStyle = quad.borderColor;
        else
          ctx.strokeStyle = 'rgb(128,128,128)';
        ctx.stroke();
      }

      // Selection outlines.
      ctx.lineWidth = vp.getDeviceLineWidthAssumingTransformIsApplied(8.0);
      var rules = window.getMatchedCSSRules(this.canvas_);

      // TODO(nduca): Figure out how to get these from css.
      for (var i = 0; i < quads.length; i++) {
        var quad = quads[i];
        if (!quad.upperBorderColor)
          continue;
        if (document.activeElement == this.canvas_) {
          var tmp = base.Color.fromString(quad.upperBorderColor).brighten(0.25);
          ctx.strokeStyle = tmp.toString();
        } else {
          ctx.strokeStyle = quad.upperBorderColor;
        }

        ctx.beginPath();
        ctx.moveTo(quad.p1[0], quad.p1[1]);
        ctx.lineTo(quad.p2[0], quad.p2[1]);
        ctx.lineTo(quad.p3[0], quad.p3[1]);
        ctx.lineTo(quad.p4[0], quad.p4[1]);
        ctx.closePath();
        ctx.stroke();
      }

      ctx.restore();
    },

    selectQuadsAtCanvasClientPoint: function(clientX, clientY) {
      clientX *= ui.RASTER_SCALE;
      clientY *= ui.RASTER_SCALE;
      var selectedQuadIndices = this.findQuadsAtCanvasClientPoint(
          clientX, clientY);
      var e = new base.Event('selectionChanged');
      e.selectedQuadIndices = selectedQuadIndices;
      this.dispatchEvent(e);
      this.viewport_.forceRedrawAll();
    },

    findQuadsAtCanvasClientPoint: function(clientX, clientY) {
      var bounds = this.canvas_.getBoundingClientRect();
      var vecInLayout = vec2.createXY(clientX - bounds.left,
                                      clientY - bounds.top);
      var vecInWorldPixels =
          this.viewport_.layoutPixelsToWorldPixels(vecInLayout);

      var quads = this.quads_;
      var hitIndices = [];
      for (var i = 0; i < quads.length; i++) {
        var hit = quads[i].vecInside(vecInWorldPixels);
        if (hit)
          hitIndices.push(i);
      }
      return hitIndices;
    },

    createViewportFromQuads_: function() {
      var quads = this.quads_ || [];
      var quadBBox = new base.BBox2();
      quads.forEach(function(quad) {
        quadBBox.addQuad(quad);
      });
      return new ui.QuadViewViewport(quadBBox.asRect());
    },

    onMouseDown_: function(e) {
      if (!this.hasEventListener('selectionChanged'))
        return;
      this.selectQuadsAtCanvasClientPoint(e.clientX, e.clientY);
      document.addEventListener('mousemove', this.onMouseMove_);
      document.addEventListener('mouseup', this.onMouseUp_);
      e.preventDefault();
      this.canvas_.focus();
      return true;
    },

    onMouseMove_: function(e) {
      this.selectQuadsAtCanvasClientPoint(e.clientX, e.clientY);
    },

    onMouseUp_: function(e) {
      this.selectQuadsAtCanvasClientPoint(e.clientX, e.clientY);
      document.removeEventListener('mousemove', this.onMouseMove_);
      document.removeEventListener('mouseup', this.onMouseUp_);
    }

  };

  return {
    QuadView: QuadView,
    RASTER_SCALE: RASTER_SCALE
  };
});