/* Simple min-height-masonry layout plugin.
Like masonry column shift, but works. */
;(function($) {

	'use strict';

	var cssPrefix = detectCSSPrefix();

	var Waterfall = function(el, opts) {
		this.$el = $(el);
		this.el = el[0];
		this._create(opts);
	};

	Waterfall.defaultClass = 'waterfall';

	$.extend(Waterfall.prototype, {
		options: {
			colMinWidth: 300, //width of column, used to calculate number of columns possible to display
			defaultContainerWidth: window.clientWidth,
			autoresize: true,
			maxCols: 4, //used to restrict max number of columns
			updateDelay: 45, //how often to reflow layout on window resize
			useCalc: undefined, //set width through -prefix-calc value. Values: true, false, undefined. Autodetection.
			useTranslate3d: undefined, //place items through translate3d instead of top/left. Values: true, false, undefined. Autodetection
			animateShow: false, //whether to animate appending items (causes browser extra-reflows, slows down rendering)

			//callbacks
			reflow: null
		},

		_create: function(opts) {
			var self = this,
				o = self.options = $.extend({}, self.options, opts);

			this.items = [];

			//init some vars
			self.lastHeights = [];
			self.lastItems = [];
			self.colPriority = []; //most left = most minimal column
			self.baseOrder = [];

			var cStyle = getComputedStyle(self.el);
			self.el.hidden = true;
			self.el.style.minHeight = cStyle.height; //prevent scrollbar width changing
			if (self.$el.css('position') === 'static') self.el.style.position = 'relative';

			//detect placing mode needed
			if (o.useCalc === undefined) {
				//transform calc detect
				this.prefixedCalc = (function() {
					var dummy = document.createElement('div'),
						props = ['calc', '-webkit-calc', '-moz-calc', '-o-calc'];
					for (var i = 0; i < props.length; ++i) {
						var prop = props[i], propStr =  prop + '(1px)';
						dummy.style.cssText = cssPrefix + 'transform: translate3d(' + [propStr, propStr, propStr].join(',') +');';
						//console.log(dummy.style[cssPrefix + 'transform'])
						if (dummy.style.length && dummy.style[cssPrefix + 'transform'].length > 14) {
							return prop;
						}
					}
				})();
				o.useCalc = !!this.prefixedCalc;
			}
			//console.log(this.prefixedCalc);
			if (o.useTranslate3d === undefined) {
				this.prefixedTranslate3d = (function() {
					var dummy = document.createElement('div');
					var props = ['translate3d', '-webkit-translate3d', '-moz-translate3d', '-o-translate3d'];
					for (var i = 0; i < props.length; ++i) {
						var prop = props[i];
						dummy.style.cssText = cssPrefix + 'transform:' + prop + '(1px, 0, 0);';
						if (dummy.style.length)
							return prop;
					}
				})();
				o.useTranslate3d = !! this.prefixedTranslate3d;
			}
			//console.log(this.prefixedTranslate3d)

			//populate items
			var items;
			{
				items = self.$el.children();
			}

			//remove text nodes
			for (var i = 0; i < self.el.childNodes.length;) {
				if (self.el.childNodes[i].nodeType !== 1 && self.el.childNodes[i].nodeType !== 8){
					self.el.removeChild(self.el.childNodes[i]);
				} else i++;
			}

			items.each(function(i, e) {
				//self.items[i].data('id', i);
				self._addItem(e);
				self._initItem(e);
			});

			self.lastItem = self.items[self.items.length - 1];

			self.el.removeAttribute("hidden");

			self._update();

			if (o.autoresize) {
				$(window)
					.resize(self.reflow.bind(self));
			}

			this._observeMutations();
		},

		_addItem: function(item){
			if (item.getAttribute("data-exclude")) return;
			this.items.push(item);
		},

		_observeMutations: function() {
			//Make Node changing observer - the fastest way to add items
			if (window.MutationObserver) {
				//FF, chrome
				this.observer = new MutationObserver(function(mutations) {
					var mNum = mutations.length;
					for (var i = 0; i < mNum; i++) {
						//console.log(mutations[i])
						if (mutations[i].removedNodes.length) {
							this._removedItems(Array.prototype.slice.apply(mutations[i].removedNodes));
						}
						if (mutations[i].addedNodes.length) {
							var nodes = Array.prototype.slice.apply(mutations[i].addedNodes);
							if (mutations[i].nextSibling) {
								this._insertedItems(nodes);
							} else {
								this._appendedItems(nodes);
							}
						}
					}
				}.bind(this));

				this.observer.observe(this.el, {
					attributes: false,
					childList: true,
					characterData: false
				});
			} else {
				//opera, ie
				this.$el.on('DOMNodeInserted', function(e) {
					var evt = (e.originalEvent || e),
						target = evt.target;

					if (target.nodeType !== 1 && target.nodeType !== 8) return;
					if (target.parentNode !== this.el) return; //if insertee is below container
					//console.log("--------" + target.className + " next:" + target.nextSibling + " prev:" + target.previousSibling)

					if (target.previousSibling && target.previousSibling.span && (!target.nextSibling || !target.nextSibling.span)) {
						this._appendedItems([target]); //append specific case, times faster than _insertedItems
					} else {
						this._insertedItems([target]);
					}
				}.bind(this)).on('DOMNodeRemoved', function(e) {
					var el = (e.originalEvent || e).target;

					if (el.nodeType !== 1 && el.nodeType !== 8) return;
					if (el.parentNode !== this.el) return; //if insertee is below container

					this._removedItems([el]);
				}.bind(this));
			}
		},

		//==========================API
		//Ensures column number correct, reallocates items
		reflow: function() {
			var self = this,
				o = self.options;

			window.clearTimeout(self._updateInterval);
			self._updateInterval = window.setTimeout(self._update.bind(self), o.updateDelay);

			return self;
		},

		//========================= Techs
		//called by mutation observer
		_appendedItems: function(items) {
			var l = items.length,
				i = 0;
			//console.log("append: " + this.items.length) //carl
			for (; i < l; i++) {
				var el = items[i];
				if (el.nodeType !== 1) continue;
				this._addItem(el);
				this._initItem(el); //TODO: optimize
				this._setItemWidth(el);
			}

			for (i = 0; i < l; i++) {
			    if (items[i].nodeType !== 1) continue;
				this._placeItem(items[i]);
			}

			this.lastItem = this.items[this.items.length - 1];

			this._maximizeHeight();
		},

		//if new items inserted somewhere inside the list
		_insertedItems: function(items) {
			//console.log("insert: " + this.items.length) //carl
			//clear old items
			this.items.length = 0;

			//init new items
			var l = items.length;
			for (var i = 0; i < l; i++) {
				var el = items[i];
				if (el.nodeType !== 1 && el.nodeType !== 8) continue;
				this._initItem(el); //TODO: optimize
				this._setItemWidth(el);
			}

			//reinit all items
			var children = this.el.childNodes,
				itemsL = children.length;

			for (var i = 0; i < itemsL; i++){
				if (children[i].nodeType !== 1 && el.nodeType !== 8) continue;
				if (!children[i].span) continue;
				this._addItem(children[i]);
			}
			this.lastItem = this.items[this.items.length - 1];

			this.reflow();
		},

		//called by mutation observer
		_removedItems: function(items) {
			var childItems = this.el.childNodes,
				cl = childItems.length;
			//console.log("before removed: " + this.items.length)

			//reinit items
			for (var i = 0; i < items.length; i++){
				this.items.splice(this.items.indexOf(items[i]), 1);
			}

			//console.log("after remove:" + this.items.length)
			this.lastItem = this.items[this.items.length - 1];

			this.reflow();
		},

		//simple trigger routine
		_trigger: function(cbName, arg) {
			try {
				if (this.options[cbName]) this.options[cbName].call(this.$el, arg);
				this.$el.trigger(cbName, [arg]);
			} catch (err) {
				throw (err);
			}
		},

		//init item properties once item appended
		_initItem: function(el) {
			var o = this.options,
				span = el.getAttribute('data-span') || 1,
				floatVal = el.getAttribute('data-float') || el.getAttribute('data-column');

			//set span
			span = (span === 'all' ? o.maxCols : Math.max(0, Math.min(~~(span), o.maxCols)));
			el.span = span; //quite bad, but no choice: dataset is sloow

			//save heavy style-attrs
			var style = getComputedStyle(el);
			el.mr = ~~(style.marginRight.slice(0, -2));
			el.ml = ~~(style.marginLeft.slice(0, -2));
			el.bt = ~~(style.borderTopWidth.slice(0, -2));
			el.bb = ~~(style.borderBottomWidth.slice(0, -2));
			el.mt = ~~(style.marginTop.slice(0, -2)); //ignored because of offsetTop instead of style.top
			el.mb = ~~(style.marginBottom.slice(0, -2));

			//set style
			el.style.position = 'absolute';
			//this._setItemWidth(el); //make it external action to not to init frominside create

			//parset float
			switch (floatVal) {
				case null: //no float
					el.floatCol = null;
					break;
				case 'right':
				case 'last':
					el.floatCol = -span;
					break;
				case 'left':
				case 'first':
					el.floatCol = 0;
					break;
				default: //int column
					el.floatCol = ~~(floatVal) - 1;
					break;
			}

			if (o.animateShow) {
				if (o.useTranslate3d) {
					//TODO: this below crashes chrome
					//el.style[cssPrefix+'translate'] = 'translate3d(0, ' + this.lastHeights[this.colPriority[0]] + 'px ,0)'
				} else {
					el.style.top = this.lastHeights[this.colPriority[this.colPriority.length - 1]] + 'px';
					el.style.left = this.colWidth * this.colPriority[this.colPriority.length - 1] + 'px';
				}
				el.removeAttribute('hidden');
			}
		},

		_initLayoutParams: function() {
			var self = this,
				o = self.options,
				cStyle = window.getComputedStyle(self.el),
				i = 0,
				prevCols = self.lastItems.length;

			self.pl = ~~(cStyle.paddingLeft.slice(0, -2));
			self.pt = ~~(cStyle.paddingTop.slice(0, -2));
			self.pr = ~~(cStyle.paddingRight.slice(0, -2));
			self.pb = ~~(cStyle.paddingBottom.slice(0, -2));

			self.lastHeights.length = 0;
			self.colPriority.length = 0; //most left = most minimal column
			self.baseOrder.length = 0;

			self.colWidth = self.el.offsetWidth - self.pl - self.pr;

			self.lastItems.length = Math.min(~~(self.colWidth / o.colMinWidth) || 1,o.maxCols); //needed length
		 //console.log(self.lastItems.length+ ' ' +o.maxCols);

			var top = o.useTranslate3d ? 0 : self.pt;
			for (i = 0; i < self.lastItems.length; i++) {
				self.lastHeights.push(top);
				self.baseOrder.push(i);
				self.colPriority.push(i);
			}

			self.colWidth /= self.lastItems.length;

			//console.log(prevCols + '->' + self.lastItems.length);
			if (!o.useCalc || prevCols !== self.lastItems.length) {
				//set item widths carefully - if columns changed or px widths used
				for (i = self.items.length; i--;) {
					this._setItemWidth(self.items[i]);
				}
			}

			return self.lastItems.length;
		},

		//full update of layout
		_updateInterval: 0,
		_update: function(from, to) {
			//window.start = Date.now()
			var self = this,
				i = 0,
				start = from || 0,
				end = to || self.items.length,
				colsNeeded = self._initLayoutParams();

			//console.log('beforePlace:' + this.lastItems.length)
			for (i = start; i < end; i++) {
				self._placeItem(self.items[i]);
			}
			//console.log('afterPlace:' + this.lastItems.length)

			self._maximizeHeight();
			self._trigger('reflow');
			//console.log('time elapsed: ' + (Date.now() - window.start) + 'ms')
		},

		//set item width based on span/colWidth
		_setItemWidth: function(el) {
			var span = el.span > this.lastItems.length ? this.lastItems.length : el.span,
				cols = this.lastItems.length,
				colWeight = span / cols;
			if (this.options.useCalc) {
				el.w = (100 * colWeight);
				el.style.width = this.prefixedCalc + '(' + (100 * colWeight) + '% - ' + (el.mr + el.ml + (this.pl + this.pr) * colWeight) + 'px)';
			} else {
				el.w = ~~(this.colWidth * span - (el.ml + el.mr));
				el.style.width = el.w + 'px';
			}
		},

		_placeItem: function(e) {
			var self = this,
				o = self.options;

			var lastHeights = self.lastHeights,
				lastItems = self.lastItems,
				colPriority = self.colPriority,
				minCol = 0,
				minH = 0,
				h = 0,
				c = 0,
				t = 0,
				end = 0,
				start = 0,
				span = e.span > lastItems.length ? lastItems.length : e.span,
				newH = 0,
				spanCols = [], //numbers of spanned columns
				spanHeights = [], //heights of spanned columns
				style,
				floatCol = e.floatCol;
				
				
				

			//console.log('------ item:' + e.innerHTML)
			//console.log('span:'+span)	
	

			//Find pro?per column to place item
			//console.log(colPriority)
			if (floatCol) {
				floatCol = floatCol > 0 ? Math.min(floatCol, lastItems.length - span) : (lastItems.length + floatCol);
			}
			if (span === 1) {
				//Single-span element
				if (floatCol === null) {
					//no align
					minCol = colPriority.shift();
				} else {
					//predefined column to align
					minCol = floatCol;
					for (c = 0; c < colPriority.length; c++) {
						if (colPriority[c] == minCol) {
							colPriority.splice(c, 1);
							break;
						}
					}
				}
				spanCols.push(minCol);
				minH = lastHeights[minCol];
			} else if (span >= lastItems.length) { //Full-span element
				minCol = 0;
				minH = lastHeights[colPriority[colPriority.length - 1]];
				spanCols = self.baseOrder.slice();
				spanCols.length = lastHeights.length;
				colPriority.length = 0;
			} else { //Some-span element
				if (floatCol !== null) {
					minCol = floatCol;
					minH = Math.max.apply(Math, lastHeights.slice(minCol, minCol + span));
					//console.log(lastHeights.slice(minCol, span))
					//console.log('fCol:' + floatCol + ' minH: ' + minH)
				} else {
					//Make span heights alternatives
					spanHeights.length = 0;
					minH = Infinity;
					minCol = 0;
					for (c = 0; c <= lastItems.length - span; c++) {
						spanHeights[c] = Math.max.apply(Math, lastHeights.slice(c, c + span));
						if (spanHeights[c] < minH) {
							minCol = c;
							minH = spanHeights[c];
						}
					}
				}
				//Replace priorities
				for (c = 0; c < colPriority.length;) {
					if (colPriority[c] >= minCol && colPriority[c] < minCol + span) {
						spanCols.push(colPriority.splice(c, 1)[0]);
					} else c++;
				}
			}

			//console.log(spanCols)
			//console.log(lastHeights)
			//console.log('? spanCols to ?')

			//TODO: correct to work ok with options
			e.top = ~~minH; //stupid save value for translate3d
			if (o.useTranslate3d) {
				var offset = (100 * minCol / span) + '% + ' + ~~((e.ml + e.mr) * minCol / span) + 'px';
				if (o.useCalc) {
					e.style[cssPrefix + 'transform'] = this.prefixedTranslate3d + '( ' + this.prefixedCalc + '(' + offset + '), ' + e.top + 'px, 0)';
				} else {
					//Safari won't set -webkit-calc in element.style
					e.style[cssPrefix + 'transform'] = this.prefixedTranslate3d + '(' + ~~(self.colWidth * minCol) + 'px, ' + e.top + 'px, 0)';
				}
			} else {
				e.style.top = e.top + 'px';
				e.style.left = self.colWidth * minCol + self.pl + 'px';
			}
			//console.log(e.style[cssPrefix + 'transform'])

			//if element was added first time and is out of flow - show it
			//e.style.opacity = 1;

				var images = e.getElementsByTagName("img");
				for(var i=0; i<images.length; i++) {
					var x=images[i].outerHTML;
					var w=e.clientWidth;
					w=w-30;

					var rawx=images[i].getAttribute('data-rawx');
					var rawy=images[i].getAttribute('data-rawy');

					if(rawx>w){ // do not scale up.

						var scaleFactor = 1.0;
						scaleFactor = rawx / rawy;
						rawx = w;
						rawy = w / scaleFactor;
					}

					if(rawx>0 && rawy>0){		
						//console.log('images:'+images[i].outerHTML+' '+rawx+' '+rawy);
						images[i].style.height=rawy+'px';
						images[i].style.width=rawx+'px';
						images[i].style.display='inline=block';
					}

					//getDivId.innerHtml = "<a id='g2Image' href='big/" + images[i].src + "'>" + images[i].src + "</a>";
				}

			e.removeAttribute('hidden');




			newH = self._getBottom(e); //this is the most difficult operation (e.clientHeight)
			for (t = 0; t < spanCols.length; t++) {
				lastItems[spanCols[t]] = e;
				self.lastHeights[spanCols[t]] = newH;
			}

			//console.log(lastItems)
			//console.log('? self.lastHeights to ?')
			//console.log(self.lastHeights)
			//console.log('minCol:'+minCol+' minH:'+minH+' newH:'+newH)
			//console.log(colPriority)
			//console.log('? colPriorities to ?')

			//Update colPriority
			for (c = colPriority.length; c--;) {
				h = self.lastHeights[colPriority[c]];
				if (newH >= h) {
					Array.prototype.splice.apply(colPriority, [c + 1, 0].concat(spanCols));
					break;
				}
			}
			if (colPriority.length < lastHeights.length) {
				Array.prototype.unshift.apply(colPriority, spanCols);
				//self.colPriority = spanCols.concat(colPriority)
			}
		},

		_getBottom: function(e) {
			if (!e) return 0; //this.pt;
			//TODO: memrize height, look for height change to avoid reflow
			return e.top + e.offsetHeight + e.bt + e.bb + e.mb + e.mt;
		},

		_maximizeHeight: function() {
			var top = this.options.useTranslate3d ? this.pt : 0;
			this.el.style.minHeight = this.lastHeights[this.colPriority[this.colPriority.length - 1]] + this.pb + top + 'px';
		}

	});


	$.fn.waterfall = function(arg, arg2) {
		if (typeof arg == 'string') { //Call API method
			return $(this).each(function(i, el) {
					$(el).data('waterfall')[arg](arg2);
				});
		} else {
			if (!this.length) {
				throw new Error("No element passed to waterfall")
				return false;
			};
			var $this = $(this),
				opts = $.extend({}, {"colMinWidth": ~~$this[0].getAttribute("data-col-min-width") ||~~$this[0].getAttribute("data-width")}, arg);
			if (opts.width && !opts.colMinWidth) {
				opts.colMinWidth = opts.width;
			}
			var wf = new Waterfall($this, opts);
			if (!$this.data('waterfall')) $this.data('waterfall', wf);
			return wf;
		}
	};

	//prefix/features detector

	function detectCSSPrefix(property) {
		if (!property) property = 'transform';

		var style = document.defaultView.getComputedStyle(document.body, '');
		if (style[property]) return '';
		if (style['-webkit-' + property]) return '-webkit-';
		if (style['-moz-' + property]) return '-moz-';
		if (style['-o-' + property]) return '-o-';
		if (style['-khtml-' + property]) return '-khtml-';

		return false;
	}

	//autostart
	$(function() {
		var defClass = window.waterfall && window.waterfall.defaultClass || Waterfall.defaultClass;

		$('.' + defClass)
			.each(function(i, e) {
				var $e = $(e),
					opts = window.waterfall || {};
				$e.waterfall(opts);
			});
	});

})(window.jQuery || window.Zepto);


