/**
 * @requires javelin-install
 *           javelin-util
 *           javelin-dom
 *           javelin-vector
 * @provides phabricator-tooltip
 * @javelin
 */

JX.install('Tooltip', {

  statics: {
    _node: null,
    _last: null,
    _lock: 0,

    show : function(root, scale, align, content) {
      var self = JX.Tooltip;

      if (self._lock) {
        return;
      }

      if (content === null) {
        return;
      }

      if (__DEV__) {
        switch (align) {
          case 'N':
          case 'E':
          case 'S':
          case 'W':
            break;
          default:
            JX.$E(
              'Only alignments "N" (north), "E" (east), "S" (south), ' +
              'and "W" (west) are supported.'
            );
            break;
        }
      }

      var node_inner = JX.$N(
        'div',
        { className: 'jx-tooltip-inner' },
        [
          JX.$N('div', { className: 'jx-tooltip' }, content),
          JX.$N('div', { className: 'jx-tooltip-anchor' })
        ]);

      var node = JX.$N(
        'div',
        { className: 'jx-tooltip-container' },
        node_inner);

      if (scale == 'auto') {
        node.style.maxWidth = '';
      } else {
        node.style.maxWidth = scale + 'px';
      }

      JX.Tooltip.hide();
      self._node = node;

      // Append the tip to the document, but offscreen, so we can measure it.
      node.style.left = '-10000px';
      document.body.appendChild(node);

      // Jump through some hoops trying to auto-position the tooltip
      var pos = self._getSmartPosition(align, root, node);
      pos.setPos(node);

      // Animate the tip if we haven't shown any tips recently. If we are
      // already showing a tip (or hid one very recently) just show the tip
      // immediately. This makes hunting for a particular item by mousing
      // through tips smoother: you only have to sit through the animation
      // once, at the beginning.

      var is_recent = false;

      var last_tip = self._last;
      if (last_tip) {
        // If we recently hid a tip, compute how many milliseconds ago we
        // hid it.
        var last_tip_age = (new Date().getTime() - self._last);
        if (last_tip_age < 500) {
          is_recent = true;
        }
      }

      if (!is_recent) {
        JX.DOM.alterClass(node, 'jx-tooltip-appear', true);
      }
    },

    _getSmartPosition: function (align, root, node) {
      var self = JX.Tooltip;

      // Figure out how to position the tooltip on screen. We will try the
      // configured aligment first.
      var try_alignments = [align];

      // If the configured alignment does not fit, we'll try the opposite
      // alignment.
      var opposites = {
        N: 'S',
        S: 'N',
        E: 'W',
        W: 'E'
      };
      try_alignments.push(opposites[align]);

      // Then we'll try the other alignments, in arbitrary order.
      for (var k in opposites) {
        try_alignments.push(k);
      }

      var use_alignment = null;
      var use_pos = null;
      for (var ii = 0; ii < try_alignments.length; ii++) {
        var try_alignment = try_alignments[ii];

        var pos = self._proposePosition(try_alignment, root, node);
        if (self.isOnScreen(pos, node)) {
          use_alignment = try_alignment;
          use_pos = pos;
          break;
        }
      }

      // If we don't come up with a good answer, default to the configured
      // alignment.
      if (use_alignment === null) {
        use_alignment = align;
        use_pos = self._proposePosition(use_alignment, root, node);
      }

      self._setAnchor(use_alignment);

      return pos;
    },

    _proposePosition: function (align, root, node) {
      var p = JX.$V(root);
      var d = JX.Vector.getDim(root);
      var n = JX.Vector.getDim(node);
      var l = 0;
      var t = 0;

      // Caculate the tip so it's nicely aligned.
      switch (align) {
        case 'N':
          l = parseInt(p.x - ((n.x - d.x) / 2), 10);
          t  = parseInt(p.y - n.y, 10);
          break;
        case 'E':
          l = parseInt(p.x + d.x, 10);
          t  = parseInt(p.y - ((n.y - d.y) / 2), 10);
          break;
        case 'S':
          l = parseInt(p.x - ((n.x - d.x) / 2), 10);
          t  = parseInt(p.y + d.y + 5, 10);
          break;
        case 'W':
          l = parseInt(p.x - n.x - 5, 10);
          t  = parseInt(p.y - ((n.y - d.y) / 2), 10);
          break;
      }

      return new JX.Vector(l, t);
    },

    isOnScreen: function (a, node) {
      var view = this._getViewBoundaries();
      var corners = this._getNodeCornerPositions(a, node);

      // Check if any of the corners are offscreen.
      for (var i = 0; i < corners.length; i++) {
        var corner = corners[i];
        if (corner.x < view.w ||
            corner.y < view.n ||
            corner.x > view.e ||
            corner.y > view.s) {
          return false;
        }
      }
      return true;
    },

    _getNodeCornerPositions: function(pos, node) {
      // Get positions of all four corners of a node.
      var n = JX.Vector.getDim(node);
      return [new JX.Vector(pos.x, pos.y),
              new JX.Vector(pos.x + n.x, pos.y),
              new JX.Vector(pos.x, pos.y + n.y),
              new JX.Vector(pos.x + n.x, pos.y + n.y)];
    },

    _getViewBoundaries: function() {
      var s = JX.Vector.getScroll();
      var v = JX.Vector.getViewport();
      var max_x = s.x + v.x;
      var max_y = s.y + v.y;

      // Even if the corner is technically on the screen, don't allow the
      // tip to display too close to the edge of the screen.
      var margin = 16;

      return {
        w: s.x + margin,
        e: max_x - margin,
        n: s.y + margin,
        s: max_y - margin
      };
    },

    _setAnchor: function (align) {
      // Orient the little tail
      JX.DOM.alterClass(this._node, 'jx-tooltip-align-' + align, true);
    },

    hide : function() {
      if (this._node) {
        JX.DOM.remove(this._node);
        this._node = null;
        this._last = new Date().getTime();
      }
    },

    lock: function() {
      var self = JX.Tooltip;
      self.hide();
      self._lock++;
    },

    unlock: function() {
      var self = JX.Tooltip;
      self._lock--;
    }
  }
});
