/* <copyright>
This file contains proprietary software owned by Motorola Mobility, Inc.<br/>
No rights, expressed or implied, whatsoever to this software are provided by Motorola Mobility, Inc. hereunder.<br/>
(c) Copyright 2011 Motorola Mobility, Inc.  All Rights Reserved.
</copyright> */

var Montage = require("montage/core/core").Montage,
    DrawingTool = require("js/tools/drawing-tool").DrawingTool,
    snapManager = require("js/helper-classes/3D/snap-manager").SnapManager,
    viewUtils = require("js/helper-classes/3D/view-utils").ViewUtils,
    vecUtils = require("js/helper-classes/3D/vec-utils").VecUtils,
    drawUtils = require("js/helper-classes/3D/draw-utils").DrawUtils,
    Properties3D = ("js/models/properties-3d").Properties3D;

exports.ModifierToolBase = Montage.create(DrawingTool, {

    //-------------------------------------------------------------------------
    // Drawing Mode Info
    drawingFeedback: { value: { mode: "Draw3D", type: "rectangle" } },

    //-------------------------------------------------------------------------
    // Snapping-specific properties
    _canSnap: { value: true },
    _dragPlane: { value: null },
	_snapParam: { value: null },
	_snapIndex: { value: -1 },
	_useQuadPt: { value: false },

	// we set snapping capabilities depending on the tool.
	// The following variables are set in a tool's initializeSnapping method called on mouse down.
	_snapToElements: { value: true },
	_snapToGrid: { value: true },

    _clickedObject : {value : null },
    clickedObject:
	{
		get: function () {
			return this._clickedObject;
		},
		set: function (value) {
			if(value === this.application.ninja.currentDocument.documentRoot)
			{
				this._clickedObject = this._target;
			}
			else
			{
				this._clickedObject = value;
			}

			if(value)
			{
				this._startMat = viewUtils.getMatrixFromElement(this._clickedObject);
			}
			else
			{
				this._startMat = null;
			}
		}
	},
    
    _getObjectBeingTracked :
    {
        value: function(hitRec)
        {
            var elt = hitRec.getElt();
            if(elt === this.application.ninja.currentDocument.documentRoot)
            {
                elt = this._target;
            }

            return elt;
        }
    },

    drawWithoutSnapping:
    {
        value: function(event)
        {
            // override in subclasses
        }
    },
    
    initializeSnapping:
    {
        value: function(event)
        {
            this._mouseDownHitRec = null;
			this._mouseUpHitRec   = null;

			snapManager.clearAvoidList();
			snapManager.clearDragPlane();

			// the translate tool does snap align to the bounds of the object only.
			// turn off snap align to the cursor.  This needs to be re-enabled in the mouse up method
			snapManager.enableSnapAlign( false );

			// snap to element and snap to grid are conditionally enabled based
			// on the snap results of the mouse down.  enable everything for the first snap
			this._snapToElements = snapManager.elementSnapEnabledAppLevel();
			this._snapToGrid = snapManager.gridSnapEnabledAppLevel();

            this._dragPlane = null;
            var do3DSnap = true;

//            if(this._handleMode === null)
//            {
//                if(!this._activateOriginHandle)
//                {
//                    this.doSelection(event);

                    snapManager.enableElementSnap	( true	);
//                    snapManager.enableGridSnap		( true	);
//                }
//            }
//            else
//            {
//                this._matL = this._handles[this._handleMode]._matL.slice(0);
//                this._planeEq = this._handles[this._handleMode]._planeEq.slice(0);
//                this._dirVecL = this._handles[this._handleMode]._dirVecL.slice(0);
//                this.m_startPoint = MathUtils.getLocalPoint(event.layerX, event.layerY, this._planeEq, this._matL);
//                if(!this.m_startPoint)
//                {
//                    this.m_startPoint = [this.m_xStart, this.m_yStart];
//                }
//            }


            if(this._targets)
            {
                var point = webkitConvertPointFromPageToNode(this.application.ninja.stage.canvas,
                                                                new WebKitPoint(event.pageX, event.pageY));
				// do the snap before setting up the avoid list to allow
				// a snap on the mouse down
				var hitRec = snapManager.snap(point.x, point.y, do3DSnap);

                // we don't want to snap to selected objects during the drag
                var len = this._targets.length;
                for(var i=0; i<len; i++)
                {
                    snapManager.addToAvoidList( this._targets[i].elt );
                }
				if (hitRec)
				{
					// disable snap attributes
					if (hitRec.getType() == hitRec.SNAP_TYPE_ELEMENT)
					{
						this._snapToElements = false;
						this._snapToGrid = false;
					}
					else if (hitRec.getType() == hitRec.SNAP_TYPE_ELEMENT_CENTER)
					{
						snapManager.enableSnapAlign( snapManager.snapAlignEnabledAppLevel() );
					}

					// parameterize the snap point on the target
					this._snapParam = this.parameterizeSnap( hitRec );

                    if(!this._dragPlane)
                    {
//                        if( this._inLocalMode && (this._startMatArray.length === 1) )
//                        {
//                            this._dragPlane = viewUtils.getUnprojectedElementPlane(this._clickedObject);
//                            snapManager.setupDragPlaneFromPlane(this._dragPlane);
//                        }
//                        else
//                        {
                            this._dragPlane = snapManager.setupDragPlanes( hitRec );
//                        }
                    }

					var wpHitRec = hitRec.convertToWorkingPlane( this._dragPlane );
					this._mouseDownHitRec = wpHitRec;
					this._mouseUpHitRec = null;

					var pt = hitRec.getScreenPoint();
					this.downPoint.x = pt[0];
					this.downPoint.y = pt[1];

                    snapManager.drawLastHit();

                    snapManager.enableSnapAlign( snapManager.snapAlignEnabledAppLevel() );
                    snapManager.enableElementSnap( snapManager.elementSnapEnabledAppLevel() );
                    snapManager.enableGridSnap( snapManager.gridSnapEnabledAppLevel() );
				}
            }
            else
            {
                this.target = null;
            }
        }
    },
    
  	/*
	 *  The parameterization is based on the position of the
	 *  snap point in pre-transformed element screen space
	 */
	parameterizeSnap:
	{
		value: function( hitRec )
		{
			var paramPt = [0,0,0];
            var elt = this._getObjectBeingTracked(hitRec);
			if (elt)
			{
                this.clickedObject = elt;

                var worldPt = hitRec.calculateStageWorldPoint();
                MathUtils.makeDimension4( worldPt );
                var mat = viewUtils.getObjToStageWorldMatrix( elt, true );
                if(mat)
                {
                    var invMat = glmat4.inverse(mat, []);
                    var scrPt = MathUtils.transformHomogeneousPoint( worldPt, invMat );
                    scrPt = MathUtils.applyHomogeneousCoordinate( scrPt );

					var bounds = viewUtils.getElementViewBounds3D( elt );
					var x0 = bounds[0][0],  x1 = bounds[3][0],
						y0 = bounds[0][1],  y1 = bounds[1][1];
					var dx = x1 - x0,   dy = y1 - y0;
					var u = 0, v = 0;
					if (MathUtils.fpSign(dx) != 0)
						u = (scrPt[0] - x0) / dx;
					if (MathUtils.fpSign(dy) != 0)
						v = (scrPt[1] - y0) / dy;

					paramPt[0] = u;
					paramPt[1] = v;
					paramPt[2] = scrPt[2];
				}
			}

			return paramPt;
		}
	},


    GetObjectHitPoint :
	{
		value: function()
		{
			var scrPt;
			var elt = this._clickedObject;
			if (elt)
			{
				var bounds = viewUtils.getElementViewBounds3D( elt );
				var x0 = bounds[0][0],  x1 = bounds[3][0],
					y0 = bounds[0][1],  y1 = bounds[1][1];
				var dx = x1 - x0,   dy = y1 - y0;

				var x = x0 + this._snapParam[0]*dx,
					y = x0 + this._snapParam[1]*dy,
					z = this._snapParam[2];
				scrPt = [x,y,z];
			}

			return scrPt;
		}
	},

	GetQuadrantSnapPoint:
	{
		value: function(xEvent, yEvent)
		{
			var globalPt;
			var elt = this._clickedObject;
			if (elt && this._snapParam)
			{
				var tx = this._snapParam[0] <= 0.5 ? 0.0 : 1.0,
					ty = this._snapParam[1] <= 0.5 ? 0.0 : 1.0;

				var bounds = viewUtils.getElementViewBounds3D( elt );
				var x0 = bounds[0][0],  x1 = bounds[3][0],
					y0 = bounds[0][1],  y1 = bounds[1][1];
				var dx = x1 - x0,   dy = y1 - y0;

				var x = x0 + tx*dx,
					y = x0 + ty*dy,
					z = 0.0;
				var localPt = [x,y,z];
				globalPt = viewUtils.localToGlobal( localPt,  elt );
 
				// add in the delta
				var hitPt = this.GetObjectHitPoint();
				var scrPt = viewUtils.localToGlobal( hitPt, this._clickedObject );
				var delta = [xEvent-scrPt[0], yEvent-scrPt[1]];
				globalPt[0] += delta[0];
				globalPt[1] += delta[1];
			}

			return globalPt;
		}
	},

	GetQuadrantPoint:
	{
		value: function(useViewPoint)
		{
			var elt = this._clickedObject;
			var tx = this._snapParam[0] <= 0.5 ? 0.0 : 1.0,
				ty = this._snapParam[1] <= 0.5 ? 0.0 : 1.0;

			var bounds = viewUtils.getElementViewBounds3D( elt );
			var x0 = bounds[0][0],  x1 = bounds[3][0],
				y0 = bounds[0][1],  y1 = bounds[1][1];
			var dx = x1 - x0,   dy = y1 - y0;

			var x = x0 + tx*dx,
				y = x0 + ty*dy,
				z = 0.0;
			var scrPt = [x,y,z];

			viewUtils.pushViewportObj( elt );
			var viewPt = viewUtils.screenToView( scrPt[0], scrPt[1], scrPt[2] );
			viewUtils.popViewportObj();

            if(useViewPoint)
			{
				return viewPt;
			}
            else
            {
                return MathUtils.transformPoint( viewPt, this._startMat );
            }
		}
	},

	GetSourcePointFromBoundary :
	{
		value: function( index, useViewPoint )
		{
			var elt = this._clickedObject;
			var bounds = viewUtils.getElementViewBounds3D( elt );

			var x = bounds[index][0],
				y = bounds[index][1],
				z = 0;
			var scrPt = [x,y,z];

			viewUtils.pushViewportObj( elt );
			var viewPt = viewUtils.screenToView( scrPt[0], scrPt[1], scrPt[2] );
			viewUtils.popViewportObj();
            
			if(useViewPoint)
			{
				return viewPt;
			}
            else
            {
                return MathUtils.transformPoint( viewPt, this._startMat );
            }
		}
	},

	GetSourcePointFromParameterizedTarget :
	{
		value: function(useViewPoint)
		{
			var elt = this._clickedObject;
			var bounds = viewUtils.getElementViewBounds3D( elt );
			var x0 = bounds[0][0],  x1 = bounds[3][0],
				y0 = bounds[0][1],  y1 = bounds[1][1];
			var dx = x1 - x0,   dy = y1 - y0;

			var x = x0 + this._snapParam[0]*dx,
				y = x0 + this._snapParam[1]*dy,
				z = this._snapParam[2];
			var scrPt = [x,y,z];

			viewUtils.pushViewportObj( elt );
			var viewPt = viewUtils.screenToView( scrPt[0], scrPt[1], scrPt[2] );
			viewUtils.popViewportObj();
            
			if(useViewPoint)
			{
				return viewPt;
			}
            else
            {
                return MathUtils.transformPoint( viewPt, this._startMat );
            }
		}
	},

    getMousePoints: {
        value: function()
        {
            var elt = this._clickedObject;
            if (elt)
            {
                var index = this._snapIndex;
                var pt0;
                var useViewPoint = (this._inLocalMode && (this._targets.length === 1));
                if (this._useQuadPt)
                {
                    pt0 = this.GetQuadrantPoint(useViewPoint);
                }
                else
                {
                    pt0 = (index < 0) ? this.GetSourcePointFromParameterizedTarget(useViewPoint) : this.GetSourcePointFromBoundary( index, useViewPoint );
                }
                var pt1 = this._mouseUpHitRec.calculateStageWorldPoint();
                MathUtils.makeDimension4( pt1 );
                var obj2World = viewUtils.getObjToStageWorldMatrix( elt, true );
                var world2Obj = glmat4.inverse(obj2World, []);
                pt1 = MathUtils.transformHomogeneousPoint( pt1, world2Obj );
                pt1 = MathUtils.applyHomogeneousCoordinate( pt1 );
                viewUtils.pushViewportObj( elt );
                pt1 = viewUtils.screenToView( pt1[0], pt1[1], pt1[2] );
                viewUtils.popViewportObj();
                if(!useViewPoint)
                {
                    pt1 = MathUtils.transformPoint( pt1, this._startMat );
                }

                return {pt0:pt0, pt1:pt1};
            }
            else
            {
                return null;
            }
        }
    },

    updateUsingSnappingData: {
        value: function(event) {
            this.getUpdatedSnapPoint(event);
            if (this._mouseDownHitRec && this._mouseUpHitRec)
            {
                var data = this.getMousePoints();
                if(data)
                {
                    this.modifyElements(data, event);
                }
            }
        }
    },

    startDraw: {
        value: function(event) {
            this.isDrawing = true;
            this.application.ninja.stage.showSelectionBounds = false;

            if(this._canSnap)
            {
                this.initializeSnapping(event);
            }
            else
            {
                this.drawWithoutSnapping(event);
            }
        }
    },

    /*
     * Non-snapping mouse move calculation for points
     */
    updateWithoutSnappingData: {
        value: function(event) {
            var point = webkitConvertPointFromPageToNode(this.application.ninja.stage.canvas,
                                                        new WebKitPoint(event.pageX, event.pageY));

            var data = {pt0:this.downPoint, pt1:point};

            this.modifyElements(data, event);
        }
    },

    doSnap: {
        value: function (event) {
            this.application.ninja.stage.clearDrawingCanvas();
            this.getUpdatedSnapPoint(event);
        }
    },

    /**
     * Used on the Mouse Move to calculate new snap point.
     */
    getUpdatedSnapPoint: {
        value: function(event) {
            var point = webkitConvertPointFromPageToNode(this.application.ninja.stage.canvas,
                                                            new WebKitPoint(event.pageX, event.pageY));
            var x = point.x, y = point.y;
			this._useQuadPt = false;

			///////////////////////////////////////////////////////////
			// do a 3D snap if the mouse button is not down
			this._snapIndex = -1;
			this._useQuadPt = false;
			var mouseIsDown = (this._mouseDownHitRec !== null);
			var do3DSnap = (!mouseIsDown || event.shiftKey);

			// set the snapping capabilities
			if (mouseIsDown)
			{
				snapManager.enableElementSnap	( this._snapToElements	);
				snapManager.enableGridSnap		( this._snapToGrid		);
			}
//			else
//			{
//				this._showFeedbackOnMouseMove(event);
//			}

			snapManager.enableElementSnap	( snapManager.elementSnapEnabledAppLevel()	);
			snapManager.enableGridSnap		( snapManager.gridSnapEnabledAppLevel()		);
			//snapManager.enableSnapAlign		( snapManager.snapAlignEnabledAppLevel()	);
			snapManager.enableSnapAlign		( false	);	// only snap to element bounds (below)

			// do the snap
			var quadPt;
			if (mouseIsDown)
				quadPt = this.GetQuadrantSnapPoint(x,y);
			var hitRec = snapManager.snap(x, y, do3DSnap, quadPt );

			snapManager.enableSnapAlign( snapManager.snapAlignEnabledAppLevel()	);
			if ( snapManager.snapAlignEnabled() && this._clickedObject &&
                (this._clickedObject !== this.application.ninja.currentDocument.documentRoot) )
			{
				var alignBounds = !hitRec || (hitRec.getType() == hitRec.SNAP_TYPE_STAGE) || hitRec.isSomeGridTypeSnap();
				if (alignBounds)
				{
					// calculate the delta to offset the points of the element by
					var scrPt = this.GetObjectHitPoint();
					scrPt = viewUtils.localToGlobal( scrPt, this._clickedObject );
					var delta = [x-scrPt[0], y-scrPt[1]];

					var alignArray = new Array();
					snapManager.snapAlignToElementBounds( this._clickedObject, delta, alignArray );
					if (alignArray.length > 0)
						hitRec = alignArray[0];
				}
			}

			if (hitRec)
			{
				if (mouseIsDown && this._clickedObject)
				{
					// make the hit record working plane relative
					this._snapIndex = hitRec.getSnapBoundaryIndex();
					this._useQuadPt = hitRec.getUseQuadPoint();
					var wp = this._dragPlane ? this._dragPlane : workingPlane;
					hitRec = hitRec.convertToWorkingPlane( wp );

                    // update the target
                    this._mouseUpHitRec = hitRec;
				}
			}
        }
    },
        
    doDraw: {
        value: function(event)
        {
            this.application.ninja.stage.clearDrawingCanvas();

            if(this._canSnap)
            {
                this.updateUsingSnappingData(event);
            }
            else
            {
                this.updateWithoutSnappingData(event);
            }
        }
    },

    endDraw: {
        value: function (event) {
            this.application.ninja.stage.clearDrawingCanvas();

            this.downPoint.x = null;
            this.downPoint.y = null;
//            this.isDrawing = false;

            if(this._canSnap)
            {
                this.cleanupSnap();
            }
        }
    },
    
    cleanupSnap: {
        value: function() {
            this.mouseDownHitRec = null;
            this.mouseUpHitRec = null;
            
            this._dragPlane = null;
			this._useQuadPt = false;

			snapManager.clearAvoidList();
			snapManager.clearDragPlane();

			// restore the snap settings
			snapManager.enableSnapAlign( snapManager.snapAlignEnabledAppLevel() );
			snapManager.enableElementSnap( snapManager.elementSnapEnabledAppLevel() );
			snapManager.enableGridSnap( snapManager.gridSnapEnabledAppLevel() );
        }
    },
    //-------------------------------------------------------------------------

    //-------------------------------------------------------------------------
    // Info for tool's handles
    _activateOriginHandle: { value: false },
    _startOriginArray : {value : null },
    _origin: { value: null },
    _delta: { value: null },
    _startPoint: { value: [0, 0] },
    _matL : { value: Matrix.I(4) },
    _planeEq: { value: null },
    _dirVecL: { value: null },

    // override in subclasses
    _initializeToolHandles: {
        value: function() {
            if(!this._handles)
            {
            }
            else
            {
            }
        }
    },
    //-------------------------------------------------------------------------

    //-------------------------------------------------------------------------
    // Info about objects being modified by the tool
    // mode === 0 => x/y translation, mode === 1 => z translation
    _mode : {value : 0 },
    _mouseSpeed: { value: 1 },

    _target: { value: null },
    target:
    {
    	get: function () {
    		return this._target;
    	},
    	set: function (value) {
    		this._target = value;
    		if (value === null)
            {
    			return;
    		}

            if (this._target.elementModel && this._target.elementModel.props3D)
            {
				// do nothing
			}
			else
            {
                console.warn("no props3D model");
//                if(!this._target.elementModel)
//                {
//
//                }
//                var p3d = Montage.create(Properties3D).init(el);
//
//                this._target.elementModel = Montage.create(ElementModel, {
//                                    type:       { value: tag},
//                                    selection:  { value: selection},
//                                    controller: { value: ControllerFactory.getController(controller)},
//                                    props3D:    { value: p3d}
//                            });
//				this._target.props3D = Object.create(Properties3D, {});
//				this._target.props3D.Init(this._target);
			}
    	}
    },

    _startMat: { value: Matrix.I(4) },
    
    _targets: { value: null },
    targets:
    {
    	get: function () {
    		return this._targets;
    	},
    	set: function (value) {
    		this._target = value;
    		if (value === null)
            {
    			return;
    		}
    	}
    },

    _undoArray: { value: [] },

    _initProps3D:
    {
        value: function(elt)
        {
            if (elt.elementModel && elt.elementModel.props3D)
            {
                // do nothing
            }
            else
            {
                console.warn("no props3D model");
//                elt.props3D = Object.create(Properties3D, {});
//                elt.props3D.Init(elt);
            }
        }
    },

    _updateTargets: {
        value: function(addToUndoStack) {
            // override in subclasses
        }
    },
    //-------------------------------------------------------------------------
    
    //-------------------------------------------------------------------------
    // Routines to run when tool is selected/deselected
    Configure: {
        value: function(wasSelected) {
            if(wasSelected) {
                this.eventManager.addEventListener("selectionChange", this, true);
                this.application.ninja.stage._iframeContainer.addEventListener("scroll", this, false);
                this._initializeToolHandles();
                this.captureSelectionDrawn(null);
                this.eventManager.addEventListener( "toolOptionsChange", this, false);
                this.eventManager.addEventListener( "toolDoubleClick", this, false);
                NJevent("enableStageMove");
            } else {
                this.eventManager.removeEventListener("selectionChange", this, true);
                this.application.ninja.stage._iframeContainer.removeEventListener("scroll", this, false);
                this._targets = null;

                // Clean up
                NJevent("disableStageMove");

                // clear the drag plane
                snapManager.clearDragPlane();

                // restore the snap settings
                snapManager.enableSnapAlign( snapManager.snapAlignEnabledAppLevel() );
                snapManager.enableElementSnap( snapManager.elementSnapEnabledAppLevel() );
                snapManager.enableGridSnap( snapManager.gridSnapEnabledAppLevel() );
                this.eventManager.removeEventListener( "toolOptionsChange", this, false);
                this.eventManager.removeEventListener( "toolDoubleClick", this, false);
            }
        }
    },

    captureSelectionChange: {
		value: function(event){
			this.eventManager.addEventListener("selectionDrawn", this, true);
		}
	},

	captureSelectionDrawn: {
		value: function(event){
			this._targets = null;

			var len = this.application.ninja.selectedElements.length;
			if(len)
			{
				if(len === 1)
				{
					this.target = this.application.ninja.selectedElements[0]._element;
					drawUtils.addElement(this.target);
				}
				else
				{
					this.target = this.application.ninja.currentDocument.documentRoot;
				}
				this._updateTargets();
			}
			else
			{
				this.target = null;
			}
			this.DrawHandles();

			if(event)
			{
                this.eventManager.removeEventListener("selectionDrawn", this, true);
			}
		}
	},
    //-------------------------------------------------------------------------

    //-------------------------------------------------------------------------
    // User interaction routines
    HandleLeftButtonDown: {
        value: function(event) {

            var point = webkitConvertPointFromPageToNode(this.application.ninja.stage.canvas,
                                                        new WebKitPoint(event.pageX, event.pageY));
            this.downPoint.x = point.x;
            this.downPoint.y = point.y;

            if(this._handleMode === null)
            {
                // TODO - Special casing the double-click workflow for reseting tool handle's center for now.
                if(!this._activateOriginHandle)
                {
                    this.application.ninja.stage.drawNow = true;
                    this.doSelection(event);
                }
            }

            this.startDraw(event);
        }
    },

    HandleMouseMove: {
        value: function(event) {
            if(this._escape) {
                this._escape = false;
                this.isDrawing = true;
            }

            if(this.isDrawing) {
                this._hasDraw = true;   // Flag for position of element
                this.doDraw(event);
            } else {
                this._showFeedbackOnMouseMove(event);
//                if(this._canSnap)
//                {
//                    this.doSnap(event);
//                }
            }

            this.DrawHandles(this._delta);
            if(this._canSnap && this._isDrawing)
            {
                snapManager.drawLastHit();
            }
        }
    },

    HandleLeftButtonUp: {
        value: function(event) {
            var point = webkitConvertPointFromPageToNode(this.application.ninja.stage.canvas,
                                                                    new WebKitPoint(event.pageX, event.pageY));

            this.isDrawing = false;
            this.application.ninja.stage.showSelectionBounds = true;
            if(this._escape) {
                this._escape = false;
                return;
            }

            if(this._hasDraw)
            {
                if(this._activateOriginHandle)
                {
                    this._setTransformOrigin(true);
                }
                else if ( ((this.downPoint.x - point.x) !== 0) ||
                            ((this.downPoint.y - point.y) !== 0) )
                {
                    this._updateTargets(true);
                }

                this.endDraw(event);
                this._hasDraw = false;
            }
            if(this._handleMode !== null)
            {
                this._handleMode = null;
                this._delta = null;
                this.DrawHandles();
            }
        }
    },

    handleToolDoubleClick: {
        value: function(event) {
            if(!this._target)
            {
                return;
            }

            this.Reset();
        }
    },

    Reset : {
        value: function () {
            // override in sub-classes
        }
    },

    handleToolOptionsChange: {
        value: function(event) {
            this._handleToolOptionsChange(event);
        }
    },

    _handleToolOptionsChange : {
        value: function (event) {
            // override in sub-classes
        }
    },

    /**
   	 * This function is for specifying custom feedback routine
   	 * upon mouse over.
   	 * For example, the drawing tools will add a glow when mousing
   	 * over existing canvas elements to signal to the user that
   	 * the drawing operation will act on the targeted canvas.
   	 */
   	_showFeedbackOnMouseMove : {
   		value: function (event) {
   			if(this._target && this._handles)
   			{
   				var len = this._handles.length;
   				var i = 0,
   					toolHandle,
   					c,
                    point = webkitConvertPointFromPageToNode(this.application.ninja.stage.canvas,
                                                            new WebKitPoint(event.pageX, event.pageY));
   				for (i=0; i<len; i++)
   				{
   					toolHandle = this._handles[i];
   					c = toolHandle.collidesWithPoint(point.x, point.y);
   					if(c === 1)
   					{
   						this.application.ninja.stage.drawingCanvas.style.cursor = "move";
   						this._handleMode = i;
   						return;
   					}
   					else if(c === 2)
   					{
   						this.application.ninja.stage.drawingCanvas.style.cursor = toolHandle._cursor;
   						this._handleMode = i;
   						return;
   					}
   				}
   			}

   			this._handleMode = null;
   //            this.application.ninja.stage.drawingCanvas.style.cursor = this._cursor;
   			this.application.ninja.stage.drawingCanvas.style.cursor = "auto";
   		}
   	},
    //-------------------------------------------------------------------------

    HandleKeyPress: {
        value : function (event) {
            var point = webkitConvertPointFromPageToNode(this.application.ninja.stage.canvas,
                                                        new WebKitPoint(event.pageX, event.pageY));
            if(event.ctrlKey || event.metaKey)
            {
                // set 'z-translation' mode
                this._mode = 1;

                if(this._mouseDownHitRec && this._target)
                {
                    if ( ((this.downPoint.x - point.x) !== 0) ||
                          ((this.downPoint.y - point.y) !== 0) )
                    {
                        this._updateTargets();

                        this.downPoint.x = point.x;
                        this.downPoint.y = point.y;
                    }
                }
            }
        }
    },

    HandleKeyUp: {
        value: function(event) {
            var point = webkitConvertPointFromPageToNode(this.application.ninja.stage.canvas,
                                                        new WebKitPoint(event.pageX, event.pageY));
            if(event.keyCode === 93 || event.keyCode === 91 || event.keyCode === 17)
            {
                // set 'x/y-translation' mode
                this._mode = 0;

                if(this._mouseDownHitRec && this._target)
                {
                    if ( ((this.downPoint.x - point.x) !== 0) ||
                          ((this.downPoint.y - point.y) !== 0) )
                    {
                        this._updateTargets();

                        this.downPoint.x = point.x;
                        this.downPoint.y = point.y;
                    }
                }
            }
        }
    },

	_updateDelta: {
		value: function(delta, handleMode){
			if(this._clickedObject !== this.application.ninja.currentDocument.documentRoot)
			{
				this._delta += ~~(delta[handleMode]);
			}
			else
			{
				this._delta = ~~(delta[handleMode]);
			}
		}
	},

    modifyElements: {
        value: function(data, event) {
            // override in subclasses.
        }
    }
    //-------------------------------------------------------------------------
});