package meyermike
{
    import flash.display.DisplayObject;
    import flash.geom.Point;
    import flash.geom.Rectangle;
    import meyermike.collision.CollisionInfo;

    /**
     * ...
     * @author Mike Meyer
     */
    public class Util
    {
        // the farthest (relative) distance we want to go between collision tests
        private static const COLLISION_STEP_SIZE_MAX:Number = 2;

        /**
         * return a random one of the things in an array
         */
        public static function RandomOneOf(a:Array):*
        {
            return a[int(Math.random() * a.length)];
        }
        
        /**
         * check for collisions between two moving GameObjects
         */
        public static function CheckAabbCollision(objectA:GameObject, objectB:GameObject, elapsedTime:Number):CollisionInfo
        {
            var objectABounds:Rectangle = objectA.GetCollisionBounds();
            var objectBBounds:Rectangle = objectB.GetCollisionBounds();

            return Util._InternalCheckAabbCollision(
                objectABounds, new Point(objectA.dx * elapsedTime, objectA.dy * elapsedTime),
                objectBBounds, new Point(objectB.dx * elapsedTime, objectB.dy * elapsedTime) );
        }
        
        /**
         * check for collisions between two potentially moving rects
         */
        private static function _InternalCheckAabbCollision(
            objectABounds:Rectangle,
            objectAMotion:Point,
            objectBBounds:Rectangle,
            objectBMotion:Point ):CollisionInfo
        {
            var dist:Number = objectAMotion.subtract(objectBMotion).length;
            var steps:Number = 1 + Math.floor(dist / COLLISION_STEP_SIZE_MAX);

            for (var step:Number = 0;  step <= steps; ++step)
            {
                var scale:Number = (step) / steps;
                var a:Rectangle = objectABounds.clone();
                a.offset(objectAMotion.x * scale, objectAMotion.y * scale);
                var b:Rectangle = objectBBounds.clone();
                b.offset(objectBMotion.x * scale, objectBMotion.y * scale);

                if (a.intersects(b))
                {
                    var ci:CollisionInfo = _CreateCollisionInfoForAabbCollision(a, b);
                    ci.t = step / steps;
                    return ci;
                }
            }

            return null;
        }

        /**
         * used internally by CheckAabbCollision
         */
        private static function _CreateCollisionInfoForAabbCollision(
            objectA:Rectangle,
            objectB:Rectangle):CollisionInfo
        {
            var a_center:Point = GetRectCenter(objectA);
            var b_center:Point = GetRectCenter(objectB);
            var offset:Point = a_center.subtract(b_center);

            // NOTE: positive and negative infinity compare
            // just like we would want them to when using
            // <, >, etc.  Which reduces the special-casing a bit

            var cutoffslope:Number = GetSlope( objectB.width, objectB.height);

            // NOTE the Abs here.  Mirorring it makes things simpler below
            var offsetslope:Number = GetSlope( Math.abs(offset.x), offset.y);

            var ci:CollisionInfo = new CollisionInfo();

            if ( offsetslope > cutoffslope )
            {
                // bottom
                ci.location.x = a_center.x;
                ci.location.y = b_center.y + (0.5 * objectB.height);
                ci.normal.x = 0.0;
                ci.normal.y = 1.0;
            }
            else if ( offsetslope < -cutoffslope)
            {
                // top
                ci.location.x = a_center.x;
                ci.location.y = b_center.y - (0.5 * objectB.height);
                ci.normal.x = 0.0;
                ci.normal.y = -1.0;
            }
            else if ( offset.x > 0.0 )
            {
                // right
                ci.location.x = b_center.x + (0.5 * objectB.width);
                ci.location.y = a_center.y;
                ci.normal.x = 1.0;
                ci.normal.y = 0.0;
            }
            else
            {
                // left
                ci.location.x = b_center.x - (0.5 * objectB.width);
                ci.location.y = a_center.y;
                ci.normal.x = -1.0;
                ci.normal.y = 0.0;
            }

            ci.t = 0.0; // needs to be set outside this function

            return ci;
        }

        /**
         * Do a linear interpolation between two values.  t in [0,1]
         */
        public static function Lerp(a:Number, b:Number, t:Number):Number
        {
            return a + (t*(b-a));
        }

        /**
         * given a rect, returns a new point telling you where its
         * center is
         */
        public static function GetRectCenter(rect:Rectangle):Point
        {
            return new Point(
                0.5*(rect.left+rect.right),
                0.5*(rect.top+rect.bottom) );
        }

        /**
         * given an x and y, determines the slope of a line
         * through (0,0) and that point.
         * MAY RETURN INFINITY (or negative infinity)
         * if you give it (0,0), it's obviously not going
         * to be able to give you a slope
         */
        public static function GetSlope(x:Number, y:Number):Number
        {
            if (0.0 != x)
            {
                return (y / x);
            }
            else if (0.0 > y)
            {
                return Number.NEGATIVE_INFINITY;
            }
            else
            {
                return Number.POSITIVE_INFINITY;
            }
        }

        /**
         * returns the "sign" of a number
         * n > 0  ==> 1
         * n < 0  ==> -1
         * n = 0  ==> 0
         */
        public static function Sign(n:Number):Number
        {
            if (n > 0) return 1;
            if (n < 0) return -1;
            return 0;
        }
        
        /**
         * take a displayobject and stretch it between two points
         * ASSUMES the object's registration point is in its center
         */
        public static function StretchBetween(object:DisplayObject, pointA:Point, pointB:Point, maxLen:Number = 0.0):void
        {
            object.rotation = 0;
            var distance:Number = Point.distance(pointA, pointB);
            if (distance == 0.0)
            {
                distance = 1.0;
            }
            
            var scale:Number = 1.0;
            
            if ( (0.0 != maxLen) && (distance > maxLen) )
            {
                scale = maxLen / distance;
            }
            
            object.width = distance * scale;
            
            var center:Point = Point.interpolate(pointB, pointA, 0.5 * scale);
            object.x = center.x;
            object.y = center.y;
            
            object.rotation = Math.atan2(pointB.y - pointA.y, pointB.x - pointA.x) * 180.0 / Math.PI;
        }
        
        /**
         * Scale a point/vector and return it (leaves the original alone)
         */
        public static function ScaleVector(v:Point, scale:Number):Point
        {
            return new Point(v.x * scale, v.y * scale);
        }
    }
}