/*
* BlurFilter
* Visit http://createjs.com/ for documentation, updates and examples.
*
* Copyright (c) 2010 gskinner.com, inc.
*
* 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.
*/

/**
 * @module EaselJS
 */

// namespace:
this.createjs = this.createjs||{};

(function() {
	"use strict";

/**
 * Applies a box blur to DisplayObjects. Note that this filter is fairly CPU intensive, particularly if the quality is
 * set higher than 1.
 *
 * <h4>Example</h4>
 * This example creates a red circle, and then applies a 5 pixel blur to it. It uses the {{#crossLink "Filter/getBounds"}}{{/crossLink}}
 * method to account for the spread that the blur causes.
 *
 *      var shape = new createjs.Shape().set({x:100,y:100});
 *      shape.graphics.beginFill("#ff0000").drawCircle(0,0,50);
 *
 *      var blurFilter = new createjs.BlurFilter(5, 5, 1);
 *      shape.filters = [blurFilter];
 *      var bounds = blurFilter.getBounds();
 *
 *      shape.cache(-50+bounds.x, -50+bounds.y, 100+bounds.width, 100+bounds.height);
 *
 * See {{#crossLink "Filter"}}{{/crossLink}} for an more information on applying filters.
 * @class BlurFilter
 * @extends Filter
 * @constructor
 * @param {Number} [blurX=0] The horizontal blur radius in pixels.
 * @param {Number} [blurY=0] The vertical blur radius in pixels.
 * @param {Number} [quality=1] The number of blur iterations.
 **/
var BlurFilter = function( blurX, blurY, quality ) {
  this.initialize( blurX, blurY, quality );
};
var p = BlurFilter.prototype = new createjs.Filter();

// constructor:
	/** @ignore */
	p.initialize = function( blurX, blurY, quality ) {
		if ( isNaN(blurX) || blurX < 0 ) blurX = 0;
		this.blurX = blurX | 0;
		if ( isNaN(blurY) || blurY < 0 ) blurY = 0;
		this.blurY = blurY | 0;
		if ( isNaN(quality) || quality < 1  ) quality = 1;
		this.quality = quality | 0;
	};

// public properties:

	/**
	 * Horizontal blur radius in pixels
	 * @property blurX
	 * @default 0
	 * @type Number
	 **/
	p.blurX = 0;

	/**
	 * Vertical blur radius in pixels
	 * @property blurY
	 * @default 0
	 * @type Number
	 **/
	p.blurY = 0;

	/**
	 * Number of blur iterations. For example, a value of 1 will produce a rough blur. A value of 2 will produce a
	 * smoother blur, but take twice as long to run.
	 * @property quality
	 * @default 1
	 * @type Number
	 **/
	p.quality = 1;
	
	//TODO: There might be a better better way to place these two lookup tables:
	p.mul_table = [ 1,171,205,293,57,373,79,137,241,27,391,357,41,19,283,265,497,469,443,421,25,191,365,349,335,161,155,149,9,278,269,261,505,245,475,231,449,437,213,415,405,395,193,377,369,361,353,345,169,331,325,319,313,307,301,37,145,285,281,69,271,267,263,259,509,501,493,243,479,118,465,459,113,446,55,435,429,423,209,413,51,403,199,393,97,3,379,375,371,367,363,359,355,351,347,43,85,337,333,165,327,323,5,317,157,311,77,305,303,75,297,294,73,289,287,71,141,279,277,275,68,135,67,133,33,262,260,129,511,507,503,499,495,491,61,121,481,477,237,235,467,232,115,457,227,451,7,445,221,439,218,433,215,427,425,211,419,417,207,411,409,203,202,401,399,396,197,49,389,387,385,383,95,189,47,187,93,185,23,183,91,181,45,179,89,177,11,175,87,173,345,343,341,339,337,21,167,83,331,329,327,163,81,323,321,319,159,79,315,313,39,155,309,307,153,305,303,151,75,299,149,37,295,147,73,291,145,289,287,143,285,71,141,281,35,279,139,69,275,137,273,17,271,135,269,267,133,265,33,263,131,261,130,259,129,257,1];
        
   
	p.shg_table = [0,9,10,11,9,12,10,11,12,9,13,13,10,9,13,13,14,14,14,14,10,13,14,14,14,13,13,13,9,14,14,14,15,14,15,14,15,15,14,15,15,15,14,15,15,15,15,15,14,15,15,15,15,15,15,12,14,15,15,13,15,15,15,15,16,16,16,15,16,14,16,16,14,16,13,16,16,16,15,16,13,16,15,16,14,9,16,16,16,16,16,16,16,16,16,13,14,16,16,15,16,16,10,16,15,16,14,16,16,14,16,16,14,16,16,14,15,16,16,16,14,15,14,15,13,16,16,15,17,17,17,17,17,17,14,15,17,17,16,16,17,16,15,17,16,17,11,17,16,17,16,17,16,17,17,16,17,17,16,17,17,16,16,17,17,17,16,14,17,17,17,17,15,16,14,16,15,16,13,16,15,16,14,16,15,16,12,16,15,16,17,17,17,17,17,13,16,15,17,17,17,16,15,17,17,17,16,15,17,17,14,16,17,17,16,17,17,16,15,17,16,14,17,16,15,17,16,17,17,16,17,15,16,17,14,17,16,15,17,16,17,13,17,16,17,17,16,17,14,17,16,17,16,17,16,17,9];

// public methods:
	/** docced in super class **/
	p.getBounds = function() {
		var q = Math.pow(this.quality, 0.6)*0.5;
		return new createjs.Rectangle(-this.blurX*q,-this.blurY*q,2*this.blurX*q,2*this.blurY*q);
	};

	p.applyFilter = function(ctx, x, y, width, height, targetCtx, targetX, targetY) {
		targetCtx = targetCtx || ctx;
		if (targetX == null) { targetX = x; }
		if (targetY == null) { targetY = y; }
		try {
			var imageData = ctx.getImageData(x, y, width, height);
		} catch(e) {
			//if (!this.suppressCrossDomainErrors) throw new Error("unable to access local image data: " + e);
			return false;
		}

		var radiusX = this.blurX/2;
		if ( isNaN(radiusX) || radiusX < 0 ) return false;
		radiusX |= 0;

		var radiusY = this.blurY/2;
		if ( isNaN(radiusY) || radiusY < 0 ) return false;
		radiusY |= 0;

		if ( radiusX == 0 && radiusY == 0 ) return false;

		var iterations = this.quality;
		if ( isNaN(iterations) || iterations < 1  ) iterations = 1;
		iterations |= 0;
		if ( iterations > 3 ) iterations = 3;
		if ( iterations < 1 ) iterations = 1;

		var pixels = imageData.data;

		var x, y, i, p, yp, yi, yw, r_sum, g_sum, b_sum, a_sum, 
		r_out_sum, g_out_sum, b_out_sum, a_out_sum,
		r_in_sum, g_in_sum, b_in_sum, a_in_sum, 
		pr, pg, pb, pa, rbs;

		var divx = radiusX + radiusX + 1;
		var divy = radiusY + radiusY + 1;
		var w4 = width << 2;
		var widthMinus1  = width - 1;
		var heightMinus1 = height - 1;
		var rxp1  = radiusX + 1;
		var ryp1  = radiusY + 1;
		var stackStartX = {r:0,b:0,g:0,a:0,next:null};
		var stackx = stackStartX;
		for ( i = 1; i < divx; i++ )
		{
			stackx = stackx.next = {r:0,b:0,g:0,a:0,next:null};
			if ( i == rxp1 ) var stackEndX = stackx;
		}
		stackx.next = stackStartX;
		
		var stackStartY = {r:0,b:0,g:0,a:0,next:null};
		var stacky = stackStartY;
		for ( i = 1; i < divy; i++ )
		{
			stacky = stacky.next = {r:0,b:0,g:0,a:0,next:null};
			if ( i == ryp1 ) var stackEndY = stacky;
		}
		stacky.next = stackStartY;
		
		var stackIn = null;



		
		while ( iterations-- > 0 ) {
			yw = yi = 0;
			var mul_sum = this.mul_table[radiusX];
			var shg_sum = this.shg_table[radiusX];
			for ( y = height; --y > -1; )
			{
				r_sum = rxp1 * ( pr = pixels[yi] );
				g_sum = rxp1 * ( pg = pixels[yi+1] );
				b_sum = rxp1 * ( pb = pixels[yi+2] );
				a_sum = rxp1 * ( pa = pixels[yi+3] );

				stackx = stackStartX;

				for( i = rxp1; --i > -1; )
				{
					stackx.r = pr;
					stackx.g = pg;
					stackx.b = pb;
					stackx.a = pa;
					stackx = stackx.next;
				}

				for( i = 1; i < rxp1; i++ )
				{
					p = yi + (( widthMinus1 < i ? widthMinus1 : i ) << 2 );
					r_sum += ( stackx.r = pixels[p]);
					g_sum += ( stackx.g = pixels[p+1]);
					b_sum += ( stackx.b = pixels[p+2]);
					a_sum += ( stackx.a = pixels[p+3]);

					stackx = stackx.next;
				}

				stackIn = stackStartX;
				for ( x = 0; x < width; x++ )
				{
					pixels[yi++] = (r_sum * mul_sum) >>> shg_sum;
					pixels[yi++] = (g_sum * mul_sum) >>> shg_sum;
					pixels[yi++] = (b_sum * mul_sum) >>> shg_sum;
					pixels[yi++] = (a_sum * mul_sum) >>> shg_sum;

					p =  ( yw + ( ( p = x + radiusX + 1 ) < widthMinus1 ? p : widthMinus1 ) ) << 2;

					r_sum -= stackIn.r - ( stackIn.r = pixels[p]);
					g_sum -= stackIn.g - ( stackIn.g = pixels[p+1]);
					b_sum -= stackIn.b - ( stackIn.b = pixels[p+2]);
					a_sum -= stackIn.a - ( stackIn.a = pixels[p+3]);

					stackIn = stackIn.next;

				}
				yw += width;
			}

			mul_sum = this.mul_table[radiusY];
			shg_sum = this.shg_table[radiusY];
			for ( x = 0; x < width; x++ )
			{
				yi = x << 2;

				r_sum = ryp1 * ( pr = pixels[yi]);
				g_sum = ryp1 * ( pg = pixels[yi+1]);
				b_sum = ryp1 * ( pb = pixels[yi+2]);
				a_sum = ryp1 * ( pa = pixels[yi+3]);

				stacky = stackStartY;

				for( i = 0; i < ryp1; i++ )
				{
					stacky.r = pr;
					stacky.g = pg;
					stacky.b = pb;
					stacky.a = pa;
					stacky = stacky.next;
				}

				yp = width;

				for( i = 1; i <= radiusY; i++ )
				{
					yi = ( yp + x ) << 2;

					r_sum += ( stacky.r = pixels[yi]);
					g_sum += ( stacky.g = pixels[yi+1]);
					b_sum += ( stacky.b = pixels[yi+2]);
					a_sum += ( stacky.a = pixels[yi+3]);

					stacky = stacky.next;

					if( i < heightMinus1 )
					{
						yp += width;
					}
				}

				yi = x;
				stackIn = stackStartY;
				if ( iterations > 0 )
				{
					for ( y = 0; y < height; y++ )
					{
						p = yi << 2;
						pixels[p+3] = pa =(a_sum * mul_sum) >>> shg_sum;
						if ( pa > 0 )
						{
							pixels[p]   = ((r_sum * mul_sum) >>> shg_sum ); 
							pixels[p+1] = ((g_sum * mul_sum) >>> shg_sum );
							pixels[p+2] = ((b_sum * mul_sum) >>> shg_sum );
						} else {
							pixels[p] = pixels[p+1] = pixels[p+2] = 0
						}

						p = ( x + (( ( p = y + ryp1) < heightMinus1 ? p : heightMinus1 ) * width )) << 2;

						r_sum -= stackIn.r - ( stackIn.r = pixels[p]);
						g_sum -= stackIn.g - ( stackIn.g = pixels[p+1]);
						b_sum -= stackIn.b - ( stackIn.b = pixels[p+2]);
						a_sum -= stackIn.a - ( stackIn.a = pixels[p+3]);

						stackIn = stackIn.next;

						yi += width;
					}
				} else {
					for ( y = 0; y < height; y++ )
					{
						p = yi << 2;
						pixels[p+3] = pa =(a_sum * mul_sum) >>> shg_sum;
						if ( pa > 0 )
						{
							pa = 255 / pa;
							pixels[p]   = ((r_sum * mul_sum) >>> shg_sum ) * pa; 
							pixels[p+1] = ((g_sum * mul_sum) >>> shg_sum ) * pa;
							pixels[p+2] = ((b_sum * mul_sum) >>> shg_sum ) * pa;
						} else {
							pixels[p] = pixels[p+1] = pixels[p+2] = 0
						}

						p = ( x + (( ( p = y + ryp1) < heightMinus1 ? p : heightMinus1 ) * width )) << 2;

						r_sum -= stackIn.r - ( stackIn.r = pixels[p]);
						g_sum -= stackIn.g - ( stackIn.g = pixels[p+1]);
						b_sum -= stackIn.b - ( stackIn.b = pixels[p+2]);
						a_sum -= stackIn.a - ( stackIn.a = pixels[p+3]);

						stackIn = stackIn.next;

						yi += width;
					}
				}
			}
		}
		targetCtx.putImageData(imageData, targetX, targetY);
		return true;
	};

	/**
	 * Returns a clone of this object.
	 * @return {BlurFilter}
	 **/
	p.clone = function() {
		return new BlurFilter(this.blurX, this.blurY, this.quality);
	};

	p.toString = function() {
		return "[BlurFilter]";
	};

	createjs.BlurFilter = BlurFilter;

}());
