/**
 * Tooltip class
 * @param {module:components.PSVHUD} hud
 * @constructor
 * @extends module:components.PSVComponent
 * @memberof module:components
 */
function PSVTooltip(hud) {
  PSVComponent.call(this, hud);

  /**
   * @member {Object}
   * @readonly
   * @private
   */
  this.config = this.psv.config.tooltip;

  /**
   * @member {Object}
   * @private
   */
  this.prop = {
    timeout: null
  };

  this.create();
}

PSVTooltip.prototype = Object.create(PSVComponent.prototype);
PSVTooltip.prototype.constructor = PSVTooltip;

PSVTooltip.className = 'psv-tooltip';
PSVTooltip.publicMethods = ['showTooltip', 'hideTooltip', 'isTooltipVisible'];

PSVTooltip.leftMap = { 0: 'left', 0.5: 'center', 1: 'right' };
PSVTooltip.topMap = { 0: 'top', 0.5: 'center', 1: 'bottom' };

/**
 * @override
 */
PSVTooltip.prototype.create = function() {
  PSVComponent.prototype.create.call(this);

  this.container.innerHTML = '<div class="psv-tooltip-arrow"></div><div class="psv-tooltip-content"></div>';
  this.container.style.top = '-1000px';
  this.container.style.left = '-1000px';

  this.content = this.container.querySelector('.psv-tooltip-content');
  this.arrow = this.container.querySelector('.psv-tooltip-arrow');

  this.psv.on('render', this);
};

/**
 * @override
 */
PSVTooltip.prototype.destroy = function() {
  this.psv.off('render', this);

  delete this.config;
  delete this.prop;

  PSVComponent.prototype.destroy.call(this);
};

/**
 * @summary Handles events
 * @param {Event} e
 * @private
 */
PSVTooltip.prototype.handleEvent = function(e) {
  switch (e.type) {
    // @formatter:off
    case 'render': this.hideTooltip(); break;
    // @formatter:on
  }
};

/**
 * @summary Checks if the tooltip is visible
 * @returns {boolean}
 */
PSVTooltip.prototype.isTooltipVisible = function() {
  return this.container.classList.contains('psv-tooltip--visible');
};

/**
 * @summary Displays a tooltip on the viewer
 * @param {Object} config
 * @param {string} config.content - HTML content of the tootlip
 * @param {int} config.top - Position of the tip of the arrow of the tooltip, in pixels
 * @param {int} config.left - Position of the tip of the arrow of the tooltip, in pixels
 * @param {string} [config.position='top center'] - Tooltip position toward it's arrow tip.
 *                                                  Accepted values are combinations of `top`, `center`, `bottom`
 *                                                  and `left`, `center`, `right`
 * @param {string} [config.className] - Additional CSS class added to the tooltip
 * @param {Object} [config.box] - Used when displaying a tooltip on a marker
 * @param {int} [config.box.width=0]
 * @param {int} [config.box.height=0]
 * @fires module:components.PSVTooltip.show-tooltip
 * @throws {PSVError} when the configuration is incorrect
 *
 * @example
 * viewer.showTooltip({ content: 'Hello world', top: 200, left: 450, position: 'center bottom'})
 */
PSVTooltip.prototype.showTooltip = function(config) {
  if (this.prop.timeout) {
    window.clearTimeout(this.prop.timeout);
    this.prop.timeout = null;
  }

  var isUpdate = this.isTooltipVisible();
  var t = this.container;
  var c = this.content;
  var a = this.arrow;

  if (!config.position) {
    config.position = ['top', 'center'];
  }

  if (!config.box) {
    config.box = {
      width: 0,
      height: 0
    };
  }

  // parse position
  if (typeof config.position === 'string') {
    var tempPos = PSVUtils.parsePosition(config.position);

    if (!(tempPos.left in PSVTooltip.leftMap) || !(tempPos.top in PSVTooltip.topMap)) {
      throw new PSVError('unable to parse tooltip position "' + config.position + '"');
    }

    config.position = [PSVTooltip.topMap[tempPos.top], PSVTooltip.leftMap[tempPos.left]];
  }

  if (config.position[0] === 'center' && config.position[1] === 'center') {
    throw new PSVError('unable to parse tooltip position "center center"');
  }

  if (isUpdate) {
    // Remove every other classes (Firefox does not implements forEach)
    for (var i = t.classList.length - 1; i >= 0; i--) {
      var item = t.classList.item(i);
      if (item !== 'psv-tooltip' && item !== 'psv-tooltip--visible') {
        t.classList.remove(item);
      }
    }
  }
  else {
    t.className = 'psv-tooltip'; // reset the class
  }

  if (config.className) {
    PSVUtils.addClasses(t, config.className);
  }

  c.innerHTML = config.content;
  t.style.top = '0px';
  t.style.left = '0px';

  // compute size
  var rect = t.getBoundingClientRect();
  var style = {
    posClass: config.position.slice(),
    width: rect.right - rect.left,
    height: rect.bottom - rect.top,
    top: 0,
    left: 0,
    arrow_top: 0,
    arrow_left: 0
  };

  // set initial position
  this._computeTooltipPosition(style, config);

  // correct position if overflow
  var refresh = false;
  if (style.top < this.config.offset) {
    style.posClass[0] = 'bottom';
    refresh = true;
  }
  else if (style.top + style.height > this.psv.prop.size.height - this.config.offset) {
    style.posClass[0] = 'top';
    refresh = true;
  }
  if (style.left < this.config.offset) {
    style.posClass[1] = 'right';
    refresh = true;
  }
  else if (style.left + style.width > this.psv.prop.size.width - this.config.offset) {
    style.posClass[1] = 'left';
    refresh = true;
  }
  if (refresh) {
    this._computeTooltipPosition(style, config);
  }

  // apply position
  t.style.top = style.top + 'px';
  t.style.left = style.left + 'px';

  a.style.top = style.arrow_top + 'px';
  a.style.left = style.arrow_left + 'px';

  t.classList.add('psv-tooltip--' + style.posClass.join('-'));

  // delay for correct transition between the two classes
  if (!isUpdate) {
    this.prop.timeout = window.setTimeout(function() {
      t.classList.add('psv-tooltip--visible');
      this.prop.timeout = null;

      /**
       * @event show-tooltip
       * @memberof module:components.PSVTooltip
       * @summary Trigered when the tooltip is shown
       */
      this.psv.trigger('show-tooltip');
    }.bind(this), this.config.delay);
  }
};

/**
 * @summary Hides the tooltip
 * @fires module:components.PSVTooltip.hide-tooltip
 */
PSVTooltip.prototype.hideTooltip = function() {
  if (this.prop.timeout) {
    window.clearTimeout(this.prop.timeout);
    this.prop.timeout = null;
  }

  if (this.isTooltipVisible()) {
    this.container.classList.remove('psv-tooltip--visible');

    this.prop.timeout = window.setTimeout(function() {
      this.content.innerHTML = null;
      this.container.style.top = '-1000px';
      this.container.style.left = '-1000px';
      this.prop.timeout = null;
    }.bind(this), this.config.delay);

    /**
     * @event hide-tooltip
     * @memberof module:components.PSVTooltip
     * @summary Trigered when the tooltip is hidden
     */
    this.psv.trigger('hide-tooltip');
  }
};

/**
 * @summary Computes the position of the tooltip and its arrow
 * @param {Object} style
 * @param {Object} config
 * @private
 */
PSVTooltip.prototype._computeTooltipPosition = function(style, config) {
  var topBottom = false;

  switch (style.posClass[0]) {
    case 'bottom':
      style.top = config.top + config.box.height + this.config.offset + this.config.arrow_size;
      style.arrow_top = -this.config.arrow_size * 2;
      topBottom = true;
      break;

    case 'center':
      style.top = config.top + config.box.height / 2 - style.height / 2;
      style.arrow_top = style.height / 2 - this.config.arrow_size;
      break;

    case 'top':
      style.top = config.top - style.height - this.config.offset - this.config.arrow_size;
      style.arrow_top = style.height;
      topBottom = true;
      break;
  }

  switch (style.posClass[1]) {
    case 'right':
      if (topBottom) {
        style.left = config.left + config.box.width / 2 - this.config.offset - this.config.arrow_size;
        style.arrow_left = this.config.offset;
      }
      else {
        style.left = config.left + config.box.width + this.config.offset + this.config.arrow_size;
        style.arrow_left = -this.config.arrow_size * 2;
      }
      break;

    case 'center':
      style.left = config.left + config.box.width / 2 - style.width / 2;
      style.arrow_left = style.width / 2 - this.config.arrow_size;
      break;

    case 'left':
      if (topBottom) {
        style.left = config.left - style.width + config.box.width / 2 + this.config.offset + this.config.arrow_size;
        style.arrow_left = style.width - this.config.offset - this.config.arrow_size * 2;
      }
      else {
        style.left = config.left - style.width - this.config.offset - this.config.arrow_size;
        style.arrow_left = style.width;
      }
      break;
  }
};