/**
 * Unfold Content component.
 *
 * @author Htmlstream
 * @version 1.0
 */
;
(function ($) {
	'use strict';

	$.HSCore.components.HSUnfold = {

		/**
		 * Base configuration of the component.
		 *
		 * @private
		 */
		_baseConfig: {
			unfoldEvent: 'click',
			unfoldType: 'simple',
			unfoldDuration: 300,
			unfoldEasing: 'linear',
			unfoldAnimationIn: 'fadeIn',
			unfoldAnimationOut: 'fadeOut',
			unfoldHideOnScroll: true,
			unfoldHideOnBlur: false,
			unfoldDelay: 350,
			unfoldOpenedElement: 'init',
			unfoldOverlay: false,
			afterOpen: function (invoker) {
			},
			beforeClose: 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;

			var fieldsQty;

			collection.each(function (i, el) {

				var $this = $(el), itemConfig;

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

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

				switch (itemConfig.unfoldType) {

					case 'css-animation' :

						$this.data('HSUnfold', new UnfoldCSSAnimation($this, itemConfig));

						break;

					case 'jquery-slide' :

						$this.data('HSUnfold', new UnfoldJSlide($this, itemConfig));

						break;

					default :

						$this.data('HSUnfold', new UnfoldSimple($this, itemConfig));

				}

				self._pageCollection = self._pageCollection.add($this);
				self._bindEvents($this, itemConfig.unfoldEvent, itemConfig.unfoldDelay);
				var UnFold = $(el).data('HSUnfold');

				fieldsQty = $(UnFold.target).find('input, textarea').length;

				if ($(UnFold.target).find('[data-unfold-target]').length) {

					$this.addClass('target-of-invoker-has-unfolds');

				}

			});

			$(document).on('click touchstart', 'body', function (e) {

				if (e.target.id === self._baseConfig.unfoldOpenedElement) return;

				if ($(e.target).closest('#' + self._baseConfig.unfoldOpenedElement).length) return;

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

					var windW = window.innerWidth,
						optIsMobileOnly = Boolean($(el).data('is-mobile-only'));

					if (!optIsMobileOnly) {

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

					} else if (optIsMobileOnly && windW < 769) {

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

					}

					if ($(el).data('HSUnfold').config.unfoldOverlay) {

						// $(el).data('HSUnfold').config.unfoldDelay = setTimeout(function () {

							$('.' + $(el).data('HSUnfold').config.unfoldOverlay.className).fadeOut($(el).data('HSUnfold').config.unfoldOverlay.animationSpeed ? $(el).data('HSUnfold').config.unfoldOverlay.animationSpeed : 200);

						// }, $(el).data('HSUnfold').config.unfoldDelay)

					}

					$(el).data('HSUnfold').config.beforeClose.call(self.target, self.element);

				});

			});

			$(window).on('scroll.HSUnfold', function () {

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

					var UnFold = $(el).data('HSUnfold');

					if (UnFold.getOption('unfoldHideOnScroll') && fieldsQty === 0) {

						UnFold.hide();

					} else if (UnFold.getOption('unfoldHideOnScroll') && !(/iPhone|iPad|iPod/i.test(navigator.userAgent))) {

						UnFold.hide();

					}

				});

			});

			$(window).on('resize.HSUnfold', function () {

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

				self._resizeTimeOutId = setTimeout(function () {

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

						var UnFold = $(el).data('HSUnfold');

						UnFold.smartPosition(UnFold.target);

					});

				}, 50);

			});

			$(document).on('keydown.HSUnfold', function (e) {

				if ($('body').hasClass('u-unfold-opened')) {

					if (e.keyCode && e.keyCode === 38 || e.keyCode && e.keyCode === 40) {

						e.preventDefault();

					}

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

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

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

						});

						$('body').removeClass('u-unfold-opened');

					}

				}

			});

			return collection;

		},

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

			var self = this,
				$unfold = $($invoker.data('unfold-target'));

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

				$invoker.on('mouseenter.HSUnfold', function () {

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

					if (!HSUnfold) return;

					if (HSUnfold.unfoldTimeOut) clearTimeout(HSUnfold.unfoldTimeOut);
					HSUnfold.show();
					$('body').addClass('u-unfold-opened');

				})
					.on('mouseleave.HSUnfold', function () {

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

						if (!HSUnfold) return;

						HSUnfold.unfoldTimeOut = setTimeout(function () {

							HSUnfold.hide();
							$('body').removeClass('u-unfold-opened');

						}, delay);

					});

				if ($unfold.length) {

					$unfold.on('mouseenter.HSUnfold', function () {

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

						if (HSUnfold.unfoldTimeOut) clearTimeout(HSUnfold.unfoldTimeOut);
						HSUnfold.show();

					})
						.on('mouseleave.HSUnfold', function () {

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

							HSUnfold.unfoldTimeOut = setTimeout(function () {
								HSUnfold.hide();
							}, delay);

						});
				}

			} else {

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

					var $curInvoker = $(this),
						$unfoldNotHasInnerUnfolds = $('[data-unfold-target].active:not(.target-of-invoker-has-unfolds)'),
						$unfoldHasInnerUnfold = $('[data-unfold-target].active.target-of-invoker-has-unfolds');

					self._baseConfig.unfoldOpenedElement = $curInvoker.data('HSUnfold').target[0].id;

					if (!$curInvoker.data('HSUnfold')) return;

					if (!$curInvoker.hasClass('target-of-invoker-has-unfolds')) {

						if ($unfoldNotHasInnerUnfolds.length) {

							$unfoldNotHasInnerUnfolds.data('HSUnfold').toggle();

						}

					} else {

						if ($unfoldHasInnerUnfold.length) {

							$unfoldHasInnerUnfold.data('HSUnfold').toggle();

						}

					}

					$curInvoker.data('HSUnfold').toggle();
					$('body').toggleClass('u-unfold-opened');

					e.stopPropagation();

					e.preventDefault();

				});

				if (Boolean($invoker.data('unfold-target-is-menu'))) {

					var $target = $($invoker.data('unfold-target')),
						$targetItems = $target.children();

					$targetItems.on('click', function () {

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

					});

				}

			}

		}
	};

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

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

		var $self = this;

		if (!element.length) return false;

		$self.element = element;
		$self.config = config;

		$self.target = $($self.element.data('unfold-target'));

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

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

			var dataset = $self.element[0].dataset,
				overlay = dataset.unfoldOverlay ? JSON.parse(dataset.unfoldOverlay) : false;

			if ($self.defaultState) {

				if (dataset.unfoldOverlay) {

					if (!$('.' + overlay.className).length) {

						$('<div class="' + overlay.className + '" style="background-color: ' + overlay.background + '"></div>').prependTo('body');

					}

					if ($('.' + overlay.className).length) {

						$('.' + overlay.className).fadeIn(overlay.animationSpeed ? overlay.animationSpeed : 200);

					}

				}

				$self.show();

			} else {

				if (dataset.unfoldOverlay) {

					if ($('.' + overlay.className).length) {

						// $self.config.unfoldTimeOut = setTimeout(function () {

							$('.' + overlay.className).fadeOut(overlay.animationSpeed ? overlay.animationSpeed : 200);

						// }, $self.config.unfoldDelay)

					}

				}

				$self.hide();

			}

			return $self;
		};

		this.smartPosition = function (target) {

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

			target.removeClass('u-unfold--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-unfold--reverse-y');

			}

		};

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

		return true;
	}


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

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

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

		this.hide();
	}

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

		var self = this;

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

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

		if (this.target.length) {

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

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

					self.target.removeClass(self.config.unfoldAnimationOut)
						.addClass('u-unfold--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.unfoldAnimationIn)) {

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

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

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

		}
	}

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

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

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

	/**
	 * Shows Unfold.
	 *
	 * @public
	 * @return {UnfoldSimple}
	 */
	UnfoldSimple.prototype.show = function () {

		var activeEls = $(this)[0].config.unfoldTarget;

		$('[data-unfold-target="' + activeEls + '"]').addClass('active');

		this.smartPosition(this.target);

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

		return this;
	};

	/**
	 * Hides Unfold.
	 *
	 * @public
	 * @return {UnfoldSimple}
	 */
	UnfoldSimple.prototype.hide = function () {

		var activeEls = $(this)[0].config.unfoldTarget;

		$('[data-unfold-target="' + activeEls + '"]').removeClass('active');

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

		return this;
	};

	/**
	 * Shows Unfold.
	 *
	 * @public
	 * @return {UnfoldCSSAnimation}
	 */
	UnfoldCSSAnimation.prototype.show = function () {

		var self = this,
			activeEls = $(self)[0].config.unfoldTarget;

		$('[data-unfold-target="' + activeEls + '"]').addClass('active');

		self.smartPosition(self.target);

		self.target.removeClass('u-unfold--hidden')
			.removeClass(self.config.unfoldAnimationOut);

		// if (!self.config.unfoldOverlay) {

			self.target.addClass(self.config.unfoldAnimationIn);

		// } else {

		// 	setTimeout(function () {

		// 		self.target.addClass(self.config.unfoldAnimationIn);

		// 	});

		// }

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

	};

	/**
	 * Hides Unfold.
	 *
	 * @public
	 * @return {UnfoldCSSAnimation}
	 */
	UnfoldCSSAnimation.prototype.hide = function () {

		var activeEls = $(this)[0].config.unfoldTarget;

		$('[data-unfold-target="' + activeEls + '"]').removeClass('active');

		this.target.removeClass(this.config.unfoldAnimationIn)
			.addClass(this.config.unfoldAnimationOut);

	};

	/**
	 * Shows Unfold.
	 *
	 * @public
	 * @return {UnfoldJSlide}
	 */
	UnfoldJSlide.prototype.show = function () {

		var self = this;

		var activeEls = $(this)[0].config.unfoldTarget;

		$('[data-unfold-target="' + activeEls + '"]').addClass('active');

		this.smartPosition(this.target);

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

	};

	/**
	 * Hides Unfold.
	 *
	 * @public
	 * @return {UnfoldJSlide}
	 */
	UnfoldJSlide.prototype.hide = function () {

		var self = this;

		var activeEls = $(this)[0].config.unfoldTarget;

		$('[data-unfold-target="' + activeEls + '"]').removeClass('active');

		this.target.slideUp({
			duration: self.config.unfoldDuration,
			easing: self.config.unfoldEasing,
			complete: function () {
				self.config.afterClose.call(self.target, self.element);
				self.target.addClass('u-unfold--hidden');
			}
		});

	}

})(jQuery);
