/* <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> */

///////////////////////////////////////////////////////////////////////
// Class StageLine
//      The line class represents a line intersected with all planes on the scene
///////////////////////////////////////////////////////////////////////
var vecUtils = require("js/helper-classes/3D/vec-utils").VecUtils;
var LinePlaneIntersectRec = require("js/helper-classes/3D/LinePlaneIntersectRec").LinePlaneIntersectRec;

var StageLine = exports.StageLine = Object.create(Object.prototype, {
    ///////////////////////////////////////////////////////////////////////
    // Instance variables
    ///////////////////////////////////////////////////////////////////////

    // the 2 points of the line
    _pt0: { value: null, writable: true },
    _pt1: { value: null, writable: true },

    // cache the 3D min and max points for the line
    _minPt: { value: null, writable: true },
    _maxPt: { value: null, writable: true },

    // the visibility at the start point (this._pt0).
    _vis: { value: null, writable: true },

    // each line/plane intersection records 2 values:  the parameter along
    // the line from pt0 to pt1, and the change in visibility (+1 or -1).  we
    // keep a doubly-linked list of intersection records
    _intersectionList: { value: null, writable: true },
    _intersectionCount: { value: 0, writable: true },

    ///////////////////////////////////////////////////////////////////////
    // Property accessors
    ///////////////////////////////////////////////////////////////////////
    getMinPoint: { value: function()    {  return this._minPt.slice(0);     } },
    getMaxPoint: { value: function()    {  return this._maxPt.slice(0);        } },

    getPoint0: { value: function()    {  return this._pt0.slice(0);          } },
    getPoint1: { value: function()    {  return this._pt1.slice(0);          } },

    getIntersectionCount: { value: function()    {  return this._intersectionCount;  } },
    getIntersectionList: { value: function()    {  return this._intersectionList;   } },

    getVisibility: { value: function()    { return this._vis;                 } },
    setVisibility: { value: function(v)   {  this._vis = v;                   } },

    ///////////////////////////////////////////////////////////////////////
    // Methods
    ///////////////////////////////////////////////////////////////////////

    intersectWithPlane: {
        value: function( plane )
        {
            // if the plane is edge-on, ignore it
            if ( MathUtils.fpSign( plane.getPlaneEq()[2] ) == 0 )  return;

            // do some quick box tests.
            var minPt = this.getMinPoint(),
                    maxPt = this.getMaxPoint();

            if (maxPt[0] < plane._rect.getLeft())     return;
            if (minPt[0] > plane._rect.getRight())    return;

            if (maxPt[1] < plane._rect.getTop())      return;
            if (minPt[1] > plane._rect.getBottom())   return;

            if (minPt[2] > plane.getZMax())           return;

            // get the boundary points for the plane
            var boundaryPts = plane.getBoundaryPoints();

            // get the points and direction vector for the current line
            var pt0 = this.getPoint0(),  pt1 = this.getPoint1();
            //var lineDir = pt1.subtract( pt0 );
            var lineDir = vecUtils.vecSubtract(3, pt1, pt0);

            // intersect with the front plane
            var planeEq = plane.getPlaneEq();
            var t = MathUtils.vecIntersectPlaneForParam( pt0, lineDir, planeEq );
            if (t != undefined)
            {
                if ((MathUtils.fpSign(t) >= 0) && (MathUtils.fpCmp(t,1.0) <= 0))
                {
                    // get the intersection point
                    var pt = MathUtils.interpolateLine3D( pt0, pt1, t );

                    // see if the intersection point is contained in the bounds
                    //var contains = this.boundaryContainsPoint( boundaryPts, plane.isBackFacing(), pt );
                    var contains = MathUtils.boundaryContainsPoint( boundaryPts, pt, plane.isBackFacing() );
                    if (contains == MathUtils.INSIDE)
                    {
                        // add the intersection
                        var dot = MathUtils.dot3( pt0, planeEq ) + planeEq[3];
                        var deltaVis = (dot > 0) ? 1 : -1;
//					if (plane.isBackFacing())
//                        deltaVis = (dot < 0) ? 1 : -1;

                        this.addIntersection( plane, t, deltaVis );
                    }
                    else if (contains == MathUtils.ON)
                    {
                        if (MathUtils.fpCmp(t,1.0) < 0)
                        {
                            // take the dot product between the line and the normal to the plane
                            // to determine the change in visibility
                            var vec = vecUtils.vecSubtract( 3, pt1, pt0 );
                            var dot = vecUtils.vecDot( 3, vec, plane.getPlaneEq() );
                            var sign = MathUtils.fpSign( dot );
                            if (sign == 0)
                                throw new Error( "coplanar intersection being treated as not coplanar" );
                            if (!plane.isBackFacing())
                            {
                                if (sign < 0)
                                    this.addIntersection( plane, t, 1 );
                            }
                            else
                            {
                                if (sign > 0)
                                    this.addIntersection( plane, t, -1 );
                            }
                        }
                    }
                }
            }
            else
            {
                // the line must be parallel to the plane.  If the line is in the plane,
                // we need to do some special processing
                var d0 = vecUtils.vecDot(3, planeEq, pt0) + planeEq[3],
                        d1 = vecUtils.vecDot(3, planeEq, pt1) + planeEq[3];
                if ((MathUtils.fpSign(d0) == 0) && (MathUtils.fpSign(d1) == 0))
                    this.doCoplanarIntersection( plane );
            }

            // intersect with the 4 planes formed by the edges of the plane, going back in Z
            var bPt1 = boundaryPts[3];
            for (var i=0;  i<4;  i++)
            {
                // get the 2 points that define the front edge of the plane
                var bPt0 = bPt1;
                var bPt1 = boundaryPts[i];

                // calculate the plane equation.  The normal should point towards the OUTSIDE of the boundary
                //var vec = bPt1.subtract( bPt0 );
                var vec = vecUtils.vecSubtract(3, bPt1, bPt0);
                if (plane.isBackFacing())
                    MathUtils.negate( vec );
                planeEq = [-vec[1], vec[0], 0];
                var normal = [planeEq[0], planeEq[1], planeEq[2]];
//			var d = -planeEq.dot(bPt0);
                var d = -vecUtils.vecDot(3, planeEq, bPt0);
                planeEq[3] = d;

                t = MathUtils.vecIntersectPlaneForParam( pt0, lineDir, planeEq );
                if (t)
                {
                    if ((MathUtils.fpSign(t) > 0) && (MathUtils.fpCmp(t,1.0) <= 0))	// the strict vs not-strict inequality comparisons are IMPORTANT!
                    {
                        // get the intersection point
                        var pt = MathUtils.interpolateLine3D( pt0, pt1, t );

                        // we need to get the parameter on the edge of the projection
                        // of the intersection point onto the line.
                        var index = (Math.abs(vec[0]) > Math.abs(vec[1])) ? 0 : 1;
                        var tEdge = (pt[index] - bPt0[index])/(bPt1[index] - bPt0[index]);
                        if ((MathUtils.fpSign(tEdge) > 0) && (MathUtils.fpCmp(tEdge,1.0) <= 0))
                        {
                            var edgePt = MathUtils.interpolateLine3D( bPt0, bPt1, tEdge );
                            if (MathUtils.fpCmp(pt[2],edgePt[2]) < 0)
                            {
                                // add the intersection
                                var deltaVis = MathUtils.dot(lineDir,normal) > 0 ? -1 : 1;
                                this.addIntersection( plane, t, deltaVis );
                            }
                        }
                    }
                }
            }
        }
    },

	doCoplanarIntersection: {
        value: function( plane )
        {
            // get the boundary points for the plane
            var boundaryPts = plane.getBoundaryPoints();
            var planeEq = plane.getPlaneEq();

            if (plane.isBackFacing())
            {
                var tmp;
                tmp = boundaryPts[0];  boundaryPts[0] = boundaryPts[3];  boundaryPts[3] = tmp;
                tmp = boundaryPts[1];  boundaryPts[1] = boundaryPts[2];  boundaryPts[2] = tmp;
            }

            var pt0 = this.getPoint0(),
                    pt1 = this.getPoint1();

            // keep a couple flags to prevent counting crossings twice in edge cases
            var gotEnter = false,
                    gotExit = false;

            var bp1 = boundaryPts[3];
            for (var i=0;  i<4;  i++)
            {
                var bp0 = bp1;
                bp1 = boundaryPts[i];
                var vec = vecUtils.vecSubtract(3, bp1, bp0);
                var nrm = vecUtils.vecCross(3, vec, planeEq);
                nrm[3] = -vecUtils.vecDot(3, bp0, nrm);

                var d0 = vecUtils.vecDot(3, nrm, pt0) + nrm[3],
                        d1 = vecUtils.vecDot(3, nrm, pt1) + nrm[3];

                var s0 = MathUtils.fpSign(d0),
                        s1 = MathUtils.fpSign(d1);

                if (s0 != s1)
                {
                    var t = Math.abs(d0)/( Math.abs(d0) + Math.abs(d1) );
                    if ( (MathUtils.fpSign(t) >= 0) && (MathUtils.fpCmp(t,1.0) <= 0))
                    {
                        // get the point where the line crosses the edge of the element plane
                        var pt = MathUtils.interpolateLine3D(pt0, pt1, t );

                        // we know that the line crosses the infinite extension of the edge.  Determine
                        // if that crossing is within the bounds of the edge
                        var dot0 = vecUtils.vecDot(3, vecUtils.vecSubtract(3,pt, bp0),  vec),
                                dot1 = vecUtils.vecDot(3, vecUtils.vecSubtract(3,pt, bp1),  vec);
                        if ((MathUtils.fpSign(dot0) > 0) && (MathUtils.fpSign(dot1) < 0))
                        {
                            // determine if the line is entering or exiting
                            if (s0 <= 0)		// entering
                            {
                                if (!gotEnter)
                                {
                                    gotEnter = true;
                                    this.addIntersection( plane, t, 1 );
                                }
                            }
                            else if (s0 > 0) // exiting
                            {
                                if (!gotExit)
                                {
                                    gotExit = true;
                                    this.addIntersection( plane, t, -1 );
                                }
                            }
                            else	// s0 == 0
                            {
                                // TODO
                            }
                        }
                        else if ((MathUtils.fpSign(dot0) == 0) && (MathUtils.fpSign(dot1) < 0))
                        {
                            var j = i - 2;
                            if (j < 0)  j += 4;
                            var bp = boundaryPts[j];

                            var v0 = vecUtils.vecSubtract( 3, bp, bp0 ),
                                    v1 = vec;

                            if (s0 <= 0)
                            {
                                var v = vecUtils.vecSubtract(3, pt1, pt0);
                                if ((MathUtils.fpSign(vecUtils.vecCross(3, v0,v)) > 0) && (MathUtils.fpSign(vecUtils.vecCross(3, v,v1)) > 0))
                                {
                                    gotEnter = true;
                                    this.addIntersection( plane, t, 1 );
                                }
                            }
                            else if (s0 > 0)
                            {
                                var v = vecUtils.vecSubtract(3, pt0, pt1);	// note the reversed order from the previous case
                                if ((MathUtils.fpSign(vecUtils.vecCross(3, v0,v)) > 0) && (MathUtils.fpSign(vecUtils.vecCross(3, v,v1)) > 0))
                                {
                                    gotEnter = true;
                                    this.addIntersection( plane, t, -1 );
                                }
                            }
                        }
                        else if ((MathUtils.fpSign(dot0) > 0) && (MathUtils.fpSign(dot1) == 0))
                        {
                            var j = (i + 1) % 4;
                            var bp = boundaryPts[j];

                            var v1 = vec.slice(0),
                                    v0 = vecUtils.vecSubtract( 3, bp, bp1 ),
                                    v1 = vecUtils.vecNegate(3, v1);

                            if (s0 <= 0)
                            {
                                var v = vecUtils.vecSubtract(3, pt1, pt0);
                                if ((MathUtils.fpSign(vecUtils.vecCross(3, v0,v)) < 0) && (MathUtils.fpSign(vecUtils.vecCross(3, v,v1)) < 0))
                                {
                                    gotEnter = true;
                                    this.addIntersection( plane, t, 1 );
                                }
                            }
                            else if (s0 > 0)
                            {
                                var v = vecUtils.vecSubtract(3, pt0, pt1);	// note the reversed order from the previous case
                                if ((MathUtils.fpSign(vecUtils.vecCross(3, v0,v)) > 0) && (MathUtils.fpSign(vecUtils.vecCross(3, v,v1)) > 0))
                                {
                                    gotEnter = true;
                                    this.addIntersection( plane, t, -1 );
                                }
                            }
                        }
                    }
                }
            }
        }
    },

    removeIntersections: {
        value: function()
        {
            this._intersectionList = null;
            this._intersectionCount = 0;
        }
    },

    addIntersection: {
        value: function( plane, t,  deltaVis )
        {
            // create the intersection record
            var iRec = Object.create(LinePlaneIntersectRec, {});
            iRec.setStageLine( this );
            iRec.setElementPlanes( plane );
            iRec.setT( t );
            iRec.setDeltaVis( deltaVis );

            // the intersection array needs to be sorted by t
            var ptr = this._intersectionList;
            var last = null;
            while (ptr && (t > ptr.getT()))
            {
                last = ptr;
                ptr = ptr.getNext();
            }
            if (ptr == null)
            {
                if (last == null)
                    this._intersectionList = iRec;
                else
                {
                    last.setNext( iRec );
                    iRec.setPrev( last );
                }
            }
            else
            {
                if (last != null)
                {
                    last.setNext( iRec );
                    iRec.setPrev( last );
                }
                else
                    this._intersectionList = iRec;

                ptr.setPrev( iRec );
                iRec.setNext( ptr );
            }

            this._intersectionCount++;
        }
    },

	boundaryContainsPoint: {
        value: function( boundaryPts, backFacing, pt )
        {
            // the computation is done in 2D.
            // this method returns false if the point is 'on' or outside the boundary

            var pt1 = boundaryPts[3];
            for (var i=0;  i<4;  i++)
            {
                var pt0 = pt1;
                var pt1 = boundaryPts[i];
                //var	vec0 = pt1.subtract( pt0 ),
                //	vec1 =  pt.subtract( pt0 );
                var vec0 = vecUtils.vecSubtract(3, pt1, pt0),
                    vec1 = vecUtils.vecSubtract(pt, pt0);

    //			var cross = vec0.cross( vec1 );
                var cross = vecUtils.vecCross(3, vec0, vec1);
                var inside;
                if (backFacing)
                    inside = (MathUtils.fpSign(cross[2]) > 0);
                else
                    inside = (MathUtils.fpSign(cross[2]) < 0);

                if (!inside)  return false;
            }

            return true;
	    }
    },

    setPoints: {
        value: function( pt0, pt1 )
        {
            this._pt0 = pt0.slice(0);
            this._pt1 = pt1.slice(0);

            // get the 3D bounds
            var  xMin, xMax,  yMin, yMax,  zMin, zMax;
            if (pt0[0] < pt1[0])  {  xMin = pt0[0];  xMax = pt1[0];  }  else  {  xMin = pt1[0];  xMax = pt0[0];  }
            if (pt0[1] < pt1[1])  {  yMin = pt0[1];  yMax = pt1[1];  }  else  {  yMin = pt1[1];  yMax = pt0[1];  }
            if (pt0[2] < pt1[2])  {  zMin = pt0[2];  zMax = pt1[2];  }  else  {  zMin = pt1[2];  zMax = pt0[2];  }

            this._minPt = [xMin, yMin, zMin];
            this._maxPt = [xMax, yMax, zMax];
        }
    }//,

//    getIntersectionParameter: {
//        value: function( index )
//        {
//            var tRtn;
//            if (this._paramArray)
//            {
//                if ((index >= 0) && (index < this._intersectionCount))
//                {
//                    var count = 0;
//                    var iRec = this._intersectionList;
//                    while (count < index)
//                        iRec = iRec.getNext();
//                    tRtn = iRec.getT();
//                }
//            }
//
//            return tRtn;
//        }
//    },
//
//    getVisibilityChange: {
//        value: function( index )
//        {
//            var delta;
//            if ((index >= 0) && (index < this._intersectionCount))
//            {
//                var count = 0;
//                var iRec = this._intersectionList;
//                while (count < index)
//                    iRec = iRec.getNext();
//                delta = iRec.getDeltaVis();
//            }
//
//            return delta;
//        }
//    }

});