if (window.Evri == undefined) window.Evri = {};
if (window.jQuery == undefined) throw new Error("jQuery is required");
if (window.Raphael == undefined) throw new Error("Rapha\u00ebl is required");

Evri.Visualization = Evri.Class.create("Evri.Visualization");
Evri.Visualization.prototype = {

  /**
   * @constructor
   *
   * Evri.Visualization renders an Entity's relations.
   *
   * Styles:
   *   .label the label placed across the entity
   *
   * @param container DOM element in which to render visualization
   */
  initialize: function(container) {

    //-----------------------------------------
    // Mixins
    //-----------------------------------------

    Evri.EventDispatcher.mixin(this);


    //-----------------------------------------
    // Constants - Events
    //-----------------------------------------

    Evri.Visualization.LOADED = "loaded";
    Evri.Visualization.VIEWED = "viewed";
    Evri.Visualization.GRAPH_ITEM_CLICKED = "itemClicked";
    Evri.Visualization.GRAPH_ITEM_MOUSE_OVER = "itemMouseOver";
    Evri.Visualization.GRAPH_ITEM_MOUSE_OUT = "itemMouseOut";


    //-----------------------------------------
    // Constants - Private
    //-----------------------------------------

    var SVG_PATH_STRING = "M{0},{1} L{2},{3}";
    var ENTITY_RADIUS = 30;
    var RELATED_ENTITY_RADIUS = 20;
    var MAX_LABEL_WIDTH = 75;
    var EDGE_STYLE = {stroke: "#FCA240", "stroke-width": 4 };
    var ENTITY_NODE_STYLES = {
      ORGANIZATION: {stroke: "#EB122F", fill: "#FFC4CC" },
      PRODUCT: {stroke: "#A56ECA" , fill: "#E8C5FF" },
      PERSON: {stroke: "#B1D535" , fill: "#E9FF9F" },
      LOCATION: {stroke: "#7BC2FF" , fill: "#BDE0FF" },
      EVENT: {stroke: "#48628E" , fill: "#C0D7FF" },
      CONCEPT: {stroke: "#F6EAA7" , fill: "#FFF9D6" },
      OTHER: {stroke: "#B1B1B1" , fill: "#CCCCCC" }
    };


    //-----------------------------------------
    // Variables - Private
    //-----------------------------------------

    var that = this;
    var $container = jQuery(container).removeClass("truncated").css("position", "relative");
    var session = new Evri.API.Session({appId: "hearst-visualization"});
    var paper = null;
    var relatedElements = null;


    //-----------------------------------------
    // Methods - Public
    //-----------------------------------------


    /**
     * Renders the Entity (and optionally, the referring Entity) as a
     * visualization on the page.
     *
     * @param entityUri The entity to render
     * @param referringEntityUri The referring entity, if any
     */
    that.render = function(entityUri, referringEntityUri) {
      var pending = 1; // we will request entity regardless
      var entity = null;
      var referringEntity = null;
      var relatedEntities = [];

      if (referringEntityUri) {
        pending++; // we have two requests to complete
        session.models.Entity.findByResource(referringEntityUri, {
          onComplete: function(response) {
            referringEntity = response;
            if (! --pending) {
              that._draw(entity, referringEntity, relatedEntities);
            }
          },
          onFailure: function(error) {
            alert("Error: " + error);
            if (! --pending) {
              that._draw(entity, referringEntity, relatedEntities);
            }
          }
        });
      }
      session.models.Entity.findByResource(entityUri, {
        onComplete: function(response) {
          entity = response;
          entity.getTopRelationsTargets({
            onComplete: function(targetList) {
              var n = targetList.targets.length;
              for (var i = 0; i < n && relatedEntities.length < 5; i++) {
                var target = targetList.targets[i].entity;
                if (target.href.match(/^\/person\/.*-0x[a-f0-9]+$/i)) {
                  relatedEntities.push(target);
                }
              }
              if (! --pending) {
                that._draw(entity, referringEntity, relatedEntities, true);
              }
            },
            onFailure: function(error) {
              alert("Error: " + error);
              if (! --pending) {
                that._draw(entity, referringEntity, relatedEntities);
              }
            }
          }, {
            count: 25
          });
        },
        onFailure: function(error) {
          alert("Error: " + error);
        }
      });
    };


    //-----------------------------------------
    // Methods - Private
    //-----------------------------------------


    /**
     * @private
     *
     * Returns the visualization to a clean slate.
     *
     */
    that._reset = function() {
      $container.find(".label").remove();

      if (paper) {
        paper.remove();
      }
      paper = new Raphael($container[0], $container.width(), $container.height());

      relatedElements = paper.set();
    };


    /**
     * @private
     *
     * Draws the visualization.
     *
     * @param entity The Entity being visualized
     * @param referringEntity The referring Entity, if any
     * @param relatedEntities An array of related Entity objects
     */
    that._draw = function(entity, referringEntity, relatedEntities) {
      that._reset();

      var centerPoint = {x: paper.width / 2, y: paper.height / 2};
      var x = 0;
      var y = 0;
      var r = 0;

      if (referringEntity) {
        x = centerPoint.x / 3;
        y = centerPoint.y;
        r = RELATED_ENTITY_RADIUS;

        paper.path(SVG_PATH_STRING, x, y, centerPoint.x, centerPoint.y).attr(EDGE_STYLE);
	// ACS: this is the center entity.. don't link it.
        that._drawNode(x, y, r, referringEntity, true);
      }

      x = referringEntity ? centerPoint.x / 1.05 : centerPoint.x / 1.5;
      y = centerPoint.y;
      r = ENTITY_RADIUS;

      var center = that._drawNode(x, y, r, entity, false);
      var dx = paper.width / 2.75;
      var dy = dx / 0.9;
      var n = relatedEntities.length + 1; // extra spot for layout
      var arc = Math.PI / n;
      for (var i = 0; i < n; i++) {
        if (i == 0) continue; // extra spot for layout

        var rootX = center.attr("cx");
        var rootY = center.attr("cy");

        y = dy * Math.sin((arc * i) - (Math.PI / 2));
        y *= 5 / 6; // skew the y
        y += rootY;
        x = dx * Math.cos((arc * i) - (Math.PI / 2));
        // x *= 3 / 4; // Math.abs(centerPoint.y - y); // skew the x
        x += rootX;
        r = RELATED_ENTITY_RADIUS;

        paper.path(SVG_PATH_STRING, rootX, rootY, x, y).attr(EDGE_STYLE);
        relatedElements.push(that._drawNode(x, y, r, relatedEntities[i - 1])); // extra spot for layout
      }

      relatedElements.attr("scale", 0.001);
      relatedElements.animate({scale: 1}, 1000, "bounce");

      center.toFront();
    };


    /**
     * @private
     *
     * Draws an Entity on the canvas
     *
     * @param x The horizontal origin point
     * @param y The vertical origin point
     * @param r The radius of the node
     * @param entity The entity to draw
     */
    that._drawNode = function(x, y, r, entity, omitlink) {
      var circle = paper.circle(x, y, r);
      // type is not returned for getTopRelationsTargets call
      var type = entity.type ? entity.type : entity.resource.split("/")[1];
      circle.attr(ENTITY_NODE_STYLES[type.toUpperCase()] || ENTITY_NODE_STYLES["OTHER"]);
      circle.attr("stroke-width", 3);

      var $node = jQuery(circle.node);
      $node.css("cursor", "pointer");
      if (!omitlink) {
	$node.click(
	  function(e) {
	    that._node_clickHandler.apply(that, [e, entity]);
	  }
	);
      }

      that._drawNodeLabel(circle, entity, omitlink);

      return circle;
    };


    /**
     * @private
     *
     * Draws the label on a node.
     *
     * @param element The Raphael element on which to place the label
     * @param entity The entity from which to create the label
     */
    that._drawNodeLabel = function(element, entity, omitlink) {
      // create our label
      var $label = jQuery("<div/>").text(entity.name).addClass("label");
      $label.css("position", "absolute");
      // add the label so measuring works
      $container.append($label);
      // reduce the size if too large
      if ($label.width() > MAX_LABEL_WIDTH) {
        $label.data("text", entity.name);
        $label.addClass("truncated");
        $label.width(MAX_LABEL_WIDTH);
      }

      that._centerOverNode(element, $label);
      if (!omitlink) {
	$label.bind(
	  "click",
	  function(e) {
	    that._node_clickHandler.apply(that, [e, entity]);
	  }
	);
      }
      $label.bind("mouseover", function() {
        if ($label.data("text")) {
          $label.removeClass("truncated");
          $label.css("z-index", 1001);
          that._centerOverNode(element, $label);
        }
      });

      $label.bind("mouseout", function() {
        if ($label.data("text")) {
          $label.addClass("truncated");
          $label.css("z-index", 1000);
          that._centerOverNode(element, $label);
        }
      });
    };


    /**
     * @private
     *
     * Centers an item over a
     * @param element
     * @param $node
     */
    that._centerOverNode = function(element, $node) {
      var x = element.attr("cx") - ($node.outerWidth() / 2);
      var y = element.attr("cy") - ($node.outerHeight() / 2);
      $node.css({left: x, top: y});
    };


    //-----------------------------------------
    // Methods - Event Handlers
    //-----------------------------------------

    that._node_clickHandler = function(mouseEvent, entity) {
      var event = {};
      event.type = Evri.Visualization.GRAPH_ITEM_CLICKED;
      event.target = mouseEvent.target;
      event.entity = entity;
      var point = Evri.Util.globalToLocal({x: mouseEvent.pageX, y: mouseEvent.pageY}, $container[0]);
      event.mouseX = point.x;
      event.mouseY = point.y;
      event.pageX = mouseEvent.pageX;
      event.pageY = mouseEvent.pageY;

      that.dispatchEvent(event.type, event);
    };


    that._nodeHoverHandler = function(event) {
      that.dispatchEvent(event.type, event);
    };
  }
};
