‘use strict’;

!function($) {

/**

* Tooltip module.
* @module foundation.tooltip
* @requires foundation.util.box
* @requires foundation.util.mediaQuery
* @requires foundation.util.triggers
*/

class Tooltip {

/**
 * Creates a new instance of a Tooltip.
 * @class
 * @fires Tooltip#init
 * @param {jQuery} element - jQuery object to attach a tooltip to.
 * @param {Object} options - object to extend the default configuration.
 */
constructor(element, options) {
  this.$element = element;
  this.options = $.extend({}, Tooltip.defaults, this.$element.data(), options);

  this.isActive = false;
  this.isClick = false;
  this._init();

  Foundation.registerPlugin(this, 'Tooltip');
}

/**
 * Initializes the tooltip by setting the creating the tip element, adding it's text, setting private variables and setting attributes on the anchor.
 * @private
 */
_init() {
  var elemId = this.$element.attr('aria-describedby') || Foundation.GetYoDigits(6, 'tooltip');

  this.options.positionClass = this.options.positionClass || this._getPositionClass(this.$element);
  this.options.tipText = this.options.tipText || this.$element.attr('title');
  this.template = this.options.template ? $(this.options.template) : this._buildTemplate(elemId);

  if (this.options.allowHtml) {
    this.template.appendTo(document.body)
      .html(this.options.tipText)
      .hide();
  } else {
    this.template.appendTo(document.body)
      .text(this.options.tipText)
      .hide();
  }

  this.$element.attr({
    'title': '',
    'aria-describedby': elemId,
    'data-yeti-box': elemId,
    'data-toggle': elemId,
    'data-resize': elemId
  }).addClass(this.options.triggerClass);

  //helper variables to track movement on collisions
  this.usedPositions = [];
  this.counter = 4;
  this.classChanged = false;

  this._events();
}

/**
 * Grabs the current positioning class, if present, and returns the value or an empty string.
 * @private
 */
_getPositionClass(element) {
  if (!element) { return ''; }
  // var position = element.attr('class').match(/top|left|right/g);
  var position = element[0].className.match(/\b(top|left|right)\b/g);
      position = position ? position[0] : '';
  return position;
};
/**
 * builds the tooltip element, adds attributes, and returns the template.
 * @private
 */
_buildTemplate(id) {
  var templateClasses = (`${this.options.tooltipClass} ${this.options.positionClass} ${this.options.templateClasses}`).trim();
  var $template =  $('<div></div>').addClass(templateClasses).attr({
    'role': 'tooltip',
    'aria-hidden': true,
    'data-is-active': false,
    'data-is-focus': false,
    'id': id
  });
  return $template;
}

/**
 * Function that gets called if a collision event is detected.
 * @param {String} position - positioning class to try
 * @private
 */
_reposition(position) {
  this.usedPositions.push(position ? position : 'bottom');

  //default, try switching to opposite side
  if (!position && (this.usedPositions.indexOf('top') < 0)) {
    this.template.addClass('top');
  } else if (position === 'top' && (this.usedPositions.indexOf('bottom') < 0)) {
    this.template.removeClass(position);
  } else if (position === 'left' && (this.usedPositions.indexOf('right') < 0)) {
    this.template.removeClass(position)
        .addClass('right');
  } else if (position === 'right' && (this.usedPositions.indexOf('left') < 0)) {
    this.template.removeClass(position)
        .addClass('left');
  }

  //if default change didn't work, try bottom or left first
  else if (!position && (this.usedPositions.indexOf('top') > -1) && (this.usedPositions.indexOf('left') < 0)) {
    this.template.addClass('left');
  } else if (position === 'top' && (this.usedPositions.indexOf('bottom') > -1) && (this.usedPositions.indexOf('left') < 0)) {
    this.template.removeClass(position)
        .addClass('left');
  } else if (position === 'left' && (this.usedPositions.indexOf('right') > -1) && (this.usedPositions.indexOf('bottom') < 0)) {
    this.template.removeClass(position);
  } else if (position === 'right' && (this.usedPositions.indexOf('left') > -1) && (this.usedPositions.indexOf('bottom') < 0)) {
    this.template.removeClass(position);
  }
  //if nothing cleared, set to bottom
  else {
    this.template.removeClass(position);
  }
  this.classChanged = true;
  this.counter--;
}

/**
 * sets the position class of an element and recursively calls itself until there are no more possible positions to attempt, or the tooltip element is no longer colliding.
 * if the tooltip is larger than the screen width, default to full width - any user selected margin
 * @private
 */
_setPosition() {
  var position = this._getPositionClass(this.template),
      $tipDims = Foundation.Box.GetDimensions(this.template),
      $anchorDims = Foundation.Box.GetDimensions(this.$element),
      direction = (position === 'left' ? 'left' : ((position === 'right') ? 'left' : 'top')),
      param = (direction === 'top') ? 'height' : 'width',
      offset = (param === 'height') ? this.options.vOffset : this.options.hOffset,
      _this = this;

  if (($tipDims.width >= $tipDims.windowDims.width) || (!this.counter && !Foundation.Box.ImNotTouchingYou(this.template))) {
    this.template.offset(Foundation.Box.GetOffsets(this.template, this.$element, 'center bottom', this.options.vOffset, this.options.hOffset, true)).css({
    // this.$element.offset(Foundation.GetOffsets(this.template, this.$element, 'center bottom', this.options.vOffset, this.options.hOffset, true)).css({
      'width': $anchorDims.windowDims.width - (this.options.hOffset * 2),
      'height': 'auto'
    });
    return false;
  }

  this.template.offset(Foundation.Box.GetOffsets(this.template, this.$element,'center ' + (position || 'bottom'), this.options.vOffset, this.options.hOffset));

  while(!Foundation.Box.ImNotTouchingYou(this.template) && this.counter) {
    this._reposition(position);
    this._setPosition();
  }
}

/**
 * reveals the tooltip, and fires an event to close any other open tooltips on the page
 * @fires Tooltip#closeme
 * @fires Tooltip#show
 * @function
 */
show() {
  if (this.options.showOn !== 'all' && !Foundation.MediaQuery.is(this.options.showOn)) {
    // console.error('The screen is too small to display this tooltip');
    return false;
  }

  var _this = this;
  this.template.css('visibility', 'hidden').show();
  this._setPosition();

  /**
   * Fires to close all other open tooltips on the page
   * @event Closeme#tooltip
   */
  this.$element.trigger('closeme.zf.tooltip', this.template.attr('id'));

  this.template.attr({
    'data-is-active': true,
    'aria-hidden': false
  });
  _this.isActive = true;
  // console.log(this.template);
  this.template.stop().hide().css('visibility', '').fadeIn(this.options.fadeInDuration, function() {
    //maybe do stuff?
  });
  /**
   * Fires when the tooltip is shown
   * @event Tooltip#show
   */
  this.$element.trigger('show.zf.tooltip');
}

/**
 * Hides the current tooltip, and resets the positioning class if it was changed due to collision
 * @fires Tooltip#hide
 * @function
 */
hide() {
  // console.log('hiding', this.$element.data('yeti-box'));
  var _this = this;
  this.template.stop().attr({
    'aria-hidden': true,
    'data-is-active': false
  }).fadeOut(this.options.fadeOutDuration, function() {
    _this.isActive = false;
    _this.isClick = false;
    if (_this.classChanged) {
      _this.template
           .removeClass(_this._getPositionClass(_this.template))
           .addClass(_this.options.positionClass);

     _this.usedPositions = [];
     _this.counter = 4;
     _this.classChanged = false;
    }
  });
  /**
   * fires when the tooltip is hidden
   * @event Tooltip#hide
   */
  this.$element.trigger('hide.zf.tooltip');
}

/**
 * adds event listeners for the tooltip and its anchor
 * TODO combine some of the listeners like focus and mouseenter, etc.
 * @private
 */
_events() {
  var _this = this;
  var $template = this.template;
  var isFocus = false;

  if (!this.options.disableHover) {

    this.$element
    .on('mouseenter.zf.tooltip', function(e) {
      if (!_this.isActive) {
        _this.timeout = setTimeout(function() {
          _this.show();
        }, _this.options.hoverDelay);
      }
    })
    .on('mouseleave.zf.tooltip', function(e) {
      clearTimeout(_this.timeout);
      if (!isFocus || (_this.isClick && !_this.options.clickOpen)) {
        _this.hide();
      }
    });
  }

  if (this.options.clickOpen) {
    this.$element.on('mousedown.zf.tooltip', function(e) {
      e.stopImmediatePropagation();
      if (_this.isClick) {
        //_this.hide();
        // _this.isClick = false;
      } else {
        _this.isClick = true;
        if ((_this.options.disableHover || !_this.$element.attr('tabindex')) && !_this.isActive) {
          _this.show();
        }
      }
    });
  } else {
    this.$element.on('mousedown.zf.tooltip', function(e) {
      e.stopImmediatePropagation();
      _this.isClick = true;
    });
  }

  if (!this.options.disableForTouch) {
    this.$element
    .on('tap.zf.tooltip touchend.zf.tooltip', function(e) {
      _this.isActive ? _this.hide() : _this.show();
    });
  }

  this.$element.on({
    // 'toggle.zf.trigger': this.toggle.bind(this),
    // 'close.zf.trigger': this.hide.bind(this)
    'close.zf.trigger': this.hide.bind(this)
  });

  this.$element
    .on('focus.zf.tooltip', function(e) {
      isFocus = true;
      if (_this.isClick) {
        // If we're not showing open on clicks, we need to pretend a click-launched focus isn't
        // a real focus, otherwise on hover and come back we get bad behavior
        if(!_this.options.clickOpen) { isFocus = false; }
        return false;
      } else {
        _this.show();
      }
    })

    .on('focusout.zf.tooltip', function(e) {
      isFocus = false;
      _this.isClick = false;
      _this.hide();
    })

    .on('resizeme.zf.trigger', function() {
      if (_this.isActive) {
        _this._setPosition();
      }
    });
}

/**
 * adds a toggle method, in addition to the static show() & hide() functions
 * @function
 */
toggle() {
  if (this.isActive) {
    this.hide();
  } else {
    this.show();
  }
}

/**
 * Destroys an instance of tooltip, removes template element from the view.
 * @function
 */
destroy() {
  this.$element.attr('title', this.template.text())
               .off('.zf.trigger .zf.tooltip')
               .removeClass('has-tip top right left')
               .removeAttr('aria-describedby aria-haspopup data-disable-hover data-resize data-toggle data-tooltip data-yeti-box');

  this.template.remove();

  Foundation.unregisterPlugin(this);
}

}

Tooltip.defaults = {

disableForTouch: false,
/**
 * Time, in ms, before a tooltip should open on hover.
 * @option
 * @type {number}
 * @default 200
 */
hoverDelay: 200,
/**
 * Time, in ms, a tooltip should take to fade into view.
 * @option
 * @type {number}
 * @default 150
 */
fadeInDuration: 150,
/**
 * Time, in ms, a tooltip should take to fade out of view.
 * @option
 * @type {number}
 * @default 150
 */
fadeOutDuration: 150,
/**
 * Disables hover events from opening the tooltip if set to true
 * @option
 * @type {boolean}
 * @default false
 */
disableHover: false,
/**
 * Optional addtional classes to apply to the tooltip template on init.
 * @option
 * @type {string}
 * @default ''
 */
templateClasses: '',
/**
 * Non-optional class added to tooltip templates. Foundation default is 'tooltip'.
 * @option
 * @type {string}
 * @default 'tooltip'
 */
tooltipClass: 'tooltip',
/**
 * Class applied to the tooltip anchor element.
 * @option
 * @type {string}
 * @default 'has-tip'
 */
triggerClass: 'has-tip',
/**
 * Minimum breakpoint size at which to open the tooltip.
 * @option
 * @type {string}
 * @default 'small'
 */
showOn: 'small',
/**
 * Custom template to be used to generate markup for tooltip.
 * @option
 * @type {string}
 * @default ''
 */
template: '',
/**
 * Text displayed in the tooltip template on open.
 * @option
 * @type {string}
 * @default ''
 */
tipText: '',
touchCloseText: 'Tap to close.',
/**
 * Allows the tooltip to remain open if triggered with a click or touch event.
 * @option
 * @type {boolean}
 * @default true
 */
clickOpen: true,
/**
 * Additional positioning classes, set by the JS
 * @option
 * @type {string}
 * @default ''
 */
positionClass: '',
/**
 * Distance, in pixels, the template should push away from the anchor on the Y axis.
 * @option
 * @type {number}
 * @default 10
 */
vOffset: 10,
/**
 * Distance, in pixels, the template should push away from the anchor on the X axis, if aligned to a side.
 * @option
 * @type {number}
 * @default 12
 */
hOffset: 12,
  /**
 * Allow HTML in tooltip. Warning: If you are loading user-generated content into tooltips,
 * allowing HTML may open yourself up to XSS attacks.
 * @option
 * @type {boolean}
 * @default false
 */
allowHtml: false

};

/**

* TODO utilize resize event trigger
*/

// Window exports Foundation.plugin(Tooltip, ‘Tooltip’);

}(jQuery);