using System;
using System.Drawing;
using System.Collections;

using Icarus.Logic.GameEngine;

namespace Icarus.Logic.Physics
{
	/// <summary>
	/// I am the Physics Engine Singleton
	///   I handle all aspects of Physical calculation
	///   Primarily, I change the positions of objects in
	///   the world based upon their velocities and accelerations.
	///   
	///   I also prevent objects from leaving the bounds of the world
	///   as well as handle the process of deciding when
	///   they collide with another object.
	///   
	///   My buddy, the Logic module, takes care of deciding what to do
	///   with the collision itself, I just figure out there is one.
	///   
	/// </summary>
	public class PhysicsEngine
	{

        #region Instance Variables
        
        /// <summary>
        /// This is a reference to the current level's collision mask bitmap
        /// </summary>
        private Bitmap collisionMask = null;

        private Icarus.Logic.Level lCurrentLevel; 

        #endregion

        #region Singleton Implementation

        private static PhysicsEngine instance = new PhysicsEngine();		
        
        /// <summary>
        /// Physics Engine Instance
        /// </summary>
        public static PhysicsEngine Instance {
            get { return instance; }
        }

        private PhysicsEngine() {
        }

        #endregion

        #region Accessors/Modifiers

        /// <summary>
        /// Gives usable access to the collision mask bitmap
        /// </summary>
        public Bitmap CollisionMask 
        {
            get { return this.collisionMask; }
            set { this.collisionMask = value; }
        }

        /// <summary>
        /// Current Level reference
        /// </summary>
        public Level currentLevel
        {
            get { return this.lCurrentLevel; }
            set { this.lCurrentLevel = value;}
        }

        #endregion

        #region Methods

        /// <summary>
        /// Updates the list of WorldObjects for the next iteration
        /// </summary>
        /// <param name="worldObjectList">
        /// A list of WorldObjects, note that no type checking is made on these
        /// objects and, as such, it is assumed that they are WorldObjects.
        /// </param>
        public void updateWorld(PhysicsObject[] worldObjectList) 
        {
            int iResult;
            PhysicsObject po1;

            //Check for collisions with the world
            for (int i = 0; i < GameSettings.Instance.sv_maxActors; i++)
            {
                po1 = worldObjectList[i];
                if(po1 == null) break;

				//First, allow the actor's controller to do its thing:
				po1.Controller.UpdateObject(po1);

                //TODO: Eventually this should be replaced with a tree traversal to limit
                //      the checks needed.
                foreach (CollisionBox cb in this.lCurrentLevel.CurrStage.CollisionBoxes)
                {
                    if(cb == null) break;

                    iResult = cb.checkAndCollide(po1);
                    if(iResult == (int)PhysicsSettings.enumActions.Remove)
                    {
                        //the obj needs to be removed
                        WorldObject[] woList = PhysicsEngine.Instance.currentLevel.CurrStage.actorList;
                        for(int index = 0; i < GameSettings.Instance.sv_maxActors; index++)
                        {
                            if(woList[index] == null) break;
                            if(woList[index].Equals(po1))
                            {
                                for(int j = index+1; j < GameSettings.Instance.sv_maxActors; j++)
                                {
                                    woList[j-1] = woList[j];
                                }
                            }
                        }

                    }

                }
                
            }

            //TODO: This is redundant and slow FIX
            //Check for collisions among all physics Objects
            foreach (PhysicsObject po in worldObjectList) 
            {
			    if(po == null) break;
	
                foreach (PhysicsObject po2 in worldObjectList)
                {
                    if(po2 == null) break;
                    if(po2.Equals(po)) continue;//Skip Self
                 
                    //Perform collision check and then perform collison action
                    if((iResult = po.CollisionBox.checkAndCollide(po2)) == (int)PhysicsSettings.enumActions.Remove)
                    {
                        //the obj needs to be removed
                        for(int i = 0; i < GameSettings.Instance.sv_maxActors; i++)
                        {
                            if(worldObjectList[i] == null) break;
                            if(worldObjectList[i].Equals(po))
                            {
                                for(int j = i+1; j < GameSettings.Instance.sv_maxActors; j++)
                                {
                                    worldObjectList[j-1] = worldObjectList[j];
                                }
                            }
                        }

                    }
                    
                    
                    //check for children
                    WorldObject wo = (WorldObject)po;
                    checkChildren(wo, po2);
                }
                				
                //Perform updates now that collisions have been made

                //If specific updates wants the object removed... remove it.
                //Otherwise, just update position and vectors as usuall
                if((iResult = po.specificUpdates()) == (int)PhysicsSettings.enumActions.Remove)
                {
                    //the obj needs to be removed
                    for(int i = 0; i < GameSettings.Instance.sv_maxActors; i++)
                    {
                        if(worldObjectList[i] == null) break;
                        if(worldObjectList[i].Equals(po))
                        {
                            for(int j = i+1; j < GameSettings.Instance.sv_maxActors; j++)
                            {
                                worldObjectList[j-1] = worldObjectList[j];
                            }
                        }
                    }

                }
                else
                {
                    updatePosition(po);
                    updateVectors(po);                
                }
            }
        }

        /// <summary>
        /// Check for collisions with all the children of a worldobject
        /// </summary>
        /// <param name="wo">The world object with children</param>
        /// <param name="po">the physics object that is being tested for collisions</param>
        private void checkChildren(WorldObject wo, PhysicsObject po)
        {
            WorldObject woChild;
            for(int i = 0; i < wo.Children.Length; i++)
            {
                woChild = wo.Children[i];
                woChild.CollisionBox.checkAndCollide(po);
                checkChildren(woChild, po);
                //ugh this makes the 0() even worse 
                checkOtherChildren(woChild, po);
            }
        }

        //TODO: Does this do any good?
        /// <summary>
        /// Check for collisions between two children
        /// </summary>
        /// <param name="wo">The world object with children</param>
        /// <param name="po">the physics object that has children to test</param>
        private void checkOtherChildren(WorldObject wo, PhysicsObject po)
        {
            WorldObject woPOChild;
            for(int i = 0; i < ((WorldObject)po).Children.Length; i++)
            {
                woPOChild = ((WorldObject)po).Children[i];
                wo.CollisionBox.checkAndCollide(woPOChild);
                checkOtherChildren(wo, woPOChild);
            }
        }

        /// <summary>
        /// Updates the current vectors of the Physics Object
        /// 
        /// Currently, AngularVelocity is not taken into account when
        ///  updating Velocity
        /// 
        /// </summary>
        /// <param name="po">Object whose vectors will be updated</param>
        private void updateVectors(PhysicsObject po) {
            //Update velocity
            //Add the Current acceleration (thrusters, increase running speed, etc
            Vector.AddVectors(po.Velocity, po.Velocity, po.Acceleration);
            //Add the Object's Gravity
            Vector.AddVectors(po.Velocity, po.Velocity, po.po_gravity); 

            //Add the Angular Acceleration
            Vector.AddVectors(po.AngularVelocity, po.AngularVelocity, po.AngularAcceleration);
                      
        }

        /// <summary>
        /// Update the Position of the Physics Object using the Vectors already defined
        /// 
        /// Currently, AngularVelocity is ignored as are bounds checks
        /// 
        /// </summary>
        /// <param name="po">Object whose position needs to be updated</param>
        private void updatePosition(PhysicsObject po) {
            Point currLoc = po.Location;
            
            if (Math.Abs(po.Velocity.i) > po.po_terminalVelocity.i) {
                currLoc.x += po.Velocity.i > 0 ? po.po_terminalVelocity.i : -po.po_terminalVelocity.i;
            } else {
                currLoc.x += po.Velocity.i;
            }            

            if (Math.Abs(po.Velocity.j) > po.po_terminalVelocity.j) 
            {
                currLoc.y += po.Velocity.j > 0 ? po.po_terminalVelocity.j : -po.po_terminalVelocity.j;
            } 
            else 
            {
                currLoc.y += po.Velocity.j;
            }  

            if (Math.Abs(po.Velocity.k) > po.po_terminalVelocity.k) 
            {
                currLoc.z += po.Velocity.k > 0 ? po.po_terminalVelocity.k : -po.po_terminalVelocity.k;
            } 
            else 
            {
                currLoc.z += po.Velocity.k;
            }  

        }


        /// <summary>
        /// This function will cause a recoil between two objects based on
        /// Intertia and both types of velocity.
        /// 
        /// TODO: Implement the above, currently Intertia is not taken into
        ///  account and a purely elastic, pure recoil calculation is made.
        ///  That sorta sucks for anything but Zero G, equal Intertia settings.
        /// </summary>
        /// <param name="po1">po1</param>
        /// <param name="po2">po2</param>
        public void doRecoil(PhysicsObject po1, PhysicsObject po2) {
            Vector temp = po1.Velocity;

            po1.Velocity = po2.Velocity;
            po2.Velocity = temp;
        }

        #endregion

	}
}





















