import com.netgrind.fx.ColorMatrix;
import com.netgrind.core.UtilsArray;
package com.netgrind.fx {
	import adobe.utils.CustomActions;
	import away3d.core.pick.PBPickingCollider;
	import away3d.library.assets.BitmapDataAsset;
	import com.bit101.components.InputSlider;
	import com.greensock.motionPaths.RectanglePath2D;
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.BlendMode;
	import flash.display.DisplayObject;
	import flash.display.IBitmapDrawable;
	import flash.display.PixelSnapping;
	import flash.display.Shader;
	import flash.display.ShaderData;
	import flash.display.Sprite;
	import flash.filters.BitmapFilter;
	import flash.filters.ColorMatrixFilter;
	import flash.filters.ShaderFilter;
	import flash.geom.ColorTransform;
	import flash.geom.Matrix;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	import flash.ui.Multitouch;
	import flash.utils.ByteArray;
	import flash.utils.Dictionary;
	import mx.core.BitmapAsset;
	
	/**
	 * ...
	 * @author cale
	 */
	public class FX {
		
		public static const WRAP:String = 'wrap';
		public static const STRETCH:String = 'strecth';
		public static const ORIGINAL:String = 'original';
		public static const DELETE:String = 'delete';
		
		private static var destroy:Dictionary = new Dictionary();
		private static var active:Dictionary = new Dictionary();
		
		[Embed(source = 'pb/pixelate.pbk', mimeType = 'application/octet-stream')]public static var PBPixelize:Class;
		
		public static function isActive(b:BitmapData):Boolean {
			for (var f:* in active) {
				if (active[f].indexOf(b) != -1) {
					return true;
				}
			}
			return false;
		}		
		
		public static function cancel(b:BitmapData, func:Function = null):void {
			if (func !=null) {
				if (!destroy[func]) {
					destroy[func] = [];
				}
				destroy[func].push(b);
			}else {
				for (var f:* in active) {
					if (active[f] && active[f].indexOf(b) != -1) {
						cancel(b, f);
					}
				}
			}
		}
		
		public static function greyscale(b:BitmapData, amount:Number = 1 ):void {
			const rc:Number = 1/3*amount, gc:Number = 1/3*amount, bc:Number = 1/3*amount;
			b.applyFilter(b, b.rect, new Point(), new ColorMatrixFilter([rc, gc, bc, 0, 0,rc, gc, bc, 0, 0, rc, gc, bc, 0, 0, 0, 0, 0, 1, 0]));
		}
		
		public static function saturation(b:BitmapData, amount:Number = 1):void {
			var c:ColorMatrix = new ColorMatrix();
			c.adjustSaturation(amount);
			b.applyFilter(b, b.rect, new Point(), new ColorMatrixFilter(c.toArray()));
		}
		
		public static function offsetRows(b:BitmapData, off:Array, flow:String = WRAP):void {
			var w:int = b.width;
			var o:BitmapData = b.clone();
			for (var i:int = 0; i < off.length && i < b.height; i++) {
				off[i] += w;
				off[i] %= w;
				var rBig:Rectangle = new Rectangle(0, i, w - off[i], 1);
				var pBig:Point =  new Point(off[i], i)
				var rSmall:Rectangle = new Rectangle(w - off[i], i, off[i]+1, 1);
				var pSmall:Point = new Point(0, i);
				if (rSmall.width > rBig.width) {
					var t:* = rBig;
					rBig = rSmall;
					rSmall = t;
					t = pBig;
					pBig = pSmall;
					pSmall = t;
				}
				b.copyPixels(o, rBig, pBig);
				if (flow == WRAP) b.copyPixels(o, rSmall, pSmall);	
				if (flow == STRETCH) b.fillRect(new Rectangle(pSmall.x,pSmall.y,rSmall.width,rSmall.height), b.getPixel32(pSmall.x==0?0:w-1,i));
				if (flow == DELETE) b.fillRect(new Rectangle(pSmall.x,pSmall.y,rSmall.width,rSmall.height), 0);
			}
		}
		
		public static function offsetColumns(b:BitmapData, off:Array, flow:String = WRAP):void {
			var h:int = b.height;
			var o:BitmapData = b.clone();
			for (var i:int = 0; i < off.length && i < b.width; i++) {
				off[i] += h;
				off[i] %= h;
				var rBig:Rectangle = new Rectangle(i,0,1, h-off[i]);
				var pBig:Point =  new Point(i, off[i])
				var rSmall:Rectangle = new Rectangle(i, h - off[i], 1, off[i]);
				var pSmall:Point = new Point(i,0);
				if (rSmall.height > rBig.height) {
					var t:* = rBig;
					rBig = rSmall;
					rSmall = t;
					t = pBig;
					pBig = pSmall;
					pSmall = t;
				}
				b.copyPixels(o, rBig, pBig);
				if (flow == WRAP) b.copyPixels(o, rSmall, pSmall);	
				if (flow == STRETCH) b.fillRect(new Rectangle(pSmall.x,pSmall.y,rSmall.width,rSmall.height), b.getPixel32(i,pSmall.y==0?0:h-1));
				if (flow == DELETE) b.fillRect(new Rectangle(pSmall.x,pSmall.y,rSmall.width,rSmall.height), 0);			
			}
		}
		
		public static function shiftHue(b:BitmapData, step:Number = 5):void {
			var c:ColorMatrix = new ColorMatrix();
			c.adjustHue(step);
			b.applyFilter(b, b.rect, new Point(), new ColorMatrixFilter(c.toArray()));
		}
		public static function shiftBrightness(b:BitmapData, step:Number = 1):void {
			var c:ColorMatrix = new ColorMatrix();
			c.adjustBrightness(step);
			b.applyFilter(b, b.rect, new Point(), new ColorMatrixFilter(c.toArray()));
		}
		
		
		
		private static var hues:Dictionary = new Dictionary();
		public static function hueShift(b:BitmapData, step:int = 5, totalSteps:int = 72, onComplete:Function = null):void {
			if (!canCompute(hueShift, b)) {
				return;
			}
			if (!hues[b]) {
				hues[b] = [b.clone(), new ColorMatrix()];
			}
			b.copyPixels(hues[b][0], b.rect, new Point());
			hues[b][1].adjustHue(step);
			var bf:ColorMatrixFilter = new ColorMatrixFilter(hues[b][1].toArray());				
			b.applyFilter(b, b.rect, new Point(), bf);			
			if (totalSteps > 0) {
				totalSteps--;
			}			
			if (totalSteps!=0) {
				Scheduler.invokeIn(hueShift, 1, b,step, totalSteps, onComplete);
			}else {
				deactivate(hueShift, b);
				if(onComplete != null) {
					onComplete();
				}
			}
		}
		
		private static var waves:Dictionary = new Dictionary();
		public static function waveShift(b:BitmapData, totalFrames:int = 30, phaseShift:Number = 1, amp:Number=1, freq:Number=20, flow:String = DELETE, onComplete:Function = null):void {
			if (!canCompute(waveShift, b)) {
				return;
			}
			if (!waves[b]) {
				waves[b] = [b.clone(),totalFrames];
			}
			b.copyPixels(waves[b][0], b.rect, new Point());
			offsetColumns(b, UtilsArray.sine(UtilsArray.fill(b.width, 0), amp, freq, phaseShift*totalFrames ), flow);	
			if (totalFrames > 0) {
				totalFrames--;
			}			
			if (totalFrames!=0) {
				Scheduler.invokeIn(waveShift, 1, b,totalFrames, phaseShift, amp, freq, flow, onComplete);
			}else {
				deactivate(waveShift, b);
				if(onComplete != null) {
					onComplete();
				}
			}
		}
		
		public static function growBoth(b:BitmapData, growColor:uint = 0x00000000, unusedColor:uint = 0xffff00ff, onComplete:Function = null):void {
			if (!canCompute(growBoth, b)) {
				return;
			}
			for (var i:int = 0; i < b.width; i++) {
				for (var j:int = 0; j < b.height; j++) {
					if(b.getPixel32(i,j) !=growColor){
						if (i != 0) {
							if (b.getPixel32(i - 1, j)==growColor) {
								b.setPixel32(i, j, unusedColor);
							}
						}
						if (i != b.width-1) {
							if (b.getPixel32(i + 1, j)==growColor) {
								b.setPixel32(i, j, unusedColor);
							}
						}
						if (j != 0) {
							if (b.getPixel32(i , j-1)==growColor) {
								b.setPixel32(i, j, unusedColor);
							}
						}
						if (j != b.height-1) {
							if (b.getPixel32(i , j+1)==growColor) {
								b.setPixel32(i, j, unusedColor);
							}
						}
					}
				}
			}
			if (applyChange(b,unusedColor,growColor)) {
				Scheduler.invokeIn(growBoth, 1, b, growColor, unusedColor, onComplete);
			}else {
				deactivate(growBoth, b);
				if(onComplete != null) {
					onComplete();
				}
			}
		}
		
		public static function growHorizontal(b:BitmapData, growColor:uint = 0x00000000, unusedColor:uint = 0xffff00ff, onComplete:Function = null):void {
			if (!canCompute(growHorizontal, b)) {
				return;
			}
			for (var i:int = 0; i < b.width; i++) {
				for (var j:int = 0; j < b.height; j++) {
					if(b.getPixel32(i,j) !=growColor){
						if (i != 0) {
							if (b.getPixel32(i - 1, j)==growColor) {
								b.setPixel32(i, j, unusedColor);
							}
						}
						if (i != b.width-1) {
							if (b.getPixel32(i + 1, j)==growColor) {
								b.setPixel32(i, j, unusedColor);
							}
						}
					}
				}
			}
			if (applyChange(b,unusedColor,growColor)) {
				Scheduler.invokeIn(growHorizontal, 1, b, growColor, unusedColor, onComplete);
			}else {
				deactivate(growHorizontal, b);
				if(onComplete != null) {
					onComplete();
				}
			}
		}
		
		public static function growVertical(b:BitmapData, growColor:uint = 0x00000000, unusedColor:uint = 0xffff00ff, onComplete:Function = null):void {
			if (!canCompute(growVertical, b)) {
				return;
			}
			for (var i:int = 0; i < b.width; i++) {
				for (var j:int = 0; j < b.height; j++) {
					if(b.getPixel32(i,j) !=growColor){
						if (j != 0) {
							if (b.getPixel32(i , j-1)==growColor) {
								b.setPixel32(i, j, unusedColor);
							}
						}
						if (j != b.height-1) {
							if (b.getPixel32(i , j+1)==growColor) {
								b.setPixel32(i, j, unusedColor);
							}
						}
					}
				}
			}
			if (applyChange(b,unusedColor,growColor)) {
				Scheduler.invokeIn(growVertical, 1, b, growColor, unusedColor, onComplete);
			}else {
				deactivate(growVertical, b);
				if(onComplete != null) {
					onComplete();
				}
			}
		}
				
		static private function applyChange(b:BitmapData, unusedColor:uint, growColor:uint):Boolean {
			var change:Boolean = false;
			for (var k:int = 0; k < b.width; k++) {
				for (var l:int = 0; l < b.height; l++) {
					if (b.getPixel32(k,l) == unusedColor) {
						b.setPixel32(k,l, growColor);
						change = true;
					}
				}
			}
			return change;
		}
		
		static private function canCompute(func:Function, b:BitmapData):Boolean {
			if (!active[func]) {
				active[func] = [];
			}
			if (active[func].indexOf(b) == -1) {
				active[func].push(b);
			}
			if(destroy[func]){
				var kill:int = destroy[func].indexOf(b);
				if (kill != -1) {
					destroy[func].splice(kill, 1);
					deactivate(func, b);
					return false;
				}
			}
			return true;
		}
		
		static private function deactivate(func:Function, b:BitmapData):void {
			var ndx:int;
			if(active[func]){
				ndx = active[func].indexOf(b)
				if(ndx!=-1){
					active[func].splice(ndx, 1);
					if (func == hueShift) {
						hues[active[func]] = null;
					}
				}
			}
			if(destroy[func]){
				ndx = destroy[func].indexOf(b)
				if(ndx!=-1){
					destroy[func].splice(ndx, 1);
				}
			}
		}
		
		static public function colorize(color:uint, bd:BitmapData):void {
			var r:* = UtilsArt.HEXtoRGB(color);
			r.r /= 255;
			r.g /= 255;
			r.b /= 255;
			bd.applyFilter(bd, bd.rect, new Point(), new ColorMatrixFilter([
				r.r,0,0,0,0,
				0,r.g,0,0,0,
				0,0, r.b, 0, 0,
				0,0,0,1,0
			]));
		}
		
		public static function pixelize( source:BitmapData, blockSize:int ):void
		{
			if (blockSize <1) blockSize = 1;
			var bitmap:Bitmap = new Bitmap( source );
			bitmap.smoothing = true;  // blends pixels values
			var smaller:BitmapData = new BitmapData( Math.ceil(source.width  / blockSize), Math.ceil(source.height / blockSize),true,0 );
			var matrix:Matrix = new Matrix();
			matrix.scale(1 / blockSize, 1 / blockSize);
			smaller.draw( bitmap, matrix );
			bitmap = new Bitmap( smaller);
			matrix.a = source.width/(bitmap.width-1);  //  gives the opposite effect from before
			matrix.d = source.height/(bitmap.height-1);  //  gives the opposite effect from before
			source.fillRect(source.rect, 0);
			source.draw( bitmap, matrix );
			smaller.dispose();  //  always dispose BitmapData no longer needed
		}
		
		public static function zoom(b:BitmapData,zoomX:Number, zoomY:Number,offsetX:int=0,offsetY:int=0):void {
			var image:Bitmap = new Bitmap(b.clone());
			b.fillRect(b.rect, 0);
			b.draw(image,new Matrix(zoomX,0,0,zoomY,offsetX,offsetY));
			
		}
		
		static public function convertToBitmap(obj:DisplayObject, bufferX:int, bufferY:int):Bitmap {
			var b:Bitmap = new Bitmap(new BitmapData(obj.width+bufferX*2, obj.height+bufferY*2, true, 0));
			b.bitmapData.draw(obj,new Matrix(1,0,0,1,bufferX,bufferY));
			b.x = obj.x - bufferX;
			b.y = obj.y - bufferY;
			b.scaleX = obj.scaleX;
			b.scaleY = obj.scaleY;
			return b;
		}
		
		public static function strokeDither(b:BitmapData, color1:uint, color2:uint, angle:Number, distance:Number, spacing:Number, minWidth:Number, maxWidth:Number):void {
			var s:Sprite = UtilsArt.makeRectangle(b.width, b.height, color2);
			for (var j:int = 0; j < b.width; j+=spacing) {
				drawFrom(j,0)
			}
			var yAdd:Number = Math.tan(Math.PI*.5-angle) * spacing;
			if(angle!=0){
				for (var i:int = 0; i < b.height; i+=Math.abs(yAdd)) {
					drawFrom(0,i)
					drawFrom(b.width-1,i)
				}
			}
			b.fillRect(b.rect, 0);
			b.draw(s);
			s.graphics.clear();
			function drawFrom(xx:Number , yy:Number):void {
				var cur:uint = b.getPixel(xx, yy);
				function setLineStyle():void { s.graphics.lineStyle(cur / 0xffffff * (maxWidth-minWidth) + minWidth, color1); }
				setLineStyle();
				s.graphics.moveTo(xx, yy);
				while (xx <= b.width && yy <= b.height && xx>= 0&&yy>=0) {
					yy += Math.cos(angle)*distance;
					xx += Math.sin(angle)*distance;
					s.graphics.lineTo(xx, yy);
					setLineStyle();								
					cur = b.getPixel(xx, yy);
				}
				s.graphics.lineTo(xx, yy);
			}
			
		}
		
		static public function halftoneDots(b:BitmapData, color1:uint, color2:uint, spacing:Number, minRad:Number, maxRad:Number):void {
			var s:Sprite = UtilsArt.makeRectangle(b.width, b.height, color1);
			for (var i:int = 0; i < b.width; i += spacing) {
				for (var j:int = 0; j < b.height; j += spacing) {
					var color:uint;
					for (var k:int = 0; k < spacing; k++) {
						for (var l:int = 0; l < spacing; l++) {
							color += b.getPixel(i + k, j + l);	
							color *= .5;
						}
					}
					s.graphics.beginFill(color2);
					if (color / 0xffffff > 1) trace('LESS');
					s.graphics.drawCircle(i + spacing * .5, j + spacing * .5, color / 0xffffff * (maxRad - minRad) + minRad);
				}
			}
			b.draw(s);
			s.graphics.clear();
			s = null;
		}
		
		static public function chunkShift(width:int, height:int, shifts:int, distance:int, b:BitmapData):void {
			for (var i:int = 0; i < shifts; i++) {
				var p:Point = new Point(Math.random() * b.width, Math.random() * b.height);
				b.copyPixels(b, new Rectangle(p.x, p.y, width, height), new Point(p.x + (Math.random() * distance * 2 - distance), p.y + (Math.random() * distance * 2 - distance)));
			}
		}
		
		static public function disolve(loops:Number, bit:BitmapData):void {
			bit.pixelDissolve(bit, bit.rect, new Point(), Math.random() * 100000, (bit.height * bit.width) * loops, 0);
		}
		
		static public function mirror(b:BitmapData, position:Number, horizontal:Boolean = true,flip:Boolean = false):void {
			var c:BitmapData = new BitmapData(b.width, b.height,true,0);
			var bit:Bitmap = new Bitmap(c);
			if(horizontal){
				if(!flip){
					c.copyPixels(b, new Rectangle(b.width*position,0,b.width-b.width*position,b.height), new Point(c.width * position));
				}else {
					c.copyPixels(b, new Rectangle(0,0,b.width*position,b.height),new Point());
				}
				bit.x = b.width * position*2;
				bit.scaleX = -1;
			}else {
				if(!flip){
					c.copyPixels(b, new Rectangle(0,b.height*position,b.width,b.height-b.height*position), new Point(0,c.height * position));
				}else {
					c.copyPixels(b, new Rectangle(0,0,b.width,b.height*position), new Point());
				}
				bit.y = b.height * position * 2;
				bit.scaleY = -1;
			}
			var hold:Sprite = new Sprite();
			hold.addChild(bit);
			b.draw(hold);
			hold = null;
			bit = null;
			c.dispose();
		}
		
		public static function sine(bd:BitmapData,horizontal:Boolean= true, freq:Number = 1, phase:Number = 0, amp:Number = 32, flow:String=WRAP):void {
			if (horizontal) {
				offsetColumns(bd, UtilsArray.sine(UtilsArray.fill(bd.width, 0), amp, freq, phase), flow);
			}else {
				offsetRows(bd, UtilsArray.sine(UtilsArray.fill(bd.height, 0), amp, freq, phase), flow);
			}
		}
		
	}
	
}