/**
 * Mosaic Flow
 *
 * Pinterest like responsive image grid that doesnt suck
 *
 * @requires jQuery
 * @author Artem Sapegin
 * @copyright 2012 Artem Sapegin, http://sapegin.me
 * @license MIT
 */
/* taken at commit https://github.com/sapegin/jquery.mosaicflow/commit/20cea9ae73bab6bfeda54434564aaf0f86bed9ac
 * which includes fn.load()
/*
/*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,
		fallbackHTML: false
	};

	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 = $('<' + ( this.options.fallbackHTML ? 'div' : 'li' ) + '>', {
				css: { 'visibility': 'hidden',	'width': '100%' },
				role: this.options.fallbackHTML ? 'listitem' : null,
			});
			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 = $('<' + ( this.options.fallbackHTML ? 'div' : 'li' ) + '>', {
						class: this.options.columnClass,
						role: this.options.fallbackHTML ? 'listitem' : null
					});

					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(); });

}));
