using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EVE.EVELogic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Collections.ObjectModel;

namespace EVE.EVEPhysics
{
    public class EVEPhysicsEngine
    {
        Random rand = new Random();
        public Dictionary<int,EVEPhysicalData> physicalObjects;
        
        public EVEPhysicsEngine()
        {
            physicalObjects = new Dictionary<int, EVEPhysicalData>();

            collisionPlanes = new List<Plane>();
            collisionPlanes.Add(new Plane(Vector3.UnitX, 99));
            collisionPlanes.Add(new Plane(-Vector3.UnitX, 99));
            collisionPlanes.Add(new Plane(Vector3.UnitZ, 99));
            collisionPlanes.Add(new Plane(-Vector3.UnitZ, 99));
        }

        public void registerPhysicalObject(EVEUnit logicalObject)
        {
            if(logicalObject.isPhysical)
                physicalObjects.Add(logicalObject.uID, logicalObject.physicalData);
        }

        public bool updateUnitPhysics(EVEUnit logicalObject, GameTime gameTime)
        {
            if (!logicalObject.isStatic)
                return updateObjectPhysics(logicalObject.physicalData, gameTime);
            else
                return false;
        }

        public bool updateObjectPhysics(EVEPhysicalData physicalObject, GameTime gameTime)
        {
            return updateSphereObjectPhysics(physicalObject, (float)gameTime.ElapsedGameTime.TotalSeconds);
            //physicalObject.position += Vector3.Multiply(new Vector3((float)rand.NextDouble() * 2.0f - 1.0f, (float)rand.NextDouble() * 2.0f - 1.0f, (float)rand.NextDouble() * 2.0f - 1.0f),(float)Math.Sin((double)gameTime.TotalGameTime.Milliseconds*0.0001f));
            //return true;
        }
        
        // Fast Hack Ball physics

        List<Plane> collisionPlanes;

        private bool updateSphereObjectPhysics(EVEPhysicalData physicsData, float elapsedSeconds)
        {

            //friction deceleration
            physicsData.acceleration -= physicsData.velocity * 0.5f;

            //recompute new velocity and position
            physicsData.velocity += Vector3.Multiply(physicsData.acceleration, elapsedSeconds);

            if (physicsData.velocity.Length() < 0.2f)
            {
                physicsData.velocity = Vector3.Zero;
                physicsData.acceleration = Vector3.Zero;
            }

            float remainingTime = elapsedSeconds;

            foreach (EVEPhysicalData sd in physicalObjects.Values)
            {
                if (sd != physicsData)
                {
                    CollideSphereWithSphere(physicsData, sd, remainingTime, out remainingTime);
                }
            }

            CollideSphereWithWall(physicsData, remainingTime);

            Vector3 movementDelta = Vector3.Multiply(physicsData.velocity, remainingTime);
            physicsData.position += movementDelta;

            float distanceMoved = movementDelta.Length();

            Vector3 frontVector = physicsData.velocity;
            Vector3 upvector = Vector3.Up;
            Vector3 rightVector = Vector3.Cross(frontVector, upvector);
            if (rightVector.Length() > 0.0f)
                rightVector.Normalize();

            physicsData.rotation *= Matrix.CreateFromAxisAngle(rightVector, -distanceMoved);
            physicsData.acceleration = Vector3.Zero;

            return (distanceMoved > 0.0f);
        }

        private bool AreSpheresColliding(EVEPhysicalData s1, EVEPhysicalData s2, float elapsedSeconds, out float timeAfterCollision)
        {
            timeAfterCollision = elapsedSeconds;
            Vector3 relativeVelocity = s1.velocity - s2.velocity;
            float radius = 1;
            Vector3 relativePosition = s1.position - s2.position;

            // If the relative movement of two spheres show that they are moving away, no collision.
            float relativeMovement = Vector3.Dot(relativePosition, relativeVelocity);
            if (relativeMovement >= 0)
            {
                return false;
            }

            // Checks if two spheres are already colliding.
            if (relativePosition.LengthSquared() - (radius * radius) <= 0.0f)
            {
                return true;
            }


            //is this still required?
            float relativeDistance = relativePosition.Length() - radius;
            if (relativeDistance <= radius)
            {
                return true;
            }

            // does collision happen this frame
            // how much time remains after collision?
            if (relativeDistance < relativeVelocity.Length() * elapsedSeconds)
            {
                float timeFraction = relativeDistance / relativeVelocity.Length();

                s1.position = s1.position + s1.velocity * timeFraction;
                s2.position = s2.position + s2.velocity * timeFraction;

                timeAfterCollision = elapsedSeconds * (1.0f - timeFraction);

                relativePosition = s1.position - s2.position;

                if ((relativePosition.LengthSquared() - (radius * radius)) <= 0)
                {
                    return true;
                }
            }

            return false;
        }

        private void CollideSphereWithSphere(EVEPhysicalData s1, EVEPhysicalData s2, float elapsedSeconds, out float remainingTime)
        {
            if (AreSpheresColliding(s1, s2, elapsedSeconds, out remainingTime))
            {
                //we have collision, compute new velocities;

                Vector3 relativePosition = s1.position - s2.position;
                Vector3 relativeUnit = Vector3.Normalize(relativePosition);

                float s1VelDotUnit = Vector3.Dot(s1.velocity, relativeUnit);
                float s2VelDotUnit = Vector3.Dot(s2.velocity, relativeUnit);

                float momentumDifference = (2.0f * (s1VelDotUnit - s2VelDotUnit)) / (2.0f);

                // Compute the final velocity of the two spheres.
                s1.velocity = s1.velocity - momentumDifference * relativeUnit;
                s2.velocity = s2.velocity + momentumDifference * relativeUnit;
            }
            else
            {
                remainingTime = elapsedSeconds;
            }
        }

        private void CollideSphereWithWall(EVEPhysicalData physicsData, float elapsedSeconds)
        {
            Vector3 u = Vector3.Normalize(physicsData.velocity);
            float vLength2 = physicsData.velocity.Length() * elapsedSeconds;
            Ray r = new Ray(physicsData.position, u);

            float? intersectPos;
            foreach (Plane p in collisionPlanes)
            {
                intersectPos = r.Intersects(p);
                if (intersectPos != null)
                {
                    float intersectValue = (float)intersectPos;
                    if ((intersectValue > 0) && (intersectValue < vLength2))
                        physicsData.velocity = Vector3.Reflect(physicsData.velocity, p.Normal);
                }
            }
        }


        
    }
}
