/**
 * The MIT License
 * 
 * Copyright (c) 2009 Jason Wyatt Feinstein
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
	
var SlickWrapFloat = new Class({
	Implements: Options,
	
	options: {
		'bgColor': null,
		'bloomPadding': false,
		'resolution': 20,
		'cutoff': 5,
		'fallback': null,
		'margin': 0
	},
	
	floatDirection: null,
	image: null,
	
	initialize: function(elem, options) {
		this.setOptions(options);
		
		this.parent = $(parent);
		this.image = $(elem);
		
		if(this.image == null) {
			return;
		}
		
		this.floatDirection = this.image.getStyle('float');
		
		this.imageWidth = this.image.getWidth();
		this.imageHeight = this.image.getHeight();
		
		this.exec();
		
		// Set background
		this.parent.setStyles({
			'background-image': 'url('+ this.canvas.toDataURL() +')',
			'background-position': 'top '+ this.floatDirection,
			'background-repeat': 'no-repeat'
		});
		
		/* 
		 * Adjust the height of the parent element just in case it's too 
		 * short. If we didn't do this the new background image could 
		 * get cut off
		 */
		var parentHeight = this.parent.getHeight();
		var imageHeight = this.image.getHeight() + this.padding.top;
		
		if(parentHeight < imageHeight) {
			this.parent.setStyle('height', imageHeight);
		}
		
		// Hide the image itself
		this.image.setStyle('display', 'none');
	},
	
	exec: function() {
		this.canvas = $(document.createElement('canvas'));
		
		//For ie browsers...
		if(window.G_vmlCanvasManager) {
			G_vmlCanvasManager.initElement(this.canvas);
		}
		
		// If the browser doesn't support canvas stop!
		if(this.canvas == null) {
			this.tryFallback();
			return;
		}
		
		// Check float and img
		if(this.floatDirection != 'left' && this.floatDirection != 'right') {
			return;
		}
		if(this.image.get('tag') != 'img') {
			return;
		}
		
		// Find the padding all the way around
		this.padding = {
			top: this.image.getStyle('padding-top').toInt(),
			right: this.image.getStyle('padding-right').toInt(),
			bottom: this.image.getStyle('padding-bottom').toInt(),
			left: this.image.getStyle('padding-left').toInt()
		};
		if(typeOf(this.padding.top) != 'number') this.padding.top = 0;
		if(typeOf(this.padding.right) != 'number') this.padding.right = 0;
		if(typeOf(this.padding.bottom) != 'number') this.padding.bottom = 0;
		if(typeOf(this.padding.left) != 'number') this.padding.left = 0;
		
		// Create a canvas and draw the image onto the canvas
		this.canvas.width = this.imageWidth + this.padding.left + this.padding.right;
		this.canvas.height = this.imageHeight + this.padding.top + this.padding.bottom;
		
		var width = this.canvas.width;
		var height = this.canvas.height;
		var context = this.canvas.getContext('2d');
		
		//this.canvas.inject(document.body, 'top');
		
		// Draw the image once in the top-left, so we can grab the background color
		context.drawImage(this.image, 0, 0);
		
		var imageData = context.getImageData(0, 0, width, height);
		var data = Array.prototype.slice.call(imageData.data);
		if(this.options.bgColor == null) {
			this.options.bgColor = {
				r: data[0],
				g: data[1],
				b: data[2],
				a: data[3]
			};
		}
		
		// Fill the whole image with the background color, then try again.
		context.clearRect(0, 0, width, height);
		context.fillStyle = 'rgba('+ this.options.bgColor.r +','+ this.options.bgColor.g +','+ this.options.bgColor.b +','+ this.options.bgColor.a +')';
		context.fillRect(0, 0, width, height);
		context.drawImage(this.image, this.padding.left, this.padding.top);
		
		// Set the parent's background-image to this image
		var divWidths = this.calculateDivWidths();
		var divHeight = this.options.resolution;
		var divWidthsLength = divWidths.length;
		
		for(var i = divWidthsLength; i > 0; i--) {
			new Element('div', { 'style': 'width: '+ (divWidths[i] + this.options.margin) +'px; float: '+ this.floatDirection +'; height: '+ divHeight +'px; clear: '+ this.floatDirection })
					.inject(this.parent, 'top');
		}
	},
	
	/**
	 * Calculates the widths of the divs for slickwrapping.
	 * @scope The node for the image to slickwrap.
	 */
	calculateDivWidths: function() {
		var lineHeight = this.options.resolution;
		var width = this.canvas.width;
		var height = this.canvas.height;
		
		// Get drawing context for canvas...
		var context = this.canvas.getContext('2d');
		
		/*
		 * Algorithm:
		 *
		 * 1. Get the image data
		 * 2. Threshold the image from the background color and bloom it by the 
		 *	  right padding size...
		 * 3. Iterate in lineHeight-sized intervals to find the width of the 
		 *	  image from one edge (depending on floatDirection)
		 *	  -> Put those widths into an array.
		 * 4. Return the array.
		 */
		
		//Step 1
		var image = context.getImageData(0, 0, width, height);
		var data = Array.prototype.slice.call(image.data);
		if(this.options.bgColor == null) {
			this.options.bgColor = {
				r: data[0],
				g: data[1],
				b: data[2],
				a: data[3]
			};
		}
		
		// Step 2 - Threshold the image
		var dataLength = data.length;
		for(var i = 0; i < dataLength; i = i + 4) {
			var distance = {
				r: Math.abs(this.options.bgColor.r - data[i]),
				g: Math.abs(this.options.bgColor.g - data[i+1]),
				b: Math.abs(this.options.bgColor.b - data[i+2]),
				a: Math.abs(this.options.bgColor.a - data[i+3])
			};
			if(distance.r < this.options.cutoff && distance.g < this.options.cutoff && distance.b < this.options.cutoff && distance.a < this.options.cutoff) {
				data[i+3] = 0;
			} else {
				data[i+3] = 255;
			}
		}
		
		// If bloomPadding is set to true, bloom the image.
		if(this.options.bloomPadding) {
			var bloomSize = (this.floatDirection == 'left' ? this.padding.right : this.padding.left);
			for(var i = 0; i < bloomSize; i++) {
				var bloomedData = [];
				for(var y = 0; y < height; y++) {
					for(var x = 0; x < width; x++) {
						var dataLocation = (x + y * width) * 4 + 3;
						if(y - 1 >= 0) {
							var upLocation = (x + (y - 1) * width) * 4 + 3;
							bloomedData[dataLocation] = data[upLocation] > 0 ? 255 : bloomedData[dataLocation];
						}
						if(y + 1 < height) {
							var downLocation = (x + (y + 1) * width) * 4 + 3;
							bloomedData[dataLocation] = data[downLocation] > 0 ? 255 : bloomedData[dataLocation];
						}
						if(x - 1 >= 0) {
							var leftLocation = (x - 1 + y * width) * 4 + 3;
							bloomedData[dataLocation] = data[leftLocation] > 0 ? 255 : bloomedData[dataLocation];
						}
						if(x + 1 < width) {
							var rightLocation = (x + 1 + y * width) * 4 + 3;
							bloomedData[dataLocation] = data[rightLocation] > 0 ? 255 : bloomedData[dataLocation];
						}
					}
				}
				data = bloomedData.slice();
			}
		}
		
		// Step 3
		var paddingSize = this.options.bloomPadding ? 0 : (this.floatDirection == 'left' ? this.padding.right : this.padding.left);
			
		var result = [];
		var rows = height / lineHeight;
		rows = height % lineHeight == 0 ? rows : rows + 1;
		for(var row = 0; row < rows; row++) {
			var maxWidth = 0;
			
			// Calculate the start and end positions for the loops.
			var startX = this.floatDirection == 'right' ? 0 : width - 1;
			var endX = this.floatDirection == 'right' ? width - 1 : 0;
			var startY = row * lineHeight;
			var endY = startY + lineHeight - 1;
			endY = endY >= height ? height - 1 : endY;
			
			if(this.floatDirection == 'right') {
				for(var y = startY; y <= endY; y++) {
					var offset = y * (width * 4);
					var foundAt = width - x;
					for(var x = startX; x <= endX; x++) {
						var location = x * 4 + offset;
						if(data[location + 3] == 255) {
							foundAt = width - x;
							break;
						}
					}
					if(foundAt > maxWidth) {
						maxWidth = foundAt;
					}
				}
			} else {
				for(var y = startY; y <= endY; y++) {
					var offset = y * (width * 4);
					var foundAt = 0;
					for(var x = startX; x >= endX; x--) {
						var location = x * 4 + offset;
						if(data[location + 3] == 255) {
							foundAt = x;
							break;
						}
					}
					if(foundAt > maxWidth) {
						maxWidth = foundAt;
					}
				}
			}
			
			result.push(maxWidth + (maxWidth != 0 ? paddingSize : 0));
		}
		
		//Step 4 :)
		return result;
	},
	
	tryFallback: function() {
		if(typeOf(this.options.fallback) == 'function') {
			this.options.fallback(this.image);
		}
	}
});

var SlickWrapBackground = new Class({
	Extends: SlickWrapFloat,
	
	initialize: function(url, parent, direction, options) {
		this.setOptions(options);
		
		this.floatDirection = direction;
		this.image = Asset.image(url, { onLoad: this.onImageLoad.bind(this) });
		this.parent = $(parent);
	},
	
	onImageLoad: function() {
		this.imageWidth = parseInt(this.image.get('width'));
		this.imageHeight = parseInt(this.image.get('height'));
		
		//this.exec();
		
		try {
			this.exec();
		} catch(e) {
			this.tryFallback();
		}
	}
});
