using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace MegaManRipoff.MainGameClasses
{
    /// <summary>
    /// Provides common calculation tools for entities.
    /// </summary>
    static class EntityHelper
    {
        /// <summary>
        /// Gets the distance between two given points. This method exists for Vector2,
        /// but not Points, which I find very odd indeed.
        /// </summary>
        /// <param name="point1">The first point.</param>
        /// <param name="point2">The second point.</param>
        /// <returns>The distance.</returns>
        public static float PointDistance(Point point1, Point point2)
        {
            return (float)Math.Sqrt(Math.Pow(point1.X - point2.X, 2)
                + Math.Pow(point1.Y - point2.Y, 2));
        }

        /// <summary>
        /// Calculates the direction between two vectors.
        /// </summary>
        /// <param name="vector1">The first vector.</param>
        /// <param name="vector2">The second vector.</param>
        /// <returns>The direction between vector1 and vector2, in radians.</returns>
        public static float Direction(Vector2 vector1, Vector2 vector2)
        {
            return (float)Math.Atan2(vector2.Y - vector1.Y, vector2.X - vector1.X);
        }

        /// <summary>
        /// Calculates the direction between two points.
        /// </summary>
        /// <param name="vector1">The first point.</param>
        /// <param name="vector2">The second point.</param>
        /// <returns>The direction between point1 and point2, in radians.</returns>
        public static float Direction(Point point1, Point point2)
        {
            //Convert the points to vectors and call the vector overload.
            Vector2 vector1 = new Vector2(point1.X, point1.Y);
            Vector2 vector2 = new Vector2(point2.X, point2.Y);
            return Direction(vector1, vector2);
        }

        /// <summary>
        /// Creates a Mega Man-esque explosion at the given position. This does not play
        /// the sound effect.
        /// </summary>
        /// <param name="room">The current room.</param>
        /// <param name="position">The centre of the explosion.</param>
        public static void MegaManExplosion(Room room, Vector2 position)
        {
            for (int i = 0; i < 360; i += 45)
            {
                //Create two explosions going in the current direction, one
                //faster than the other.
                room.Add(new PlayerDeathEffect(room, position,
                                               EntityHelper.RadialToVector(2, MathHelper.ToRadians(i))));
                room.Add(new PlayerDeathEffect(room, position,
                                               EntityHelper.RadialToVector(1, MathHelper.ToRadians(i))));
            }
        }

        /// <summary>
        /// Takes a speed and direction and turns it into a Cartesian-form vector.
        /// </summary>
        /// <param name="speed">The speed.</param>
        /// <param name="direction">The direction, in radians.</param>
        /// <returns>The constructed speed vector.</returns>
        public static Vector2 RadialToVector(float speed, float direction)
        {
            return new Vector2(speed * (float)Math.Cos(direction),
                speed * (float)Math.Sin(direction));
        }

        /// <summary>
        /// Takes a speed and direction and turns it into a Cartesian-form vector.
        /// </summary>
        /// <param name="speed">The speed.</param>
        /// <param name="direction">The direction, in degrees.</param>
        /// <returns>The constructed speed vector.</returns>
        public static Vector2 RadialToVector(float speed, int direction)
        {
            //I got lazy of putting MathHelper.ToRadians() everywhere, so I made this overload.
            return RadialToVector(speed, MathHelper.ToRadians(direction));
        }

        #region Collision and Physics

        /// <summary>
        /// Checks the given hitbox for intersecting solid tiles. Quick but sloppy.
        /// </summary>
        /// <param name="room">The current room.</param>
        /// <param name="hitbox">The hitbox to check against.</param>
        /// <returns>Returns the intersecting tile, if there is one.</returns>
        public static Tile CheckForSolidTiles(Room room, Rectangle hitbox)
        {
            //Check each tile for the type and if it intersects the given hitbox.
            foreach (Tile tile in room.Tiles)
            {
                //Break if we've found a colliding solid tile.
                if (tile.IsSolid && hitbox.Intersects(tile.Hitbox))
                    return tile;
            }
            return null;
        }

        /// <summary>
        /// Calculates deceleration due to friction.
        /// </summary>
        /// <param name="speed">The original speed vector.</param>
        /// <param name="friction">The friction scalar.</param>
        /// <returns>The new speed vector after friction has been induced.</returns>
        public static Vector2 InduceFriction(Vector2 speed, float friction)
        {
            //This calculation gets the positive value of the speed and subtracts the friction
            //value. Using Math.Max(), the result is compared with zero to ensure that we don't
            //decelerate so much that the player starts going backwards!! The result is finally
            //given the correct sign (positive/negative) using Math.Sign().
            return new Vector2(Math.Sign(speed.X) * Math.Max(Math.Abs(speed.X) - friction, 0),
                               speed.Y);
        }

        /// <summary>
        /// Provides horizontal tile collision to entities that's lovely and smooth!!
        /// </summary>
        /// <param name="room">The current room.</param>
        /// <param name="hitbox">The entity's hitbox.</param>
        /// <param name="position">The entity's position vector.</param>
        /// <param name="speed">The entity's position vector.</param>
        /// <returns>The type of tile that was collided with.</returns>
        public static Tile HorizontalCollision(Room room, Rectangle hitbox,
            ref Vector2 position, ref Vector2 speed)
        {
            //The collision works by nudging the entity along by a single pixel until there is
            //a collision detected. It does this for as many times dependent on the speed, as such
            //the distance moved per update step is always an integer. This means that in some
            //circumstances (e.g. ice deceleration) it doesn't look too good. Oh well.
            for (int i = 1; i < Math.Floor(Math.Abs(speed.X)); i++)
            {
                foreach (Tile tile in room.Tiles)
                {
                    //Check if the tile is a solid, then check if the entity and the tile align vertically.
                    if ((tile.IsSolid) && (hitbox.Top < tile.Hitbox.Bottom && hitbox.Bottom > tile.Hitbox.Top))
                    {
                        //Check if the player at the next step intersects with the tile.
                        if ((speed.X > 0 && hitbox.Right + i > tile.Hitbox.Left && hitbox.Left + i < tile.Hitbox.Left)
                            || (speed.X < 0 && hitbox.Left - i < tile.Hitbox.Right && hitbox.Right - i > tile.Hitbox.Right))
                        {
                            //We've collided, so cancel our speed and get out of the method.
                            speed.X = 0;
                            return tile;
                        }
                    }
                }

                //If we've successfully not collided with all of the tiles,
                //increment the position.
                position.X += Math.Sign(speed.X);
            }

            //If we've reached this point, return that we've collided with no tiles.
            return null;
        }



        /// <summary>
        /// Applies Newton's first law to an entity.
        /// </summary>
        /// <param name="speed">The original speed vector.</param>
        /// <returns>The new speed vector after gravity has been induced.</returns>
        public static Vector2 InduceGravity(Vector2 speed)
        {
            //The XML summary for this method is actually a lie because nothing
            //in this game has any mass. Oh well.
            return new Vector2(speed.X,
                               speed.Y + Level.GRAVITY);
        }

        /// <summary>
        /// Provides vertical tile collision to entities that's lovely and smooth!!
        /// </summary>
        /// <param name="room">The current room.</param>
        /// <param name="hitbox">The entity's hitbox.</param>
        /// <param name="position">The entity's position vector.</param>
        /// <param name="speed">The entity's position vector.</param>
        /// <param name="floor">Whether or not the entity is on the floor.</param>
        /// <returns>The tile that was collided with.</returns>
        public static Tile VerticalCollision(Room room, Rectangle hitbox,
            ref Vector2 position, ref Vector2 speed, ref bool floor)
        {
            //The collision works by nudging the entity along by a single pixel until there is
            //a collision detected. It does this for as many times dependent on the speed, as such
            //the distance moved per update step is always an integer. This means that in some
            //circumstances (e.g. ice deceleration) it doesn't look too good. Oh well.
            for (int i = 1; i < Math.Floor(Math.Abs(speed.Y)); i++)
            {
                foreach (Tile tile in room.Tiles)
                {
                    //Check if the tile is a solid, then check if the entity and the tile align horizontally.
                    if (tile.IsSolid && (hitbox.Left < tile.Hitbox.Right && hitbox.Right > tile.Hitbox.Left))
                    {
                        //Check if the entity at the current step intersects with the tile.
                        if ((speed.Y > 0 && hitbox.Bottom + i > tile.Hitbox.Top && hitbox.Top + i < tile.Hitbox.Top)
                            || (speed.Y < 0 && hitbox.Top - i < tile.Hitbox.Bottom && hitbox.Bottom - i > tile.Hitbox.Bottom))
                        {
                            //If we've moved downwards to collide, we're on the floor.
                            if (speed.Y > 0)
                            {
                                floor = true;
                            }

                            //We've collided, so cancel our speed and get out of the method.
                            speed.Y = 0;
                            return tile;
                        }
                    }
                }

                //If we've successfully not collided with all of the tiles,
                //increment the position. This also means we're in the air.
                position.Y += Math.Sign(speed.Y);
                floor = false;
            }

            //If we've reached this point, return that we've collided with no tiles.
            return null;
        }

        /// <summary>
        /// Provides vertical tile collision to entities that's lovely and smooth!!
        /// </summary>
        /// <param name="room">The current room.</param>
        /// <param name="hitbox">The entity's hitbox.</param>
        /// <param name="position">The entity's position vector.</param>
        /// <param name="speed">The entity's position vector.</param>
        /// <returns>The tile that was collided with.</returns>
        public static Tile VerticalCollision(Room room, Rectangle hitbox,
            ref Vector2 position, ref Vector2 speed)
        {
            //Create a new dummy variable and call the original method.
            bool dummyBool = false;
            return VerticalCollision(room, hitbox, ref position, ref speed, ref dummyBool);
        }

        /// <summary>
        /// Calculates the jump velocity required to get from vector1 to vector2
        /// when under the influence of gravity.
        /// </summary>
        /// <param name="vector1">The original vector.</param>
        /// <param name="vector2">The final vector.</param>
        /// <returns>The calculated vertical velocity. If vector1 is above vector2,
        /// 0 is returned.</returns>
        public static float JumpVelocity(Vector2 vector1, Vector2 vector2)
        {
            if (vector1.Y < vector2.Y)
                return 0;
            else
            {
                //Calculate the velocity using SUVAT: v^2 = u^2 + 2as.
                float displacement = vector1.Y - vector2.Y;
                float finalSquared = 2 * Level.GRAVITY * displacement;

                return -(float)Math.Sqrt(Math.Abs(finalSquared));
            }
        }

        #endregion
    }
}
