/**

* Dropdown component.
*
* @author Htmlstream
* @version 1.0
*/

; (function($) {

'use strict';

$.HSCore.components.HSDropdown = {

  /**
   * Base configuration of the component.
   *
   * @private
   */
  _baseConfig: {

    dropdownEvent: 'click',
    dropdownType: 'simple',
    dropdownDuration: 300,
    dropdownEasing: 'linear',
    dropdownAnimationIn: 'fadeIn',
    dropdownAnimationOut: 'fadeOut',
    dropdownHideOnScroll: true,
    dropdownHideOnBlur: false,
    dropdownDelay: 350,

    afterOpen: function(invoker){},
    afterClose: function(invoker){}
  },

  /**
   * Collection of all initialized items on the page.
   *
   * @private
   */
  _pageCollection: $(),

  /**
   * Initialization.
   *
   * @param {jQuery} collection
   * @param {Object} config
   *
   * @public
   * @return {jQuery}
   */
  init: function(collection, config) {

    var self;

    if( !collection || !collection.length ) return;

    self = this;

    collection.each(function(i, el){

      var $this = $(el), itemConfig;

      if( $this.data('HSDropDown') ) return;

      itemConfig = config && $.isPlainObject(config) ? 
                   $.extend(true, {}, self._baseConfig, config, $this.data()) :
                   $.extend(true, {}, self._baseConfig, $this.data());

      switch( itemConfig.dropdownType ) {

        case 'css-animation' :

          $this.data('HSDropDown', new DropdownCSSAnimation($this, itemConfig));

        break;

        case 'jquery-slide' : 

          $this.data('HSDropDown', new DropdownJSlide($this, itemConfig));

        break;

        default : 

          $this.data('HSDropDown', new DropdownSimple($this, itemConfig));

      }

      self._pageCollection = self._pageCollection.add( $this );
      self._bindEvents( $this, itemConfig.dropdownEvent, itemConfig.dropdownDelay );

    });

    $(document).on('keyup.HSDropDown', function(e){

      if(e.keyCode && e.keyCode == 27) {

        self._pageCollection.each( function(i, el){

          $(el).data('HSDropDown').hide();

        } );

      }

    });

    $(window).on('scroll.HSDropDown', function(e){

      self._pageCollection.each(function(i, el){

        var DropDown = $(el).data('HSDropDown');

        if(DropDown.getOption('dropdownHideOnScroll')) {

          DropDown.hide();

        }

      });

    });

    $(window).on('resize.HSDropDown', function(e){

      if(self._resizeTimeOutId) clearTimeout(self._resizeTimeOutId);

      self._resizeTimeOutId = setTimeout(function(){

        self._pageCollection.each(function(i, el){

          var DropDown = $(el).data('HSDropDown');

          DropDown.smartPosition(DropDown.target);

        });

      }, 50);

    });

    return collection;

  },

  /**
   * Binds necessary events.
   * 
   * @param {jQuery} $invoker
   * @param {String} eventType
   * @param {Number} delay
   * @private 
   */
  _bindEvents: function( $invoker, eventType, delay ) {

    var $dropdown = $($invoker.data('dropdown-target'));

    if( eventType == 'hover' && !_isTouch() ) {

      $invoker.on('mouseenter.HSDropDown', function(e) {

        var $invoker = $(this),
            HSDropDown = $invoker.data('HSDropDown');

        if( !HSDropDown ) return;

        if( HSDropDown.dropdownTimeOut ) clearTimeout( HSDropDown.dropdownTimeOut );
        HSDropDown.show();

      })
      .on('mouseleave.HSDropDown', function(e) {

        var $invoker = $(this),
            HSDropDown = $invoker.data('HSDropDown');

        if( !HSDropDown ) return;

        HSDropDown.dropdownTimeOut = setTimeout(function(){

          HSDropDown.hide();

        }, delay);

      });

      if( $dropdown.length ) {
        $dropdown.on('mouseenter.HSDropDown', function(e){

          var HSDropDown = $invoker.data('HSDropDown');

          if( HSDropDown.dropdownTimeOut ) clearTimeout( HSDropDown.dropdownTimeOut );
          HSDropDown.show();

        })
        .on('mouseleave.HSDropDown', function(e) {

          var HSDropDown = $invoker.data('HSDropDown');

          HSDropDown.dropdownTimeOut = setTimeout(function() {
            HSDropDown.hide();
          }, delay);

        });
      }

    }
    else {

      $invoker.on('click.HSDropDown', function(e){

        var $invoker = $(this);

        if( !$invoker.data('HSDropDown') ) return;

        $invoker.data('HSDropDown').toggle();

        e.stopPropagation();
        e.preventDefault();

      });

    }

  }
};

function _isTouch() {
  return 'ontouchstart' in window;
}

/**
 * Abstract Dropdown class.
 *
 * @param {jQuery} element
 * @param {Object} config
 * @abstract 
 */
function AbstractDropdown(element, config) {

  if( !element.length ) return false;

  this.element = element;
  this.config = config;

  this.target = $(this.element.data('dropdown-target'));

  this.allInvokers = $('[data-dropdown-target="'+this.element.data('dropdown-target')+'"]');

  this.toggle = function() {
    if( !this.target.length ) return this;

    if( this.defaultState ) {
      this.show();
    }
    else {
      this.hide();
    }

    return this;
  };

  this.smartPosition = function( target ) {

    if(target.data('baseDirection')) {
      target.css(
        target.data('baseDirection').direction,
        target.data('baseDirection').value
      );
    }

    target.removeClass('u-dropdown--reverse-y');

    var $w = $(window),
        styles = getComputedStyle(target.get(0)),
        direction = Math.abs( parseInt( styles.left, 10) ) < 40 ? 'left' : 'right',
        targetOuterGeometry = target.offset();

    // horizontal axis
    if(direction == 'right') {

      if( !target.data('baseDirection') ) target.data('baseDirection', {
        direction: 'right',
        value: parseInt( styles.right, 10)
      } );

      if( targetOuterGeometry.left < 0 ) {

        target.css(
          'right',
          (parseInt( target.css('right'), 10 ) - (targetOuterGeometry.left - 10 )) * -1
        );

      }

    }
    else {

      if( !target.data('baseDirection') ) target.data('baseDirection', {
        direction: 'left',
        value: parseInt( styles.left, 10)
      } );

      if( targetOuterGeometry.left + target.outerWidth() > $w.width() ) {

        target.css(
          'left',
          (parseInt( target.css('left'), 10 ) - (targetOuterGeometry.left + target.outerWidth() + 10 - $w.width()))
        );

      }

    }

    // vertical axis
    if( targetOuterGeometry.top + target.outerHeight() - $w.scrollTop() > $w.height() ) {
      target.addClass('u-dropdown--reverse-y');
    }

  };

  this.getOption = function(option){
    return this.config[option] ? this.config[option] : null;
  };

  return true;
}

/**
 * DropdownSimple constructor.
 *
 * @param {jQuery} element
 * @param {Object} config
 * @constructor 
 */
function DropdownSimple(element, config) {
  if( !AbstractDropdown.call(this, element, config) ) return;

  Object.defineProperty(this, 'defaultState', {
    get: function() {
      return this.target.hasClass('u-dropdown--hidden');
    }
  });

  this.target.addClass('u-dropdown--simple');

  this.hide();
}

/**
 * Shows dropdown.
 *
 * @public 
 * @return {DropdownSimple}
 */
DropdownSimple.prototype.show = function() {

  this.smartPosition(this.target);

  this.target.removeClass('u-dropdown--hidden');
  if( this.allInvokers.length ) this.allInvokers.attr('aria-expanded', 'true');
  this.config.afterOpen.call(this.target, this.element);

  return this;
}

/**
 * Hides dropdown.
 *
 * @public 
 * @return {DropdownSimple}
 */
DropdownSimple.prototype.hide = function() {

  this.target.addClass('u-dropdown--hidden');
  if( this.allInvokers.length ) this.allInvokers.attr('aria-expanded', 'false');
  this.config.afterClose.call(this.target, this.element);

  return this;
}

/**
 * DropdownCSSAnimation constructor.
 *
 * @param {jQuery} element
 * @param {Object} config
 * @constructor 
 */
function DropdownCSSAnimation(element, config) {
  if( !AbstractDropdown.call(this, element, config) ) return;

  var self = this;

  this.target
      .addClass('u-dropdown--css-animation u-dropdown--hidden')
      .css('animation-duration', self.config.dropdownDuration + 'ms');

  Object.defineProperty(this, 'defaultState', {
    get: function() {
      return this.target.hasClass('u-dropdown--hidden');
    }
  });

  if(this.target.length) {

    this.target.on('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', function(e){

      if( self.target.hasClass( self.config.dropdownAnimationOut ) ) {
        self.target.removeClass( self.config.dropdownAnimationOut )
                   .addClass('u-dropdown--hidden');

        if( self.allInvokers.length ) self.allInvokers.attr('aria-expanded', 'false');

        self.config.afterClose.call(self.target, self.element);
      }

      if( self.target.hasClass( self.config.dropdownAnimationIn ) ) {

        if( self.allInvokers.length ) self.allInvokers.attr('aria-expanded', 'true');

        self.config.afterOpen.call(self.target, self.element);
      }

      e.preventDefault();
      e.stopPropagation();
    });

  }
}

/**
 * Shows dropdown.
 *
 * @public 
 * @return {DropdownCSSAnimation}
 */
DropdownCSSAnimation.prototype.show = function() {

  this.smartPosition(this.target);

  this.target.removeClass( 'u-dropdown--hidden' )
             .removeClass( this.config.dropdownAnimationOut )
             .addClass( this.config.dropdownAnimationIn );

}

/**
 * Hides dropdown.
 *
 * @public 
 * @return {DropdownCSSAnimation}
 */
DropdownCSSAnimation.prototype.hide = function() {

  this.target.removeClass( this.config.dropdownAnimationIn )
             .addClass( this.config.dropdownAnimationOut );

}

/**
 * DropdownSlide constructor.
 *
 * @param {jQuery} element
 * @param {Object} config
 * @constructor 
 */
function DropdownJSlide(element, config) {
  if( !AbstractDropdown.call(this, element, config) ) return;

  this.target.addClass('u-dropdown--jquery-slide u-dropdown--hidden').hide();

  Object.defineProperty(this, 'defaultState', {
    get: function() {
      return this.target.hasClass('u-dropdown--hidden');
    }
  });
}

/**
 * Shows dropdown.
 *
 * @public 
 * @return {DropdownJSlide}
 */
DropdownJSlide.prototype.show = function() {

  var self = this;

  this.smartPosition(this.target);

  this.target.removeClass('u-dropdown--hidden').stop().slideDown({
    duration: self.config.dropdownDuration,
    easing: self.config.dropdownEasing,
    complete: function(){
      self.config.afterOpen.call(self.target, self.element);
    }
  });

}

/**
 * Hides dropdown.
 *
 * @public 
 * @return {DropdownJSlide}
 */
DropdownJSlide.prototype.hide = function() {

  var self = this;

  this.target.stop().slideUp({
    duration: self.config.dropdownDuration,
    easing: self.config.dropdownEasing,
    complete: function(){
      self.config.afterClose.call(self.target, self.element);
      self.target.addClass('u-dropdown--hidden');
    }
  });

}

})(jQuery);