/*!
 * @preserve
 *
 * Readmore.js jQuery plugin
 * Author: @jed_foster
 * Project home: http://jedfoster.github.io/Readmore.js
 * Licensed under the MIT license
 *
 * Debounce function from http://davidwalsh.name/javascript-debounce-function
 */

/* global jQuery */
var gpr_vars;
(function (factory) {
	if (typeof define === 'function' && define.amd) {
		// AMD
		define(['jquery'], factory);
	} else if (typeof exports === 'object') {
		// CommonJS
		module.exports = factory(require('jquery'));
	} else {
		// Browser globals
		factory(jQuery);
	}
}(function ($) {
	'use strict';

	var readmore = 'readmore',
		defaults = {
			speed: 100,
			collapsedHeight: 200,
			heightMargin: 16,
			moreLink: '<a href="#">' + gpr_vars.read_more + '</a>',
			lessLink: '<a href="#">' + gpr_vars.read_less + '</a>',
			embedCSS: true,
			blockCSS: 'display: block; width: 100%;',
			startOpen: false,

			// callbacks
			blockProcessed: function () {
			},
			beforeToggle: function () {
			},
			afterToggle: function () {
			}
		},
		cssEmbedded = {},
		uniqueIdCounter = 0;

	function debounce(func, wait, immediate) {
		var timeout;

		return function () {
			var context = this, args = arguments;
			var later = function () {
				timeout = null;
				if (!immediate) {
					func.apply(context, args);
				}
			};
			var callNow = immediate && !timeout;

			clearTimeout(timeout);
			timeout = setTimeout(later, wait);

			if (callNow) {
				func.apply(context, args);
			}
		};
	}

	function uniqueId(prefix) {
		var id = ++uniqueIdCounter;

		return String(prefix === null ? 'rmjs-' : prefix) + id;
	}

	function setBoxHeights(element) {
		var el = element.clone().css({
				height: 'auto',
				width: element.width(),
				maxHeight: 'none',
				overflow: 'hidden'
			}).insertAfter(element),
			expandedHeight = el.outerHeight(),
			cssMaxHeight = parseInt(el.css({maxHeight: ''}).css('max-height').replace(/[^-\d\.]/g, ''), 10),
			defaultHeight = element.data('defaultHeight');

		el.remove();

		var collapsedHeight = cssMaxHeight || element.data('collapsedHeight') || defaultHeight;

		// Store our measurements.
		element.data({
			expandedHeight: expandedHeight,
			maxHeight: cssMaxHeight,
			collapsedHeight: collapsedHeight
		})
		// and disable any `max-height` property set in CSS
			.css({
				maxHeight: 'none'
			});
	}

	var resizeBoxes = debounce(function () {
		$('[data-readmore]').each(function () {
			var current = $(this),
				isExpanded = (current.attr('aria-expanded') === 'true');

			setBoxHeights(current);

			current.css({
				height: current.data((isExpanded ? 'expandedHeight' : 'collapsedHeight'))
			});
		});
	}, 100);

	function embedCSS(options) {
		if (!cssEmbedded[options.selector]) {
			var styles = ' ';

			if (options.embedCSS && options.blockCSS !== '') {
				styles += options.selector + ' + [data-readmore-toggle], ' +
					options.selector + '[data-readmore]{' +
					options.blockCSS +
					'}';
			}

			// Include the transition CSS even if embedCSS is false
			styles += options.selector + '[data-readmore]{' +
				'transition: height ' + options.speed + 'ms;' +
				'overflow: hidden;' +
				'}';

			(function (d, u) {
				var css = d.createElement('style');
				css.type = 'text/css';

				if (css.styleSheet) {
					css.styleSheet.cssText = u;
				}
				else {
					css.appendChild(d.createTextNode(u));
				}

				d.getElementsByTagName('head')[0].appendChild(css);
			}(document, styles));

			cssEmbedded[options.selector] = true;
		}
	}

	function Readmore(element, options) {
		this.element = element;

		this.options = $.extend({}, defaults, options);

		embedCSS(this.options);

		this._defaults = defaults;
		this._name = readmore;

		this.init();

		// IE8 chokes on `window.addEventListener`, so need to test for support.
		if (window.addEventListener) {
			// Need to resize boxes when the page has fully loaded.
			window.addEventListener('load', resizeBoxes);
			window.addEventListener('resize', resizeBoxes);
		}
		else {
			window.attachEvent('load', resizeBoxes);
			window.attachEvent('resize', resizeBoxes);
		}
	}


	Readmore.prototype = {
		init: function () {
			var current = $(this.element);

			current.data({
				defaultHeight: this.options.collapsedHeight,
				heightMargin: this.options.heightMargin
			});

			setBoxHeights(current);

			var collapsedHeight = current.data('collapsedHeight'),
				heightMargin = current.data('heightMargin');

			if (current.outerHeight(true) <= collapsedHeight + heightMargin) {
				// The block is shorter than the limit, so there's no need to truncate it.
				if (this.options.blockProcessed && typeof this.options.blockProcessed === 'function') {
					this.options.blockProcessed(current, false);
				}
				return true;
			}
			else {
				var id = current.attr('id') || uniqueId(),
					useLink = this.options.startOpen ? this.options.lessLink : this.options.moreLink;

				current.attr({
					'data-readmore': '',
					'aria-expanded': this.options.startOpen,
					'id': id
				});

				current.after($(useLink)
					.on('click', (function (_this) {
						return function (event) {
							_this.toggle(this, current[0], event);
						};
					})(this))
					.attr({
						'data-readmore-toggle': id,
						'aria-controls': id
					}));

				if (!this.options.startOpen) {
					current.css({
						height: collapsedHeight
					});
				}

				if (this.options.blockProcessed && typeof this.options.blockProcessed === 'function') {
					this.options.blockProcessed(current, true);
				}
			}
		},

		toggle: function (trigger, element, event) {
			if (event) {
				event.preventDefault();
			}

			if (!trigger) {
				trigger = $('[aria-controls="' + this.element.id + '"]')[0];
			}

			if (!element) {
				element = this.element;
			}

			var $element = $(element),
				newHeight = '',
				newLink = '',
				expanded = false,
				collapsedHeight = $element.data('collapsedHeight');

			if ($element.height() <= collapsedHeight) {
				newHeight = $element.data('expandedHeight') + 'px';
				newLink = 'lessLink';
				expanded = true;
			}
			else {
				newHeight = collapsedHeight;
				newLink = 'moreLink';
			}

			// Fire beforeToggle callback
			// Since we determined the new "expanded" state above we're now out of sync
			// with our true current state, so we need to flip the value of `expanded`
			if (this.options.beforeToggle && typeof this.options.beforeToggle === 'function') {
				this.options.beforeToggle(trigger, $element, !expanded);
			}

			$element.css({'height': newHeight});

			// Fire afterToggle callback
			$element.on('transitionend', (function (_this) {
				return function () {
					if (_this.options.afterToggle && typeof _this.options.afterToggle === 'function') {
						_this.options.afterToggle(trigger, $element, expanded);
					}

					$(this).attr({
						'aria-expanded': expanded
					}).off('transitionend');
				};
			})(this));

			$(trigger).replaceWith($(this.options[newLink])
				.on('click', (function (_this) {
					return function (event) {
						_this.toggle(this, element, event);
					};
				})(this))
				.attr({
					'data-readmore-toggle': $element.attr('id'),
					'aria-controls': $element.attr('id')
				}));
		},

		destroy: function () {
			$(this.element).each(function () {
				var current = $(this);

				current.attr({
					'data-readmore': null,
					'aria-expanded': null
				})
					.css({
						maxHeight: '',
						height: ''
					})
					.next('[data-readmore-toggle]')
					.remove();

				current.removeData();
			});
		}
	};


	$.fn.readmore = function (options) {
		var args = arguments,
			selector = this.selector;

		options = options || {};

		if (typeof options === 'object') {
			return this.each(function () {
				if ($.data(this, 'plugin_' + readmore)) {
					var instance = $.data(this, 'plugin_' + readmore);
					instance.destroy.apply(instance);
				}

				options.selector = selector;

				$.data(this, 'plugin_' + readmore, new Readmore(this, options));
			});
		}
		else if (typeof options === 'string' && options[0] !== '_' && options !== 'init') {
			return this.each(function () {
				var instance = $.data(this, 'plugin_' + readmore);
				if (instance instanceof Readmore && typeof instance[options] === 'function') {
					instance[options].apply(instance, Array.prototype.slice.call(args, 1));
				}
			});
		}
	};

}));