package com.guru.component.masking
{
	import com.guru.call.FunctionCall;
	import com.guru.display.button.LabelDragButton;
	import flash.display.BitmapData;
	import flash.display.MovieClip;
	import flash.display.Shape;
	import flash.events.MouseEvent;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	import flash.text.TextField;
	import flash.text.TextFieldAutoSize;
	import flash.text.TextFormat;
	import flash.ui.Keyboard;
	import flash.ui.Mouse;
	/**
	 * ...
	 * @author Eddy Lee
	 */
	public class MaskingTool
	{
		private var mTimeline_mc:MovieClip
		private var mDraggableArea_mc:MovieClip;
		private var mPointBtn_array:Array;
		private var mStickBtn_array:Array;
		private var mCurve_mc:MovieClip;
		private var mShape_mc:MovieClip;
		private var mFrame_mc:MovieClip;
		private var mDummy_mc:MovieClip;
		private var mData_array:Array;
		private var mPointConstructor:Class;
		private var mStickConstructor:Class;
		private var mSource_mc:MovieClip;
		private var mMaskContainer_mc:MovieClip;
		
		public function MaskingTool(pTimeline_mc:MovieClip)
		{
			mTimeline_mc = pTimeline_mc;
			mDraggableArea_mc = mTimeline_mc.draggableArea_mc;
			
			mSource_mc = mTimeline_mc.source_mc;
			mMaskContainer_mc = mTimeline_mc.mask_mc;
			
			mPointConstructor = mTimeline_mc.pointBtn0_mc.constructor;
			mStickConstructor = mTimeline_mc.stickBtn0_mc.constructor;
			
			mPointBtn_array = new Array();
			mStickBtn_array = new Array();
			mData_array = new Array();
			var i:Number;
			
			var btn_mc:MovieClip;
			for (i = 0; btn_mc = mTimeline_mc["stickBtn" + i + "_mc"]; i++ )
			{
				this.initBtn(btn_mc, mStickBtn_array, mStickBtn_array.length);
			}
			
			for (i = 0; btn_mc = mTimeline_mc["pointBtn" + i + "_mc"]; i++ )
			{
				this.initBtn(btn_mc, mPointBtn_array, mPointBtn_array.length);
			}
			
			mCurve_mc = new MovieClip();
			mShape_mc = new MovieClip();
			mFrame_mc = new MovieClip();
			mDummy_mc = new MovieClip();
			
			mMaskContainer_mc.addChild(mFrame_mc);
			mMaskContainer_mc.addChild(mShape_mc);
			mMaskContainer_mc.addChild(mDummy_mc);
			mMaskContainer_mc.addChild(mCurve_mc);
			
			mCurve_mc.buttonMode = true;
			
			this.refreshMask();
		}
		
		public function getMaskedBitmapData():BitmapData
		{
			var bmpData:BitmapData = new BitmapData(mDraggableArea_mc.width, mDraggableArea_mc.height, true, 0x000000)
			
			mSource_mc.cacheAsBitmap = true;
			mShape_mc.cacheAsBitmap = true;
			mSource_mc.mask = mShape_mc;
			
			bmpData.draw(mSource_mc);
			return bmpData;
		}
		
		public function dispose():void
		{
			while (mPointBtn_array.length > 0)
			{
				mPointBtn_array.shift().dispose();
			}
			
			while (mStickBtn_array.length > 0)
			{
				mStickBtn_array.shift().dispose();
			}
		}
		
		private function updateBtn(pBtn:LabelDragButton, pIndex_num:Number):void
		{
			pBtn.setDoubleClickCall(new FunctionCall(this, onBtnDoubleClick, pIndex_num));
		}
		
		private function initBtn(pBtn_mc:MovieClip, pStore_array:Array, pIndex_num:Number):LabelDragButton
		{
			var btn:LabelDragButton = new LabelDragButton(pBtn_mc);
			btn.setDragableArea(new Rectangle(mDraggableArea_mc.x, mDraggableArea_mc.y, mDraggableArea_mc.width, mDraggableArea_mc.height));
			btn.setDragStartCall(new FunctionCall(this, onBtnDragStart));
			btn.setDraggingCall(new FunctionCall(this, onBtnDragging));
			btn.setDragStopCall(new FunctionCall(this, onBtnDragStop));
			btn.setDoubleClickCall(new FunctionCall(this, onBtnDoubleClick, pIndex_num));
			pStore_array.splice(pIndex_num, 0, btn);
			return btn;
		}
		
		private function onBtnDragStart():void
		{
			this.onDragStart();
		}
		
		private function onBtnDragging():void
		{
			this.refreshMask();
		}
		
		private function onBtnDragStop():void
		{
			this.onDragStop();
		}
		
		private function onDragStart():void
		{
			Mouse.hide();
		}
		
		private function onDragStop():void
		{
			Mouse.show();
		}
		
		private function onBtnDoubleClick(pIndex_num:Number):void
		{
			if (mStickBtn_array.length > 2)
			{			
				var btn:LabelDragButton;
				var btn_mc:MovieClip;
				
				btn = mPointBtn_array.splice(pIndex_num, 1)[0];
				btn_mc = btn.getTimeline();
				btn.dispose();
				mTimeline_mc.removeChild(btn_mc);
				
				btn = mStickBtn_array.splice(pIndex_num, 1)[0];
				btn_mc = btn.getTimeline();
				btn.dispose();
				mTimeline_mc.removeChild(btn_mc);
				
				this.refreshMask();
			}
			else
			{
				trace("WARNING: Too few points!");
			}
		}
		
		private function refreshMask():void
		{
			while (mCurve_mc.numChildren > 0)
			{
				mCurve_mc.removeChildAt(0).removeEventListener(MouseEvent.DOUBLE_CLICK, onCurveDoubleClickHandler);
			}
			
			while (mFrame_mc.numChildren > 0)
			{
				mFrame_mc.removeChildAt(0);
			}
			
			while (mShape_mc.numChildren > 0)
			{
				mShape_mc.removeChildAt(0);
			}
			
			while (mDummy_mc.numChildren > 0)
			{
				mDummy_mc.removeChildAt(0);
			}
			
			while (mData_array.length > 0)
			{
				mData_array.shift();
			}
			
			var tmpShape:Shape = new Shape();
			var tmpDummy:Shape = new Shape();
			var tmpFrame:Shape = new Shape();
			
			tmpDummy.graphics.lineStyle(2, uint(0xFF0000), 1);
			tmpShape.graphics.beginFill(0x0000FF, 0);
			tmpFrame.graphics.beginFill(0x000000, 0.6);
			tmpFrame.graphics.lineTo(mDraggableArea_mc.width, 0);
			tmpFrame.graphics.lineTo(mDraggableArea_mc.width, mDraggableArea_mc.height);
			tmpFrame.graphics.lineTo(0, mDraggableArea_mc.height);
			tmpFrame.graphics.lineTo(0, 0);
			
			for (var i:Number = 0; i < mPointBtn_array.length; i++ )
			{
				this.updateBtn(mStickBtn_array[i], i);
				this.updateBtn(mPointBtn_array[i], i);
				
				var start_pt:Point = new Point(LabelDragButton(mStickBtn_array[i]).getTimeline().x, LabelDragButton(mStickBtn_array[i]).getTimeline().y);
				var end_pt:Point = new Point(LabelDragButton(mStickBtn_array[(i+1)%mStickBtn_array.length]).getTimeline().x, LabelDragButton(mStickBtn_array[(i+1)%mStickBtn_array.length]).getTimeline().y);
				var turning_pt:Point = new Point(LabelDragButton(mPointBtn_array[i]).getTimeline().x, LabelDragButton(mPointBtn_array[i]).getTimeline().y);
				
				//Quadratic Bézier curves: http://en.wikipedia.org/wiki/B%C3%A9zier_curve
				var t:Number = 0.5;
				var tangentialIntersection_pt:Point = this.getTangentialIntersectionPointByQuadraticBezierCurveFormula(start_pt, turning_pt, end_pt, t);
				
				mData_array.push([start_pt, turning_pt, end_pt, tangentialIntersection_pt]);
				
				if (i == 0)
				{
					tmpShape.graphics.moveTo(start_pt.x, start_pt.y);
					tmpFrame.graphics.moveTo(start_pt.x, start_pt.y);
				}
				tmpShape.graphics.curveTo(tangentialIntersection_pt.x, tangentialIntersection_pt.y, end_pt.x, end_pt.y);
				tmpFrame.graphics.curveTo(tangentialIntersection_pt.x, tangentialIntersection_pt.y, end_pt.x, end_pt.y);
				
				var tmpCurve:Shape = new Shape();
				tmpCurve.graphics.lineStyle(5, uint(0xFF0000), 0.6);
				tmpCurve.graphics.moveTo(start_pt.x, start_pt.y);
				tmpCurve.graphics.curveTo(tangentialIntersection_pt.x, tangentialIntersection_pt.y, end_pt.x, end_pt.y);
				var tmpCurve_mc:MovieClip = new MovieClip();
				tmpCurve_mc.doubleClickEnabled = true;
				tmpCurve_mc.addChild(tmpCurve);
				tmpCurve_mc.name = "curve" + i;
				tmpCurve_mc.addEventListener(MouseEvent.DOUBLE_CLICK, onCurveDoubleClickHandler);
				mCurve_mc.addChild(tmpCurve_mc);
			}
			
			tmpFrame.graphics.lineTo(0, 0);
			
			tmpShape.graphics.endFill();
			tmpFrame.graphics.endFill();
			
			mFrame_mc.addChild(tmpFrame);
			mShape_mc.addChild(tmpShape);
			mDummy_mc.addChild(tmpDummy);
		}
		
		private function getNearestTByOnePointOnCurve(pX_Num:Number, pY_num:Number, pStart_pt:Point, pTangentialIntersection_pt:Point, pEnd_pt:Point):Number
		{
			var shortestDistance_num:Number = 10000;
			var targetT_num:Number;
			
			for (var t:Number = 0; t < 1; t = t + 0.1 )
			{
				t = Math.round(t * 10) / 10;
				var turning_pt:Point = this.getTurningPointByQuadraticBezierCurveFormula(pStart_pt, pTangentialIntersection_pt, pEnd_pt, t);
				var distance_num:Number = this.calculateDistanceBetween(pX_Num, pY_num, turning_pt.x, turning_pt.y);
				
				if (distance_num < shortestDistance_num)
				{
					shortestDistance_num = distance_num;
					targetT_num = t;
				}
			}
			
			return targetT_num;
		}
		
		private function getTangentialIntersectionPointByQuadraticBezierCurveFormula(pStart_pt:Point, pTurning_pt:Point, pEnd_pt:Point, t):Point
		{
			var x_num:Number = ( pTurning_pt.x - Math.pow(1 - t, 2) * pStart_pt.x - Math.pow(t, 2) * pEnd_pt.x) / 2 / (1 - t) / t;
			var y_num:Number = ( pTurning_pt.y - Math.pow(1 - t, 2) * pStart_pt.y - Math.pow(t, 2) * pEnd_pt.y) / 2 / (1 - t) / t;
			
			var turning_pt:Point = new Point(x_num, y_num);
			return turning_pt;
		}
		
		private function getTurningPointByQuadraticBezierCurveFormula(pStart_pt:Point, pTangentialIntersection_pt:Point, pEnd_pt:Point, t):Point
		{
			var x_num:Number = Math.pow(1 - t, 2) * pStart_pt.x + Math.pow(t, 2) * pEnd_pt.x + 2 * (1 - t) * t * pTangentialIntersection_pt.x;
			var y_num:Number = Math.pow(1 - t, 2) * pStart_pt.y + Math.pow(t, 2) * pEnd_pt.y + 2 * (1 - t) * t * pTangentialIntersection_pt.y;
			
			var turning_pt:Point = new Point(x_num, y_num);
			return turning_pt;
		}
		
		private function onCurveDoubleClickHandler(e:MouseEvent):void
		{
			this.createExtraPointAtCurve(Number(MovieClip(e.target).name.charAt(5)), e.localX, e.localY);
		}
		
		private function createText(pText:*, pX_num:Number, pY_num:Number):TextField
		{
			var text_txt:TextField = new TextField();
			var textFormat:TextFormat = new TextFormat();
			textFormat.size = 20;
			text_txt.defaultTextFormat = textFormat;
			text_txt.text = pText + "";
			text_txt.x = pX_num;
			text_txt.y = pY_num;
			text_txt.selectable = false;
			text_txt.mouseEnabled = false;
			text_txt.mouseWheelEnabled = false;
			text_txt.autoSize = TextFieldAutoSize.LEFT;
			return text_txt;
		}
		
		private function createCross(pX_num:Number, pY_num:Number, pSize_num:Number = 10, pColor:uint = 0xFFFFFF):Shape
		{
			var shape:Shape = new Shape();
			shape.graphics.lineStyle(3, pColor);
			shape.graphics.moveTo(pX_num - pSize_num/2, pY_num - pSize_num/2);
			shape.graphics.lineTo(pX_num + pSize_num / 2, pY_num + pSize_num / 2);
			shape.graphics.moveTo(pX_num - pSize_num/2, pY_num + pSize_num/2);
			shape.graphics.lineTo(pX_num + pSize_num/2, pY_num - pSize_num/2);
			return shape;
		}
		
		private function createExtraPointAtCurve(pIndex_num:Number, pX_num:Number, pY_num:Number):void
		{
			var short_bol:Boolean = this.getNearestTByOnePointOnCurve(pX_num, pY_num, mData_array[pIndex_num][0], mData_array[pIndex_num][3], mData_array[pIndex_num][2]) < 0.5;
			
			var mid_pt:Point;
			var pointBtn_mc:MovieClip = new mPointConstructor();
			var stickBtn_mc:MovieClip = new mStickConstructor();
			mTimeline_mc.addChild(pointBtn_mc);
			mTimeline_mc.addChild(stickBtn_mc);
			
			if (short_bol)
			{
				mid_pt = this.calculateMidPointBetween(mData_array[pIndex_num][0].x, mData_array[pIndex_num][0].y, pX_num, pY_num);
				
				this.initBtn(stickBtn_mc, mStickBtn_array, pIndex_num + 1);
				this.initBtn(pointBtn_mc, mPointBtn_array, pIndex_num);
			}
			else
			{
				mid_pt = this.calculateMidPointBetween(mData_array[pIndex_num][2].x, mData_array[pIndex_num][2].y, pX_num, pY_num);
				
				this.initBtn(stickBtn_mc, mStickBtn_array, pIndex_num + 1);
				this.initBtn(pointBtn_mc, mPointBtn_array, pIndex_num + 1);
			}
			
			stickBtn_mc.x = pX_num;
			stickBtn_mc.y = pY_num;
			pointBtn_mc.x = mid_pt.x;
			pointBtn_mc.y = mid_pt.y;
		
			this.refreshMask();
		}
		
		private function calculateMidPointBetween(pFromX_num:Number, pFromY_num:Number, pToX_num:Number, pToY_num:Number):Point
		{
			return new Point((pToX_num + pFromX_num) / 2, (pToY_num + pFromY_num) / 2);
		}
		
		private function calculateDistanceBetween(pFromX_num:Number, pFromY_num:Number, pToX_num:Number, pToY_num:Number):Number
		{
			return Math.pow(Math.pow(pFromY_num - pToY_num, 2) + Math.pow(pFromX_num - pToX_num, 2), 0.5);
		}
	}

}