text: /**
   * Mosaic Flow
   *
   * Pinterest like responsive image grid that doesn’t sucks
   *
   * @requires jQuery
   * @author Artem Sapegin
   * @copyright 2012 Artem Sapegin, http://sapegin.me
   * @license MIT
   */
  
  /*jshint browser:true, jquery:true, white:false, smarttabs:true */
  /*global jQuery:false, define:false*/
  (function(factory) {  // Try to register as an anonymous AMD module
  	if (typeof define === 'function' && define.amd) {
  		define(['jquery'], factory);
  	}
  	else {
  		factory(jQuery);
  	}
  }(function($) {
  	'use strict';
  	var cnt = 0;
  
  	$.fn.mosaicflow = function(options) {
  		var args = Array.prototype.slice.call(arguments, 0);
  
  		return this.each(function() {
  			var elm = $(this);
  			var data = elm.data('mosaicflow');
  
  			if (!data) {
  				options = $.extend({}, $.fn.mosaicflow.defaults, options, dataToOptions(elm));
  				data = new Mosaicflow(elm, options);
  				elm.data('mosaicflow', data);
  			}
  			else if (typeof options === 'string') {
  				data[options](args[1]);
  			}
  		});
  	};
  
  	$.fn.mosaicflow.defaults = {
  		itemSelector: '> *',
  		columnClass: 'mosaicflow__column',
  		minItemWidth: 240,
  		minColumns: 2,
  		itemHeightCalculation: 'auto',
  		threshold: 40
  	};
  
  	function Mosaicflow(container, options) {
  		this.container = container;
  		this.options = options;
  
  		this.container.trigger('mosaicflow-start');
  		this.init();
  		this.container.trigger('mosaicflow-ready');
  	}
  
  	Mosaicflow.prototype = {
  		init: function() {
  			this.__uid = cnt++;
  			this.__uidItemCounter = 0;
  			this.items = this.container.find(this.options.itemSelector);
  			this.columns = $([]);
  			this.columnsHeights = [];
  			this.itemsHeights = {};
  			this.tempContainer = $('<div>').css({'visibility': 'hidden', 'width': '100%'});
  			this.workOnTemp = false;
  			this.autoCalculation = this.options.itemHeightCalculation === 'auto';
  
  			this.container.append(this.tempContainer);
  
  			var that = this;
  			this.items.each(function() {
  				var elm = $(this);
  				var id = elm.attr('id');
  				if (!id) {
  					// Generate an unique id
  					id = that.generateUniqueId();
  					elm.attr('id', id);
  				}
  			});
  
  			this.container.css('visibility', 'hidden');
  			if (this.autoCalculation) {
  				$(window).on('load', $.proxy(this.refill, this));
  			}
  			else {
  				this.refill();
  			}
  			$(window).resize($.proxy(this.refill, this));
  		},
  
  		refill: function() {
  			this.container.trigger('mosaicflow-fill');
  			this.numberOfColumns = Math.floor(this.container.width() / this.options.minItemWidth);
  			// Always keep min columns number
  			if (this.numberOfColumns < this.options.minColumns)
  				this.numberOfColumns = this.options.minColumns;
  
  			var needToRefill = this.ensureColumns();
  			if (needToRefill) {
  				this.fillColumns();
  
  				// Remove excess columns, only if there are visible columns remaining
  				if (this.columns.filter(':visible').length > 0) {
  					this.columns.filter(':hidden').remove();
  				}
  			}
  			this.container.css('visibility', 'visible');
  			this.container.trigger('mosaicflow-filled');
  		},
  
  		ensureColumns: function() {
  			var createdCnt = this.columns.filter(':visible').length;
  			var calculatedCnt = this.numberOfColumns;
  
  			this.workingContainer = createdCnt === 0 ? this.tempContainer : this.container;
  
  			if (calculatedCnt > createdCnt) {
  				var neededCnt = calculatedCnt - createdCnt;
  				for (var columnIdx = 0; columnIdx < neededCnt; columnIdx++) {
  					var column = $('<div>', {
  						'class': this.options.columnClass
  					});
  
  					this.workingContainer.append(column);
  				}
  			}
  			else if (calculatedCnt < createdCnt) {
  				var lastColumn = createdCnt;
  				while (calculatedCnt <= lastColumn) {
  					// We can't remove columns here becase it will remove items to. So we hide it and will remove later.
  					this.columns.eq(lastColumn).hide();
  					lastColumn--;
  				}
  
  				var diff = createdCnt - calculatedCnt;
  				this.columnsHeights.splice(this.columnsHeights.length - diff, diff);
  			}
  
  			if (calculatedCnt !== createdCnt) {
  				this.columns = this.workingContainer.find('.' + this.options.columnClass);
  				this.columns.css('width', (100 / calculatedCnt) + '%');
  				return true;
  			}
  
  			return false;
  		},
  
  		fillColumns: function() {
  			var columnsCnt = this.numberOfColumns;
  			var itemsCnt = this.items.length;
  
  			for (var columnIdx = 0; columnIdx < columnsCnt; columnIdx++) {
  				var column = this.columns.eq(columnIdx);
  				this.columnsHeights[columnIdx] = 0;
  				for (var itemIdx = columnIdx; itemIdx < itemsCnt; itemIdx += columnsCnt) {
  					var item = this.items.eq(itemIdx);
  					var height = 0;
  					column.append(item);
  
  					if (this.autoCalculation) {
  						// Check height after being placed in its column
  						height = item.outerHeight();
  					}
  					else {
  						// Read img height attribute
  						height = parseInt(item.find('img').attr('height'), 10);
  					}
  
  					this.itemsHeights[item.attr('id')] = height;
  					this.columnsHeights[columnIdx] += height;
  				}
  			}
  
  			this.levelBottomEdge(this.itemsHeights, this.columnsHeights);
  
  			if (this.workingContainer === this.tempContainer) {
  				this.container.append(this.tempContainer.children());
  			}
  			this.container.trigger('mosaicflow-layout');
  		},
  
  		levelBottomEdge: function(itemsHeights, columnsHeights) {
  			while (true) {
  				var lowestColumn = $.inArray(Math.min.apply(null, columnsHeights), columnsHeights);
  				var highestColumn = $.inArray(Math.max.apply(null, columnsHeights), columnsHeights);
  				if (lowestColumn === highestColumn) return;
  
  				var lastInHighestColumn = this.columns.eq(highestColumn).children().last();
  				var lastInHighestColumnHeight = itemsHeights[lastInHighestColumn.attr('id')];
  				var lowestHeight = columnsHeights[lowestColumn];
  				var highestHeight = columnsHeights[highestColumn];
  				var newLowestHeight = lowestHeight + lastInHighestColumnHeight;
  
  				if (newLowestHeight >= highestHeight) return;
  
  				if (highestHeight - newLowestHeight < this.options.threshold) return;
  
  				this.columns.eq(lowestColumn).append(lastInHighestColumn);
  				columnsHeights[highestColumn] -= lastInHighestColumnHeight;
  				columnsHeights[lowestColumn] += lastInHighestColumnHeight;
  			}
  		},
  
  		add: function(elm) {
  			this.container.trigger('mosaicflow-item-add', [elm]);
  			var lowestColumn = $.inArray(Math.min.apply(null, this.columnsHeights), this.columnsHeights);
  			var height = 0;
  
  			if (this.autoCalculation) {
  
  				// Get height of elm
  				elm.css({
  					position: 'static',
  					visibility: 'hidden',
  					display: 'block'
  				}).appendTo(this.columns.eq(lowestColumn));
  
  				height = elm.outerHeight();
  
  				var inlineImages = elm.find('img');
  				if (inlineImages.length !== 0) {
  					inlineImages.each(function() {
  						var image = $(this);
  						var imageSizes = getImageSizes(image);
  						var actualHeight = (image.width() * imageSizes.height) / imageSizes.width;
  
  						height += actualHeight;
  					});
  				}
  
  				elm.detach().css({
  					position: 'static',
  					visibility: 'visible'
  				});
  			}
  			else {
  				height = parseInt(elm.find('img').attr('height'), 10);
  			}
  
  			if (!elm.attr('id')) {
  				// Generate a unique id
  				elm.attr('id', this.generateUniqueId());
  			}
  
  			// Update item collection.
  			// Item needs to be placed at the end of this.items to keep order of elements
  			var itemsArr = this.items.toArray();
  			itemsArr.push(elm);
  			this.items = $(itemsArr);
  
  			this.itemsHeights[elm.attr('id')] = height;
  			this.columnsHeights[lowestColumn] += height;
  			this.columns.eq(lowestColumn).append(elm);
  
  			this.levelBottomEdge(this.itemsHeights, this.columnsHeights);
  			this.container.trigger('mosaicflow-layout');
  			this.container.trigger('mosaicflow-item-added', [elm]);
  		},
  
  		remove: function(elm) {
  			this.container.trigger('mosaicflow-item-remove', [elm]);
  			var column = elm.parents('.' + this.options.columnClass);
  
  			// Update column height
  			this.columnsHeights[column.index() - 1] -= this.itemsHeights[elm.attr('id')];
  
  			elm.detach();
  
  			// Update item collection
  			this.items = this.items.not(elm);
  			this.levelBottomEdge(this.itemsHeights, this.columnsHeights);
  			this.container.trigger('mosaicflow-layout');
  			this.container.trigger('mosaicflow-item-removed', [elm]);
  		},
  
  		empty: function() {
  			var columnsCnt = this.numberOfColumns;
  
  			this.items = $([]);
  			this.itemsHeights = {};
  
  			for (var columnIdx = 0; columnIdx < columnsCnt; columnIdx++) {
  				var column = this.columns.eq(columnIdx);
  				this.columnsHeights[columnIdx] = 0;
  				column.empty();
  			}
  			this.container.trigger('mosaicflow-layout');
  		},
  
  		recomputeHeights: function() {
  			function computeHeight(idx, item) {
  				item = $(item);
  				var height = 0;
  				if (that.autoCalculation) {
  					// Check height after being placed in its column
  					height = item.outerHeight();
  				}
  				else {
  					// Read img height attribute
  					height = parseInt(item.find('img').attr('height'), 10);
  				}
  
  				that.itemsHeights[item.attr('id')] = height;
  				that.columnsHeights[columnIdx] += height;
  			}
  
  			var that = this;
  			var columnsCnt = this.numberOfColumns;
  
  			for (var columnIdx = 0; columnIdx < columnsCnt; columnIdx++) {
  				var column = this.columns.eq(columnIdx);
  
  				this.columnsHeights[columnIdx] = 0;
  				column.children().each(computeHeight);
  			}
  		},
  
  		generateUniqueId: function() {
  			// Increment the counter
  			this.__uidItemCounter++;
  
  			// Return an unique ID
  			return 'mosaic-' + this.__uid + '-itemid-' + this.__uidItemCounter;
  		}
  	};
  
  	// Camelize data-attributes
  	function dataToOptions(elem) {
  		function upper(m, l) {
  			return l.toUpper();
  		}
  		var options = {};
  		var data = elem.data();
  		for (var key in data) {
  			options[key.replace(/-(\w)/g, upper)] = data[key];
  		}
  		return options;
  	}
  
  	function getImageSizes(image) {
  		var sizes = {};
  
  		sizes.height = parseInt(image.attr('height'), 10);
  		sizes.width = parseInt(image.attr('width'), 10);
  
  		if (sizes.height === 0 || sizes.width === 0) {
  			var utilImage = new Image();
  			utilImage.src = image.attr('src');
  
  			sizes.width = utilImage.width;
  			sizes.height = utilImage.height;
  		}
  
  		return sizes;
  	}
  
  	// Auto init
  	$(function() { $('.mosaicflow').mosaicflow(); });
  
  }));
  