/* <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,
    Component = require("montage/ui/component").Component,
    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;

var ToolHandle = exports.ToolHandle = Montage.create(Component, {
    _x: {
        value: 0,
        writable: true
    },

    _y: {
        value: 0,
        writable: true
    },

    _width: {
        value: 4,
        writable: true
    },

    _height: {
        value: 4,
        writable: true
    },

    VERTEX_HIT_RAD: {
        value: 4,
        writable: true
    },

    _cursor: {
        value: "default",
        writable: true
    },

    _strokeStyle: {
        value: 'rgba(255,255,255,0.6)'
    },

    _fillStyle: {
        value: 'rgba(0,0,0,1)'
    },

    init: {
        value: function (cursorStyle) {
            this._cursor = cursorStyle;
        }
    },

    draw: {
        value: function(x, y) {
            var context = this.application.ninja.stage.drawingContext;
            context.save();

            context.fillStyle = this._fillStyle;
            this._x = x - this._width/2;
            this._y = y - this._height/2;

            context.fillRect(this._x, this._y, this._width, this._height);

            context.strokeStyle = this._strokeStyle;
            this._x = x - this._width/2 - 1;
            this._y = y - this._height/2 - 1;

            context.strokeRect(this._x, this._y, this._width + 2, this._height + 2);

            context.restore();

        }
    },

    collidesWithPoint: {
        value:function (x, y) {
            if(x < (this._x - this.VERTEX_HIT_RAD)) return false;
            if(x > (this._x + this._width + this.VERTEX_HIT_RAD)) return false;
            if(y < this._y - this.VERTEX_HIT_RAD) return false;
            if(y > (this._y + this._height + this.VERTEX_HIT_RAD)) return false;

            return true;
        }
    }
    
});


exports.RotateHandle = Montage.create(ToolHandle, {
    _originL: {
        value: null,
        writable: true
    },

    _origin: {
        value: null,
        writable: true
    },


    _dirVec: {
        value: null,
        writable: true
    },

    _dirVecL: {
        value: null,
        writable: true
    },

    _radius: {
        value: 50,
        writable: true
    },

    _transformCenterRadius: {
        value: 5,
        writable: true
    },

    _cursor: {
        value: "default",
        writable: true
    },

    _strokeStyle: {
        value: 'rgba(255,0,255,1)'
    },

    _axis: {
        value: null,
        writable: true
    },

    _lineWidth: {
        value: 2
    },

    _fillStyle: {
        value: 'rgba(255,0,255,1)'
    },

    _nTriangles: {
        value: 30,
        writable: true
    },

    _rotMat: {
        value: null,
        writable: true
    },

    _rotMatInv: {
        value: null,
        writable: true
    },

    _planeEq: {
        value: null,
        writable: true
    },

    _matW: {
        value: null,
        writable: true
    },

    _matL: {
        value: null,
        writable: true
    },

    _vec: {
        value: null,
        writable: true
    },

    _vec2: {
        value: null,
        writable: true
    },

    _vec3: {
        value: null,
        writable: true
    },

    _dragPlane: {
        value: null,
        writable: true
    },

    init: {
        value: function (cursorStyle, color, axis) {
            this._cursor = cursorStyle;
            this._strokeStyle = color;
            this._fillStyle = color;
            this._axis = axis;
            switch(this._axis)
            {
                case "x":
                    this._vec = Vector.create([1, 0, 0]);
                    this._vec2 = Vector.create([0, 1, 0]);
                    this._vec3 = Vector.create([0, 0, 1]);
                    break;
                case "y":
                    this._vec = Vector.create([0, 1, 0]);
                    this._vec2 = Vector.create([1, 0, 0]);
                    this._vec3 = Vector.create([0, 0, 1]);
                    break;
                case "z":
                    this._vec = Vector.create([0, 0, 1]);
                    this._vec2 = Vector.create([1, 0, 0]);
                    this._vec3 = Vector.create([0, 1, 0]);
                    break;
            }

            // get a matrix to rotate a point around the circle
            var angle = 2.0*Math.PI/Number(this._nTriangles);
            this._rotMat = Matrix.RotationZ( angle );
            this._rotMatInv = glmat4.inverse(this._rotMat, []);
        }
    },

    draw: {
        value: function(base, item, inLocalMode) {
            var context = this.application.ninja.stage.drawingContext;
            context.save();

            context.strokeStyle = this._strokeStyle;
            context.fillStyle = this._fillStyle;
            context.lineWidth = this._lineWidth;

            var pointOnElt = base.slice(0);
//            this._origin = viewUtils.localToGlobal(pointOnElt, item);
            this._origin = pointOnElt;


            var viewMat = viewUtils.getMatrixFromElement(this.application.ninja.currentDocument.documentRoot);

            var transMat = viewMat.slice(0);
            if(inLocalMode)
            {
                var objMat = viewUtils.getMatrixFromElement(item);
                glmat4.multiply(viewMat, objMat, transMat);
            }

            this._planeEq = MathUtils.transformVector(this._vec, transMat);
            this._planeEq2 = MathUtils.transformVector(this._vec2, transMat);
            this._planeEq3 = MathUtils.transformVector(this._vec3, transMat);

            var viewVec = Vector.create([0, 0, 1]);

            var angle2 = MathUtils.getAngleBetweenVectors(this._planeEq2, viewVec);
            var angle3 = MathUtils.getAngleBetweenVectors(this._planeEq3, viewVec);

            if(angle3 < angle2)
            {
                this._dirVec = vecUtils.vecNormalize(3, this._planeEq2, this._radius);
            }
            else
            {
                this._dirVec = vecUtils.vecNormalize(3, this._planeEq3, this._radius);
            }

            this._matW = drawUtils.getPlaneToWorldMatrix(this._planeEq, this._origin);
            this._matL = glmat4.inverse(this._matW, []);

            this._originL = MathUtils.transformPoint(this._origin, this._matL);

            this._planeEq[3] = -vecUtils.vecDot(3, this._planeEq, this._origin);

            this._dirVecL = MathUtils.transformPoint(this._dirVec, this._matL);
            
            context.beginPath();

            var pt = Vector.create( [this._radius, 0.0, 0.0] );
            var pts;

            for (var i=0;  i<this._nTriangles;  i++)
            {
                pt = MathUtils.transformPoint(pt, this._rotMat);
                pts = MathUtils.transformPoint(pt, this._matW);
                context.lineTo(pts[0], pts[1]);
            }

            context.closePath();
            context.stroke();



            // Draw the transform handle
            context.beginPath();
            
            pt = Vector.create( [this._transformCenterRadius, 0.0, 0.0] );

            for (var i=0;  i<this._nTriangles;  i++)
            {
                pt = MathUtils.transformPoint(pt, this._rotMat);
                pts = MathUtils.transformPoint(pt, this._matW);
                context.lineTo(pts[0], pts[1]);
            }

            context.closePath();
            context.stroke();


            
            context.restore();
        }
    },

    collidesWithPoint: {
        value:function (x, y) {
            var globalPt = Vector.create( [x, y, 0] );
            var vec = Vector.create( [0,0,1] );

            // if angle between view direction and the handle's plane is within 5 degrees, use line test instead
            var angle = MathUtils.getAngleBetweenVectors(vec, this._planeEq);
            angle = Math.abs(90 - angle * 180 / Math.PI);

            var localPt = MathUtils.vecIntersectPlane(globalPt, vec, this._planeEq);

            // If rotate handle's plane is straight on to the screen, special case and use line test instead.
            // For now, just return false;
            if(!localPt || (angle < 5))
            {
                var nearPt = MathUtils.nearestPointOnLine2D( this._origin, this._dirVec,  globalPt );
                if(!nearPt)
                {
                    return 0;
                }

                var t = MathUtils.parameterizePointOnLine2D( this._origin, this._dirVec, nearPt );
                if(angle !== 0)
                {
                    var theta = MathUtils.getAngleBetweenVectors(this._dirVec, Vector.create([50, 0, 0]));
                    t = t * Math.cos(theta);
                }

                var dist = vecUtils.vecDist( 2, globalPt, nearPt );
                if (dist <= 5)
                {
                    if( (t <= 0.1) && (t >= -0.1) )
                    {
                        return 1;
                    }
                    else if ( (t <= 1) && (t >= -1) )
                    {
                        return 2;
                    }
                }

                return 0;
            }
            
            localPt = MathUtils.transformPoint(localPt, this._matL);

            var theta = Math.atan2(localPt[1], localPt[0]);
            var xC = this._transformCenterRadius*Math.cos(theta);
            var yC = this._transformCenterRadius*Math.sin(theta);
            var ptOnCircle = Vector.create([xC, yC, 0]);

            var dist = vecUtils.vecDist( 2, localPt, ptOnCircle );

            if ( dist <= 5 )
            {
                return 1;
            }
            
            xC = this._radius*Math.cos(theta);
            yC = this._radius*Math.sin(theta);
            ptOnCircle = Vector.create([xC, yC, 0]);

            dist = vecUtils.vecDist( 2, localPt, ptOnCircle );
            
            if ( dist <= 5 )
            {
                return 2;
            }

            return 0;
        }
    },

    
    drawShadedAngle: {
        value: function(angle, localPt) {

            var theta = Math.atan2(localPt[1], localPt[0]);
            var xC = this._radius*Math.cos(theta);
            var yC = this._radius*Math.sin(theta);
            var pt = Vector.create([xC, yC, 0]);

            var context = this.application.ninja.stage.drawingContext;
            context.save();

            context.strokeStyle = this._strokeStyle;
            context.fillStyle = this._fillStyle;
            context.globalAlpha = 0.2;

            context.beginPath();

            context.moveTo(this._origin[0], this._origin[1]);

            var pts = MathUtils.transformPoint(pt, this._matW);
            context.lineTo(pts[0], pts[1]);

            var n = Math.ceil(Math.abs( (this._nTriangles*angle) / (2*Math.PI) ) );

            for (var i=0;  i<n;  i++)
            {
                pt = MathUtils.transformVector(pt, this._rotMat);
                pts = MathUtils.transformPoint(pt, this._matW);
                context.lineTo(pts[0], pts[1]);
            }

            context.lineTo(this._origin[0], this._origin[1]);
            
            context.stroke();
            context.fill();

            context.font = "10px sans-serif"; // TODO - Make this a global app preference
            var dirV = vecUtils.vecSubtract(2, pts, this._origin);
            dirV = vecUtils.vecNormalize(2, dirV, 70);
            dirV = vecUtils.vecAdd(2, this._origin, dirV);
            context.globalAlpha = 1.0;
            context.fillStyle = "rgba(0,0,0,1)";
            var deg = Math.round( (angle*180/Math.PI) % 360 );
            context.fillText(deg + "" + "\u00B0", dirV[0], dirV[1]);

            context.closePath();

            context.restore();
        }
    }
});


exports.TranslateHandle = Montage.create(ToolHandle, {
    _originL: {
        value: null,
        writable: true
    },

    _origin: {
        value: null,
        writable: true
    },

    _endPt: {
        value: null,
        writable: true
    },

    _dirVec: {
        value: null,
        writable: true
    },

    _dirVecL: {
        value: null,
        writable: true
    },

    _arrowSize: {
        value: 50,
        writable: true
    },

    _arrowHead: {
        value: 6,
        writable: true
    },

    _cursor: {
        value: "default",
        writable: true
    },

    _strokeStyle: {
        value: 'rgba(255,0,255,1)'
    },

    _axis: {
        value: null,
        writable: true
    },

    _lineWidth: {
        value: 2
    },

    _fillStyle: {
        value: 'rgba(255,0,255,1)'
    },

    _nTriangles: {
        value: 30
    },

    _planeEq: {
        value: null,
        writable: true
    },

    _matW: {
        value: null,
        writable: true
    },

    _matL: {
        value: null,
        writable: true
    },

    _vec: {
        value: null,
        writable: true
    },

    _vec2: {
        value: null,
        writable: true
    },

    _vec3: {
        value: null,
        writable: true
    },

    _dragPlane: {
        value: null,
        writable: true
    },

    init: {
        value: function (cursorStyle, color, axis) {
            this._cursor = cursorStyle;
            this._strokeStyle = color;
            this._fillStyle = color;
            this._axis = axis;
            switch(this._axis)
            {
                case "x":
                    this._vec = Vector.create([1, 0, 0]);
                    this._vec2 = Vector.create([0, 1, 0]);
                    this._vec3 = Vector.create([0, 0, 1]);
                    break;
                case "y":
                    this._vec = Vector.create([0, -1, 0]);
                    this._vec2 = Vector.create([1, 0, 0]);
                    this._vec3 = Vector.create([0, 0, 1]);
                    break;
                case "z":
                    this._vec = Vector.create([0, 0, 1]);
                    this._vec2 = Vector.create([1, 0, 0]);
                    this._vec3 = Vector.create([0, 1, 0]);
                    break;
            }
        }
    },

    draw: {
        value: function(base, item, inLocalMode) {
            var context = this.application.ninja.stage.drawingContext;
            context.save();

            context.strokeStyle = this._strokeStyle;
            context.fillStyle = this._fillStyle;
            context.lineWidth = this._lineWidth;

            var pointOnElt = base.slice(0);
//            this._origin = viewUtils.localToGlobal(pointOnElt, item);
            this._origin = pointOnElt;


            var viewMat = viewUtils.getMatrixFromElement(this.application.ninja.currentDocument.documentRoot);

            var transMat = viewMat.slice(0);
            if(inLocalMode)
            {
                var objMat = viewUtils.getMatrixFromElement(item);
                glmat4.multiply(viewMat, objMat, transMat);
            }

            this._planeEq2 = MathUtils.transformVector(this._vec2, transMat);
            this._planeEq3 = MathUtils.transformVector(this._vec3, transMat);

            var viewVec = Vector.create([0, 0, 1]);

            var angle2 = MathUtils.getAngleBetweenVectors(this._planeEq2, viewVec);
            var angle3 = MathUtils.getAngleBetweenVectors(this._planeEq3, viewVec);

            if(angle3 < angle2)
            {
                this._planeEq = this._planeEq3;
            }
            else
            {
                this._planeEq = this._planeEq2;
            }

            this._matW = drawUtils.getPlaneToWorldMatrix(this._planeEq, this._origin);
            this._matL = glmat4.inverse(this._matW, []);

            this._originL = MathUtils.transformPoint(this._origin, this._matL);

            this._planeEq[3] = -vecUtils.vecDot(3, this._planeEq, this._origin);

            context.beginPath();

            var pt = Vector.create([0.0, 0.0, 0.0]);
            var pts = MathUtils.transformPoint(pt, this._matW);

            context.moveTo(pts[0], pts[1]);

            pt = vecUtils.vecNormalize(3, this._vec, this._arrowSize);
            pts = MathUtils.transformVector(pt, transMat);
            pts = vecUtils.vecAdd(3, pts, this._origin);

            this._endPt = pts.slice(0);
            this._dirVecL = MathUtils.transformPoint(this._endPt, this._matL);
            this._dirVec = vecUtils.vecSubtract(3, this._endPt, this._origin);

            context.lineTo(pts[0], pts[1]);
            context.closePath();
            context.stroke();

            this._drawArrowHead(this._origin, this._endPt, context);
            
            context.restore();
        }
    },

    _drawArrowHead: {
        value: function(base, onAxis, context) {
            var headWidth = this._arrowHead;

            // draw the arrowhead
            var head = MathUtils.interpolateLine3D( base, onAxis, 0.7 );
            var p0 = head.slice(0);  p0[1] += headWidth;
            var p1 = head.slice(0);  p1[0] += headWidth;
            var p2 = head.slice(0);  p2[1] -= headWidth;
            var p3 = head.slice(0);  p3[0] -= headWidth;


            context.beginPath();

            context.moveTo(base[0], base[1]);
            context.lineTo(onAxis[0], onAxis[1]);

            context.moveTo(onAxis[0], onAxis[1]);
            context.lineTo(p0[0], p0[1]);
            context.moveTo(onAxis[0], onAxis[1]);
            context.lineTo(p1[0], p1[1]);
            context.moveTo(onAxis[0], onAxis[1]);
            context.lineTo(p2[0], p2[1]);
            context.moveTo(onAxis[0], onAxis[1]);
            context.lineTo(p3[0], p3[1]);

            context.moveTo( p0[0], p0[1] );
            context.lineTo( p1[0], p1[1] );
            context.lineTo( p2[0], p2[1] );
            context.lineTo( p3[0], p3[1] );
            context.lineTo( p0[0], p0[1] );

            context.closePath();

            context.stroke();
        }
    },

    drawDelta: {
        value: function(delta) {
            var context = this.application.ninja.stage.drawingContext;
            context.save();

            context.beginPath();

            context.font = "10px sans-serif"; // TODO - Make this a global app preference
            context.fillStyle = "rgba(0,0,0,1)";
            context.fillText(delta + " px", this._endPt[0]+20, this._endPt[1]-20);

            context.closePath();

            context.restore();
        }
    },
    
    collidesWithPoint: 
	{
        value:function (x, y) 
		{
            var globalPt = Vector.create( [x, y, this._origin[2]] );

			// test for a hit on the origin
			var dist = vecUtils.vecDist( 2, globalPt, this._origin );
			if (dist <= 5)  return 1;

            var nearPt = MathUtils.nearestPointOnLine2D( this._origin, this._dirVec,  globalPt );
            if(!nearPt)
            {
                return 0;
            }
            
            var t = MathUtils.parameterizePointOnLine2D( this._origin, this._dirVec, nearPt );
            dist = vecUtils.vecDist( 2, globalPt, nearPt );
            if (dist <= 5)
            {
                if ( (t <= 1) && (t >= 0) )
                    return 2;
            }

            return 0;
        }
    }

});