/**
 * 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);
