/**
* Grid()
* by Bachir Soussi-Chiadmi www.g-u-i.net
* version 2.0
*/

(function($) {	
  function Grid(ctx, settings){
    
  	var _defaults = {
  				children:'', // tell which dom elmt to target, by default $ctx.children();
  				cell_w: 20, cell_h: 20,
  				cell_padding:{t:5,r:5,b:5,l:5}, // with or without padding cell width and height are the same
  				padding:{t:5,r:5,b:5,l:5}, // padding (in cell) inside the grid where not placed element
  				gouttiere: 5, // space between cells h and v
  				lines: 20, columns: 20,// how much line and columns grid has
  				grille: false, // see the grid with cell's free space infos
  				groove: 0, // add groove attitude (randomize placement)
  				positioning_mode:'random', // positioning_mode play with with the position on items
  				max_distance:0, // distance max between prev item and current item. O = no limit
  				min_distance:0, // distance min between prev item and current item. O = no limit
  			},
  		$ctx = $(ctx),
  		_cells = new Array(),
  		_positions = new Array(),
  		_grid = this;

  	this.ctx = $ctx;
  	this.settings = $.extend({}, _defaults, settings);

  	this.settings.init_maxdist = this.settings.max_distance;
  	this.settings.init_mindist = this.settings.min_distance;
	
	
  	// $.log('this.settings', this.settings);

  	// set dom grille visualsation context if enabled
  	if (this.settings.grille) {
  		$ctx.append('<div class="grille"><div>');
  		$('.grille', $ctx).css({
  		   'width': '100%',
  		   'height': '100%',
  		   'position': 'relative'
  		});
  	}

  	// create all empty cells
  	for (var l = 0; l < this.settings.lines; l++) {
  	  _cells[l] = new Array();
  	  for (var c = 0; c < this.settings.columns; c++) {
  	    _cells[l][c] = new Cell(this, c, l);
  	  };
  	};
	

  	function _setPrevCell(){
  		// _grid.prev_cell = false;
  		switch(_grid.settings.positioning_mode){
  			case 'lefttop': // add items from top left to bootm rif-ght
  					_grid.prev_cell = _cells[0][0];
  				break;
  			case 'center': // add items from center of grid to both side randomly
  					_grid.prev_cell = _cells[Math.floor(_grid.settings.lines*.5)][Math.floor(_grid.settings.columns*.5)];
  					break;
  			case 'borders': // add items from center of grid to both side randomly
  					var side = Math.floor(Math.random()*4);
            // $.log('side = '+side);
  					switch(side){
  						case 0 :
  							_grid.prev_cell = _cells[0][Math.floor(_grid.settings.columns*Math.random())];
  							break;
  						case 1 :
  							_grid.prev_cell = _cells[_grid.settings.lines-1][Math.floor(_grid.settings.columns*Math.random())];
  							break;
  						case 2 :
  							_grid.prev_cell = _cells[Math.floor(_grid.settings.lines*Math.random())][0];
  							break;
  						case 3 :
  							_grid.prev_cell = _cells[Math.floor(_grid.settings.lines*Math.random())][_grid.settings.columns-1];
  							break;
  					}
  					break;
  			case 'random': // add items randomly
  			default:
  					_grid.prev_cell = _cells[Math.floor(_grid.settings.lines*Math.random())][Math.floor(_grid.settings.columns*Math.random())];
  				break;
  		}
		
  	}
  	_setPrevCell();


  	/**
  	* placeTargets()
  	*/
  	function _placeTargets(){
		
  		_grid.settings.max_distance = _grid.settings.init_maxdist;
  		_grid.settings.min_distance = _grid.settings.init_mindist;
		
		
  		// parse targets
  		var $targets = _grid.settings.children != '' ? $(_grid.settings.children+':not(.grid-placed)', $ctx) : $ctx.children(':not(.grid-placed)');

  		$targets.each(function() {

  		  var $this = $(this),
  					// get the width and height ( ! on cell unit ! how much cells it takes ? ) of item to be positioned
  					this_cells_w = Math.ceil($this.width() / (_grid.settings.cell_w + _grid.settings.gouttiere)),
  					this_cells_h = Math.ceil($this.height() / (_grid.settings.cell_h + _grid.settings.gouttiere)),
  					// get free cells function of width and height of item
  					free_cells = new Array();

  			while(free_cells.length == 0){
  				free_cells = getFreeCells(this_cells_w, this_cells_h);
  				if(free_cells.length == 0)
  					_grid.settings.max_distance = _grid.settings.max_distance*1.2;
  			}
					
  			if(free_cells.length > 0){

  				// positioning mode, to change this mode uncomment/comment lines after description
  				switch(_grid.settings.positioning_mode){
  					case 'lefttop': // add items from top left to bootm rif-ght
  							var cell_num = 0,
  									cell = free_cells[cell_num];
  						break;
  					default: // add items from center of grid to both side randomly
  							// var cell_num = Math.floor((free_cells.length-1)*0.5 + (-2+Math.random()*4));
  							// cell_num = cell_num > free_cells.length-1 ? free_cells.length-1 : ( cell_num < 0 ? 0 : cell_num );
  							// var cell = free_cells[cell_num];
  							var cell_num = Math.round(Math.random() * (free_cells.length - 1)),
  									cell = free_cells[cell_num];
  						break;
  				}

  				_grid.prev_cell = cell;

  				var line_limit_loop = cell.row() + (this_cells_h),
  						column_limit_loop = cell.column() + (this_cells_w);

  		    for (var l = cell.row(); l < line_limit_loop; l++) {
  		        for (var c = cell.column(); c < column_limit_loop; c++) {
  		            var temp_cell = _cells[l][c];
  		            temp_cell.setFull();
  		        };
  		    };

  				// get tp and left coordinates
  				// should be directly recorded in the cell object
  		    var top = cell.row() * (_grid.settings.cell_h + _grid.settings.gouttiere),
  						left = cell.column() * (_grid.settings.cell_w + _grid.settings.gouttiere),
  						groove = _grid.settings.groove;

  				// set groove ;)
  				top = !groove ? top: top + Math.round(Math.random() * groove - groove / 2);
  				left = !groove ? left: left + Math.round(Math.random() * groove - groove / 2);

  				// apply ccs position
  				top += _grid.settings.cell_padding.t;
  				left += _grid.settings.cell_padding.l;
				
  				var event = jQuery.Event("grid_positioned");
  				event.top = top;
  				event.left = left;
				
  		    $this.css({
  	        'top': top,
  	        'left': left
  		    }).addClass('grid-positioned').trigger(event);
  			}else{
          // $.log('GRID no free cells left');
  				// if no free cells exist
  				$this.css({
  					'border': '2px solid red',
  					'opacity': 0.2
  				}).trigger('grid_removed');//.remove();
  		  }
  			$this.addClass('grid-placed').trigger('grid_placed');
  		}); // end children each
	
  	}
	
	
  	/**
  	* getFreeCells()
  	*
  	* @ width in cells
  	* @ height in cells
  	*
  	* return array of cell objects
  	*
  	*/
  	function getFreeCells(this_cells_w, this_cells_h){
		
  		var free_cells = new Array(),
  				padding = _grid.settings.padding,
  				// max_dist = _grid.settings.max_distance,
  				// min_dist = _grid.settings.min_distance,
  				max_dist_l = _grid.settings.lines * _grid.settings.max_distance,
  				min_dist_l = _grid.settings.lines * _grid.settings.min_distance,
  				max_dist_c = _grid.settings.columns * _grid.settings.max_distance,
  				min_dist_c = _grid.settings.columns * _grid.settings.min_distance,
  				prev_cell = _grid.prev_cell;
		
  		// $.log()
		
  		for (var l = 0; l < _grid.settings.lines; l++) {
  			for (var c = 0; c < _grid.settings.columns; c++) {
  				var cell = _cells[l][c];
				
  				/*
  					TODO about the max_dist keep in mind that all element will not always be 1 cell elemnts !!
  				*/
				
  				// here is a big stuff of conditionning
  				// we check a lot of things
  				// 1 - basicly if the cell is free
  				// 2 - if the cell is under the padding limits
  				// 3 - if she has enought space arround her
  				// 4 - if we got a prev cell
  				//	4.1 - if we got a max_dist != 0
  				//		4.1.1 - if the cell is under the max limits
  				//	4.2 - if we got a min_dist != 0
  				//		4.2.1 - if the cell is behind the min limits
				
  				// if (cell.isFree() // if cell is free
  				// 		&& (cell.row() > padding.t && cell.row() < _grid.settings.lines - padding.b
  				// 				&& cell.column() > padding.l && cell.column() < _grid.settings.columns - padding.r
  				// 			)
  				// 		&& (
  				// 				(cell.getAround().right + 1) >= this_cells_w // if enought free space on the right
  				// 				&& (cell.getAround().bottom + 1) >= this_cells_h // if enought free space on the bottom
  				// 			)
  				// 		&& ( !_grid.prev_cell // else if _grid.prev_cell undefined it's true
  				// 			|| ( // check max_dist AND min_dist
  				// 					(!max_dist // if max_distance is 0 return (stop condition) true
  				// 					|| ( cell.row() < prev_cell.row()+max_dist && cell.row() > prev_cell.row()-max_dist
  				// 							&& cell.column() < prev_cell.column()+max_dist && cell.column() > prev_cell.column()-max_dist )// if we got max_distance and prev_cell we check the distance
  				// 					) 
  				// 				&& 
  				// 					(!min_dist // if min_distance is 0 return (stop condition) true
  				// 					|| ( (cell.row() > prev_cell.row()+min_dist || cell.row() < prev_cell.row()-min_dist)
  				// 							|| (cell.column() > prev_cell.column()+min_dist || cell.column() < prev_cell.column()-min_dist) )// if we got min_distance and prev_cell we check the distance
  				// 					) 
  				// 				)
  				// 			)
  				// 	) { // true of first big stuff if

  				if (cell.isFree() // if cell is free
  						&& (cell.row() > padding.t && cell.row() < _grid.settings.lines - padding.b
  								&& cell.column() > padding.l && cell.column() < _grid.settings.columns - padding.r
  							)
  						&& (
  								(cell.getAround().right + 1) >= this_cells_w // if enought free space on the right
  								&& (cell.getAround().bottom + 1) >= this_cells_h // if enought free space on the bottom
  							)
  						&& ( !_grid.prev_cell // else if _grid.prev_cell undefined it's true
  							|| ( // check max_dist AND min_dist
  									((!max_dist_l && !max_dist_c) // if max_dist_l AND max_dist_c are 0 return (stop condition) true
  									|| ( (!max_dist_l || (cell.row() < prev_cell.row()+max_dist_l && cell.row() > prev_cell.row()-max_dist_l)) // if max_dist_l is 0, return true, so abort "between test" on lines
  											&& (!max_dist_c || (cell.column() < prev_cell.column()+max_dist_c && cell.column() > prev_cell.column()-max_dist_c)) ) // if max_dist_c is 0, return true, so abort "between test" on columns
  									) // if we got max_distance and prev_cell we check the distance
  								&& 
  									((!min_dist_l && !min_dist_c) // if min_dist_l AND min_dist_c are 0 return (stop condition) true
  									|| ( (!min_dist_l || (cell.row() > prev_cell.row()+min_dist_c || cell.row() < prev_cell.row()-min_dist_c))
  											|| (!min_dist_c || (cell.column() > prev_cell.column()+min_dist_c || cell.column() < prev_cell.column()-min_dist_c)) )// if we got min_distance and prev_cell we check the distance
  									) 
  								)
  							)
  					) { // true of first big stuff if
  					var good = true;
  					for (var i = 1; i < this_cells_w; i++) { // for each free cells needed on right
  						var temp_cell = _cells[l][c + i];
  						if ((temp_cell.getAround().bottom + 1) < this_cells_h) { // if enought free space on bottom
  							good = false;
  							break;
  						}
  					};
  					if (good) free_cells.push(cell);
  				}
  			};
  		};
	
  		return free_cells;
  	}

  	/**
  	* Cell()
  	*/
  	function Cell(g, c, r) {
  	  var _grid = g,
  			_column = c,
  	    _row = r,
  	    _free = true,
  	    _free_cell_right = _grid.settings.columns - 1 - c,
  	    _free_cell_bottom = _grid.settings.lines - 1 - l;

  	  if (_grid.settings.grille) {
  	      var _table = '<table>';
  	      _table += '<tr><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td></tr>';
  	      // _table += '<tr><td>&nbsp;</td><td class="state">'+_free+'</td><td class="fr">'+_free_cell_right+'</td></tr>';
  	      _table += '<tr><td>&nbsp;</td><td>&nbsp;</td><td class="fr">' + _free_cell_right + '</td></tr>';
  	      _table += '<tr><td>&nbsp;</td><td class="fb">' + _free_cell_bottom + '</td><td>&nbsp;</td></tr>';
  	      _table += '</table>';
  	      _grid.ctx.find('.grille').append('<div class="cell-' + l + '-' + c + '">' + _table + '<div>');
  	      var _dom = $('.cell-' + l + '-' + c, _grid.ctx.find('.grille'));
  	      _dom.css({
  	          'position': 'absolute',
  	          'top': _row * (_grid.settings.cell_h + _grid.settings.gouttiere) + _grid.settings.cell_padding.t,
  	          'left': _column * (_grid.settings.cell_w + _grid.settings.gouttiere) + _grid.settings.cell_padding.l,
  	          'width': _grid.settings.cell_w,
  	          'height': _grid.settings.cell_h,
  	          'backgroundColor': '#00FF00',
  	          'opacity': 0.6
  	      });
  	      // $('td', _dom).css({
  	      //     'fontSize': '8px'
  	      // });
  	  }

  	  this.isFree = function() { return _free; };

  	  this.getPos = function() { return { c: _column, l: _row }; };
  		this.column = function() { return _column; };
  		this.row = function() { return _row; };

  	  this.getAround = function() { return { right: _free_cell_right, bottom: _free_cell_bottom }; };

  	  this.setFull = function() {

  	      _free = false;
  	      _free_cell_bottom = -1;
  	      _free_cell_right = -1;

  	      if (_grid.settings.grille) {
  	          _dom.css({
  	              'backgroundColor': '#0000FF'
  	          });
  	          $('.fb', _dom).html(_free_cell_bottom);
  	          $('.fr', _dom).html(_free_cell_right);
  	      }

  	      _propageFreeCells();
  	  };

  	  this.setFreeCellsRight = function(right) {
  	      if (_free) {
  	          // $.log('_setFreeCellRigth | _free_cell_right = ' + _free_cell_right + ' | new right = ' + right);
  	          _free_cell_right = right;
  	          if (_grid.settings.grille) $('.fr', _dom).html(_free_cell_right);
  	          _propageFreeCellsLeft();
  	      }
  	  };

  	  this.setFreeCellsBottom = function(bottom) {
  	      if (_free) {
  	          // $.log('_setFreeCellsBottom | _free_cell_bottom = ' + _free_cell_bottom + ' | new right = ' + bottom);
  	          _free_cell_bottom = bottom;
  	          if (_grid.settings.grille) $('.fb', _dom).html(_free_cell_bottom);
  	          _propageFreeCellsTop();
  	      }
  	  };

  	  function _propageFreeCells() {
  	      // $.log('_propageFreeCells');
  	      _propageFreeCellsLeft();
  	      _propageFreeCellsTop();
  	  };

  	  function _propageFreeCellsLeft() {
  	      // $.log('_propageFreeCellsLeft | _column = '+_column);
  	      if (_column > 0) {
  	          var left_cel = _cells[_row][_column - 1];
  	          left_cel.setFreeCellsRight(_free_cell_right + 1);
  	      }
  	  };

  	  function _propageFreeCellsTop() {
  	      // $.log('_propageFreeCellsTop | _row = '+_row);
  	      if (_row > 0) {
  	          var top_cel = _cells[_row - 1][_column];
  	          top_cel.setFreeCellsBottom(_free_cell_bottom + 1);
  	      }
  	  };

  	  if (typeof Cell.initialized == "undefined") {
  	      Cell.prototype.infos = function() {
  	          //console.log('cell | column = ' + this.column() + ', line = ' + this.row() + ', free = ' + this.isFree() + ', free cell right = ' + this.getAround().right + ', free cell bottom = ' + this.getAround().bottom);
  	      };

  	      Cell.initialized = true;
  	  }
  	}; // cell()
	
  	if (typeof Node.initialized == "undefined") {

      Grid.prototype.refresh = function() {
  			_placeTargets();
  		};

      Grid.prototype.resetPrevCell = function() {
  			_setPrevCell();
  			// this.prev_cell = false;
  		};


  		Node.initialized = true;

    }
	
	
  };

	$.fn.grid = function(settings) {
		return this.each(function(index) {
			$(this).data('grid', new Grid(this, settings));
		});
	}
})(jQuery);