/**
  * Flowr.js - Simple jQuery plugin to emulate Flickr's justified view
  * For usage information refer to http://github.com/kalyan02/flowr-js
  *
  *
  * @author: Kalyan Chakravarthy (http://KalyanChakravarthy.net)
  * @version: v0.1
  */
(function($){
	//$("#container2").css( 'border', '1px solid #ccc');
	$.fn.flowr = function(options) {

		$this = this;
		var ROW_CLASS_NAME = 'flowr-row';	// Class name for the row of flowy
		var MAX_LAST_ROW_GAP = 25;			// If the width of last row is lesser than max-width, recalculation is needed
		var NO_COPY_FIELDS = [ 'complete', 'data', 'responsive' ];	// these attributes will not be carried forward for append related calls
		var DEFAULTS = {
			'data' : [],
			'padding' : 5,					// whats the padding between flowy items
			'height' : 240,					// Minimum height an image row should take
			'render' : null,				// callback function to get the tag
			'append' : false,				// TODO
			'widthAttr' : 'width',			// a custom data structure can specify which attribute refers to height/width
			'heightAttr' : 'height',
			'maxScale' : 1.5,				// In case there is only 1 elment in last row
			'maxWidth' : this.width()-1,	// 1px is just for offset
			'itemWidth' : null,				// callback function for width
			'itemHeight' : null,			// callback function for height
			'complete' : null,				// complete callback
			'rowClassName' : ROW_CLASS_NAME,
			'rows' : -1, 					// Maximum number of rows to render. -1 for no limit.
			'responsive' : true				// make content responsive
		};
		var settings = $.extend( DEFAULTS, options);

		// If data is being appended, we already have settings
		// If we already have settings, retrieve them
		if( settings.append && $this.data('lastSettings') ) {
			lastSettings = $this.data('lastSettings');

			// Copy over the settings from previous init
			for( attr in DEFAULTS ) {
				if( NO_COPY_FIELDS.indexOf(attr)<0 && settings[attr] == DEFAULTS[attr] ) {
					settings[attr] = lastSettings[attr];
				}
			}

			// Check if we have an incomplete last row
			lastRow = $this.data('lastRow');
			if( lastRow.data.length > 0 && settings.maxWidth-lastRow.width > MAX_LAST_ROW_GAP ) {
				// Prepend the incomplete row to newly loaded data and redraw
				lastRowData = lastSettings.data.slice( lastSettings.data.length - lastRow.data.length - 1 );
				settings.data = lastRowData.concat(settings.data);

				// Remove the incomplete row
				// TODO: Don't reload this stuff later. Reattach to new row.
				$( '.' + settings.rowClassName + ':last', $this ).detach();
			} else {
				// console.log( lastRow.data.length );
				// console.log( lastRow.width );
			}
		}

		// only on the first initial call
		if( !settings.responsive && !settings.append )
			$this.width( $this.width() );

		// Basic sanity checks
		if( !(settings.data instanceof Array) )
			return;

		if( typeof(settings.padding) != 'number' )
			settings.padding = parseInt( settings.padding );

		if( typeof(settings.itemWidth) != 'function' ) {
			settings.itemWidth = function(data) {
				return data[ settings.widthAttr ];
			}
		}

		if( typeof(settings.itemHeight) != 'function' ) {
			settings.itemHeight = function(data) {
				return data[ settings.heightAttr ];
			}
		}

		// A standalone utility to calculate the item widths for a particular row
		// Returns rowWidth: width occupied & data : the items in the new row
		var utils = {
			getNextRow : function( data, settings ) {
				var itemIndex = 0;
				var itemsLength = data.length;
				var lineItems = [];
				var lineWidth = 0;
				var maxWidth = settings.maxWidth;
				var paddingSize = settings.padding;

				// console.log( 'maxItems=' + data.length );

				requiredPadding = function() {
					var extraPads = arguments.length == 1 ? arguments[0] : 0;
					return (lineItems.length - 1 + extraPads) * settings.padding;
				}

				while( lineWidth + requiredPadding() < settings.maxWidth && (itemIndex < itemsLength) ) {
					var itemData = data[ itemIndex ];
					var itemWidth = settings.itemWidth.call( $this, itemData );
					var itemHeight = settings.itemHeight.call( $this, itemData );

					var minHeight = settings.height;
					var minWidth = Math.floor( itemWidth * settings.height / itemHeight );

					var newLineWidth = lineWidth + minWidth + requiredPadding(1);

			                if (minWidth > settings.maxWidth) {
			                    // very short+wide images like panoramas
			                    // show them even if ugly, as wide as possible
			                    minWidth = settings.maxWidth-1;
			                    minHeight = settings.height * minHeight / minWidth;
			                }

					// console.log( 'lineWidth = ' + lineWidth );
					// console.log( 'newLineWidth = ' + newLineWidth );
					if( newLineWidth < settings.maxWidth ) {
						lineItems.push({
							'height' : minHeight,
							'width' : minWidth,
							'itemData' : itemData
						});

						lineWidth += minWidth;
						itemIndex ++;
					} else {
						// We'd have exceeded width. So break off to scale.
						// console.log( 'breaking off = ' + itemIndex );
						// console.log( 'leave off size = ' + lineItems.length );
						break;
					}
				} //while

				// Scale the size to max width
				testWidth=0;
				if( lineWidth < settings.maxWidth ) {
					var fullScaleWidth = settings.maxWidth - requiredPadding() - 10;
					var currScaleWidth = lineWidth;
					var scaleFactor = fullScaleWidth / currScaleWidth;
					if( scaleFactor > settings.maxScale )
						scaleFactor = 1;

					var newHeight = Math.round( settings.height * scaleFactor );
					for( i=0; i<lineItems.length; i++ ) {
						var lineItem = lineItems[ i ];
						lineItem.width = Math.floor(lineItem.width * scaleFactor);
						lineItem.height = newHeight;

						testWidth += lineItem.width;
					}
				}

				return {
					data : lineItems,
					width : testWidth + requiredPadding()
				};
			},	//getNextRow
			reorderContent : function(){
				/*
				 TODO: optimize for faster resizing by reusing dom objects instead of killing the dom
				 */
				var _initialWidth = $this.data('width');
				var _newWidth = $this.width();
				var _change = _initialWidth - _newWidth;

				if(_initialWidth!=_newWidth) {
					$this.html('');
					var _settings = $this.data( 'lastSettings' );
					_settings.data = $this.data( 'data' );
					_settings.maxWidth = $this.width() - 1;
					$this.flowr( _settings );
				}
			}
		} //utils

		// If the resposive var is set to true then listen for resize method
		// and prevent resizing from happening twice if responsive is set again during append phase!
		if( settings.responsive && !$this.data('__responsive') ) {
			$(window).resize(function(){
				initialWidth = $this.data('width');
				newWidth = $this.width();

				//initiate resize
				if( initialWidth != newWidth ) {
					var task_id = $this.data('task_id');
					if( task_id ) {
						task_id = clearTimeout( task_id );
						task_id = null;
					}
					task_id = setTimeout( utils.reorderContent, 80 );
					$this.data('task_id', task_id );
				}
			});
			$this.data('__responsive',true);
		}


		return this.each(function(){

			// Get a copy of original data. 1 level deep copy is sufficient.
			var data = settings.data.slice(0);
			var rowData = null;
			var currentRow = 0;
			var currentItem = 0;

			// Store all the data
			var allData = $this.data( 'data' ) || [];
			for(i=0;i<data.length;i++) {
				allData.push( data[i] );
			}
			$this.data( 'data', allData );

			// While we have a new row
			while( ( rowData = utils.getNextRow(data,settings) ) != null && rowData.data.length > 0 ) {
				if( settings.rows > 0 && currentRow >= settings.rows )
					break;
				// remove the number of elements in the new row from the top of data stack
				data.splice( 0, rowData.data.length );

				// Create a new row div, add class, append the htmls and insert the flowy items
				var $row = $('<div>').addClass(settings.rowClassName);
				for( i=0; i<rowData.data.length; i++ ) {
					var displayData = rowData.data[i];
					// Get the HTML object from custom render function passed as argument
					var displayObject = settings.render.call( $this, displayData );
					displayObject = $(displayObject);
					// Set some basic stuff
					displayObject
						.css( 'width', displayData.width )
						.css( 'height', displayData.height )
						.css( 'margin-left', i==0 ? '0' : settings.padding + "px" );	//TODO:Refactor
					$row.append( displayObject );

					currentItem++;
				}
				$this.append( $row );
				// console.log ( "I> rowData.data.length="+rowData.data.length +"   rowData.width="+rowData.width );

				currentRow++;
				$this.data('lastRow', rowData );
			}
			// store the current state of settings and the items in last row
			// we'll need this info when we append more items
			$this.data('lastSettings', settings );

			// onComplete callback
			// pass back info about list of rows and items rendered
			if( typeof (settings.complete) == 'function' ) {
				var completeData = {
					renderedRows : currentRow,
					renderedItems : currentItem
				}
				settings.complete.call( $this, completeData );
			}
		});
	};

})(jQuery);
