
using System;
using GDI=System.Drawing;
using System.Xml.Serialization;

using Icarus.Graphics.Animation;
using Icarus.Logic.Physics;
using Icarus.Logic.Controllers;

namespace Icarus.Logic
{
	/// <summary>
	/// I contain the necessary properties needed by any
	/// Physical universe as proposed by Sir Isaac Newton
	/// so long ago.
	/// 
	/// I also contain useful information such as my Inertia,
	/// which is a measure of my stubbornness to change motion,
	/// as well as my CollisionMask, which defines my girth.
	/// </summary>
	public abstract class PhysicsObject : IViewable 
	{

        #region IViewable Members

        private FramesetAnimation sAnimation = null;

        /// <summary>
        /// The id of the animation to use for the viewable.
        /// </summary>
        public FramesetAnimation Animation 
        {
            get { return this.sAnimation; }
            set { this.sAnimation = value; }
        }

        /// <summary>
        /// Allows access to the current frame for position logic
        /// </summary>
        [XmlIgnore]
        public IFrame CurrentFrame
        {
            get { return this.Animation.CurrentFrame; }
        }

        #endregion

        #region Declarations
        
        private Point pntLocation = new Point();
        private Vector vecAngle = new Vector();
        private Vector vecVelocity = new Vector();
        private Vector vecAngularVelocity = new Vector();
        private Vector vecAcceleration = new Vector();
        private Vector vecAngularAcceleration = new Vector();
        private Vector vecGravity = new Vector();
        private Vector vecTerminalVelocity = new Vector();

        private float fInertia = 0;

        /// <summary>
        /// The coeffecient multiplied to velocities on a collision.
        /// This can be used to create object with different levels
        /// of "bouncing recoil"
        /// </summary>
        protected float fBounceCoeffecient = 1.0f;

        /// <summary>
        /// The number of collisions this object will make
        /// before it is removed from the world. 0 = infinite.
        /// </summary>
        protected int iCollisionsToLive = 0;

        /// <summary>
        /// The amount of time(ms) this object will live before
        /// it is removed from the world. 0 = infinite.
        /// </summary>
        protected int iTimeToLive = 0;

        /// <summary>
        /// This is the time of creation of an object.  It is used
        /// in conjunction with iTimeToLive to remove the object
        /// after it has been alive too long.
        /// </summary>
        protected DateTime dtBirthDate;

        /// <summary>
        /// State that represents the current action of the object
        /// </summary>
        private int iActionState;

        /// <summary>
        /// Holds the last action state of this player.
        /// Used for determining when states have changed.
        /// </summary>
        private int iLastActionState;

        /// <summary>
        /// The collision box for this PO. This box is so this object has 
        /// a reference to the smaller internal bounding rectangles located
        /// in this bounding box.
        /// </summary>
        private CollisionBox collisionBox;

        /// <summary>
        /// X distance from the location to the collision box anchor
        /// </summary>
        private int iCBXoffset;

        /// <summary>
        /// Y distance from the location to the collision box anchor
        /// </summary>
		private int iCBYoffset;

		/// <summary>
		/// Me controller.
		/// </summary>
		private IController mController = ControllerFactory.Instance.DefaultController;

        #endregion

        #region Fields

        /// <summary>
        /// X distance from the location to the collision box anchor
        /// </summary>
        public int CBXoffset
        {
            get { return this.iCBXoffset; }
            set { this.iCBXoffset = value; }
        }

        /// <summary>
        /// Y distance from the location to the collision box anchor
        /// </summary>
        public int CBYoffset
        {
            get { return this.iCBYoffset; }
            set { this.iCBYoffset = value; }
        }

        /// <summary>
        /// The collision box for this PO
        /// </summary>
        //[XmlIgnore]
        public virtual CollisionBox CollisionBox
        {
            get 
            {
                //Setting the current bounding rectangle of the bounding box.
                PhysicsRectangle rect = this.collisionBox.BoundingBox;
                rect.X = (int)this.Location.x - iCBXoffset;
                rect.Y = (int)this.Location.y - iCBYoffset;
                rect.Width = this.collisionBox.BoundingBoxWidth + Math.Abs((int)this.Velocity.i + 1);
                rect.Height = this.collisionBox.BoundingBoxHeight + Math.Abs((int)this.Velocity.j + 1);
                if(this.Velocity.i < 0) rect.X += (int)this.Velocity.i;
                if(this.Velocity.j < 0) rect.Y += (int)this.Velocity.j;
                this.collisionBox.BoundingBox = rect;
                return this.collisionBox; 
            }
            set { this.collisionBox = value; }
        }

        /// <summary>
        /// The current action being performed
        /// </summary>
        public int ActionState
        {
            get { return this.iActionState; }
            set { this.iActionState = value; }
        }

        /// <summary>
        /// The last action state of this player
        /// </summary>
        public int LastActionState 
        {
            get { return this.iLastActionState; }
            set { this.iLastActionState = value; }
        }

        /// <summary>
        /// Provides usable access to the birthdate of a physics object.
        /// </summary>
        public DateTime BirthDate
        {
            get { return this.dtBirthDate; }
            set { this.dtBirthDate = value; }
        }
        
        /// <summary>
        /// Allows access and modification of the number of collisions
        /// an object will make before it is removed from the world.
        /// 0 = infinite.
        /// </summary>
        public int CollisionsToLive
        {
            get { return this.iCollisionsToLive; }
            set { this.iCollisionsToLive = value; }
        }

        /// <summary>
        /// Allows acces and modification of the time that an object will
        /// remain in the world before it is removed. 0 = infinite.
        /// </summary>
        public int TimeToLive
        {
            get { return this.iTimeToLive; }
            set { this.iTimeToLive = value; }
        }

        /// <summary>
        /// This sets the Terminal Velocity of an Object
        /// 
        /// A value of 0 or smaller means there is no Terminal Velocity
        /// 
        /// Default is null (use global gravity)
        ///  
        /// Note that Team RETRO's classic game physics can easily be
        ///  implemented by setting the terminal velocity to something
        ///  less than initial velocity (i.e. move up and down at same rate)
        ///  
        /// </summary>
        public Vector po_terminalVelocity {
            get { 
                if (this.vecTerminalVelocity == null) {
                    return PhysicsSettings.Instance.sv_terminalVelocity;
                } else {
                    return this.vecTerminalVelocity; 
                }
            } set { this.vecTerminalVelocity = value;}
        }
        
        

        /// <summary>
        /// This sets the current gravity to be used
        /// 
        /// Can be negative (accelerate upwards), zero means no gravity
        /// 
        /// This is a constant acceleration in the j direction
        /// 
        /// Default is null (use global gravity)
        /// 
        /// Note that Classic gameplay issues such as underwater and space
        ///  Physics can be implemented by varying the gravity (hint, hint
        ///  underwater Mario level (: )
        /// 
        /// </summary>
        public Vector po_gravity {
            get { 
                if (this.vecGravity == null) {
                    return PhysicsSettings.Instance.sv_gravity;
                } else {
                    return this.vecGravity; 
                }
            } set { this.vecGravity = value;}
        }

        
        /// <summary>
        /// This defines the current Location of the Physics
        ///  Object.  
        ///  
        /// This is unscaled to world Coordinates.
        /// </summary>
        public virtual Point Location {
            get { return this.pntLocation; }
            set { this.pntLocation = value;}
        }
        
        /// <summary>
        /// This defines the velocity of the Physics Object.
        /// 
        /// This is unscaled to World Coordinates
        /// </summary>
        public virtual Vector Velocity {
            get { return this.vecVelocity;  }
            set { this.vecVelocity = value; }
        }

        /// <summary>
        /// This defines the angular velocity of the Physics Object.
        /// 
        /// Angular Velocity = rotational velocity.  
        /// 
        /// This is unscaled to World Coordinates.
        /// </summary>
        public Vector AngularVelocity {
            get { return this.vecAngularVelocity; }
            set { this.vecAngularVelocity = value;}
        }

        /// <summary>
        /// This defines the current angle of the object.
        /// 
        /// This should be used by the Renderer to properly
        ///  rotate the object.
        ///  
        /// This needs to be scaled and then sent to the 
        ///  renderer for it to do an appropriate rotation.
        /// </summary>
        public Vector Angle {
            get { return this.vecAngle; }
            set { this.vecAngle = value;}
        }

        /// <summary>
        /// This is the acceleration of the Object
        /// 
        /// This is in Physics Coordinates.
        /// </summary>
        public Vector Acceleration {
            get { return this.vecAcceleration; }
            set { this.vecAcceleration = value;}
        }

        /// <summary>
        /// This is the angular acceleration of the object.
        /// 
        /// This is in Physics Coordinates.
        /// </summary>
        public Vector AngularAcceleration {
            get { return this.vecAngularAcceleration; }
            set { this.vecAngularAcceleration = value;}
        }

        /// <summary>
        /// The Interia of the body.
        /// 
        /// This isn't what causes an object to slow down
        ///  as some who didn't pay attention to Newton's
        ///  First Law of motion might think.
        ///  
        /// This is the reluctance of the object to move.
        /// 
        /// This is modeled by delayed movements or by
        ///  dividing the acceleration by this.
        ///  
        /// It would make sense to just use the acceleration
        ///  uniquely, but then recoils would become
        ///  complicated.
        /// 
        /// </summary>
        public float Inertia {
            get { return this.fInertia; }
            set { this.fInertia = value;}
        }

        /// <summary>
        /// Gives usable access to the fBounceCoefficient
        /// </summary>
        public float BounceCoeffecient 
        {
            get { return this.fBounceCoeffecient; }
            set { this.fBounceCoeffecient = value; }
		}

		/// <summary>
		/// Returns the controller for this object.
		/// </summary>
		[XmlIgnore]
		public IController Controller
		{
			get { return mController; }
		}

		/// <summary>
		/// Gets the name of the controller type or sets the controller when controller type is set. This is mainly done for XML purposes.
		/// </summary>
		public string ControllerType
		{
			get { return mController.GetType().FullName; }
			set { mController = ControllerFactory.Instance.GetControllerForType(value); }
		}

        #endregion
        
        #region Constructors

        /// <summary>
        /// Generate a generic Physics Object
        /// 
        /// All members are set to empty/default values
        /// 
        /// Vectors are set to 0
        /// </summary>
        public PhysicsObject()
		{
			pntLocation = new Point();
            vecAngle = new Vector();

            vecVelocity = new Vector();
            vecAngularVelocity = new Vector();
            vecAngularAcceleration = new Vector();
            vecAcceleration = new Vector();

            this.CollisionBox = new CollisionBox();

            this.vecGravity = null;
            this.vecTerminalVelocity = null;

            dtBirthDate = System.DateTime.Now;
            
            fInertia = 1.0f;
            
		}

        #endregion

		#region Methods

		/// <summary>
		/// Sets the animation with the specified parameters.
		/// </summary>
		/// <param name="id"></param>
		/// <param name="t"></param>
		/// <param name="delay"></param>
		public void SetAnimation(string id, AnimationType t, int delay)
		{
			if (sAnimation == null)
			{
				sAnimation = new FramesetAnimation();
			}

			sAnimation.FramesetPath = id;
			sAnimation.AnimationType = t;
			sAnimation.AnimationDelay = delay;
		}

        /// <summary>
        /// Output the Physics Object information to Console
        /// </summary>
        public void PrintPhysicsObject() 
        {
            Console.WriteLine(this.ToString());
        }

        /// <summary>
        /// Convert the contents of the PhysicsObject to string
        /// for debugging purposes
        /// </summary>
        /// <returns>A string containing the information</returns>
        public override string ToString() {
            return ("PhysicsObject:\n" + 
                    "\n Location:     " + pntLocation +
                    "\n Velocity:     " + vecVelocity +
                    "\n Acceleration: " + vecAcceleration +
                    "\n AngularAccel: " + vecAngularAcceleration +
                    "\n AngularVel:   " + vecAngularVelocity +
                    "\n Inertia:      " + this.fInertia +
                    "\n");
        }


        /// <summary>
        /// Returns the current direction up,down,left,right,upleft,upright,downleft,downright
        /// </summary>
        /// <returns>An enumDirection from PhysicsSettings.</returns>
        protected int currentDirection()
        {
            // Which direction is this guy going
            if(this.vecVelocity.i < 0)//Moving Left
            {
                if(this.vecVelocity.j < 0)//Moving Up Left
                {
                    return (int)PhysicsSettings.enumDirection.UpLeft;
                }
                else if(this.vecVelocity.j > 0)//Moving Down Left
                {
                    return (int)PhysicsSettings.enumDirection.DownLeft;
                }
                else//Moving Left only
                {
                    return (int)PhysicsSettings.enumDirection.Left;
                }
            }
            else if(this.vecVelocity.i > 0)//Moving Right
            {
                if(this.vecVelocity.j < 0)//Moving Up Right
                {
                    return (int)PhysicsSettings.enumDirection.UpRight;
                }
                else if(this.vecVelocity.j > 0)//Moving Down Right
                {
                    return (int)PhysicsSettings.enumDirection.DownRight;
                }
                else//Moving right only
                {
                    return (int)PhysicsSettings.enumDirection.Right;
                }
            }
            else//Moving Up or Down only
            {
                if(this.vecVelocity.j < 0)//Moving Up
                {
                    return (int)PhysicsSettings.enumDirection.Up;
                }
                else/* if(this.vecVelocity.j > 0)*///Moving Down
                {
                    return (int)PhysicsSettings.enumDirection.Down;
                }
            }
        }

        /// <summary>
        /// This method determines if a physics object has lived longer
        /// than its timetolive value or if it still has more time to live.
        /// </summary>
        /// <returns>
        /// true = still has not reached time limit
        /// false = has reached time limit. 
        ///</returns>
        protected bool checkAge()
        {
            if(this.dtBirthDate.AddMilliseconds(this.TimeToLive).CompareTo(System.DateTime.Now) > 0)
            {
                //Outlived its time
                return false;
            }
            return true;
        }

        /// <summary>
        /// This will be called every time the physics object is updated.
        /// </summary>
        public virtual int specificUpdates() 
        {
            return (int)PhysicsSettings.enumActions.DoNothing;
        }

        #endregion
        
	}
}
