using ActionGame.GameArtifacts.Models.Characters;
using ActionGame.GameArtifacts.Models.Game;
using ActionGame.GameArtifacts.Models.WeaponSprites;
using ActionGame.GameResources.Models;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;

namespace ActionGame.GameArtifacts.Operators
{
    public class PhysicsEngine
    {
        private IMoveableObject moveableObject;
        private readonly WorldMapCell worldMapCell;
        private readonly GameState gameState;
        public IList<IMoveableObject> IgnoreObjects { get; private set; }
        public Boolean IgnoreAllCollisions { get; set; }


        public PhysicsEngine(IMoveableObject moveableObject, WorldMapCell worldMapCell, GameState gameState)
        {
            this.moveableObject = moveableObject;
            this.worldMapCell = worldMapCell;
            this.gameState = gameState;
            
            this.IgnoreObjects = new List<IMoveableObject>();
            this.IgnoreAllCollisions = false;
        }


        public void Update()
        {
            MovementPhysics physics = this.moveableObject.MovementPhysics;

            while (physics.IsMoving())
            {
                if (CanMoveToLocation(physics.VelocityX, physics.VelocityY))
                {
                    MoveObject(physics.VelocityX, physics.VelocityY);

                    AdjustMotionX();
                    AdjustMotionY();

                    return;
                }
                else if (CanMoveToLocation(physics.VelocityX, 0) && (physics.VelocityX != 0))
                {
                    MoveObject(physics.VelocityX, 0);

                    AdjustMotionX();
                    AdjustMotionY();

                    return;
                }
                else if (CanMoveToLocation(0, physics.VelocityY) && (physics.VelocityY != 0))
                {
                    MoveObject(0, physics.VelocityY);

                    AdjustMotionX();
                    AdjustMotionY();

                    return;
                }
                else
                {
                    SlowObject();
                }
            }

            StopObject();
        }


        private void SlowObject()
        {
            MovementPhysics movementPhysics = moveableObject.MovementPhysics;

            movementPhysics.VelocityX /= 2;
            movementPhysics.VelocityY /= 2;
            movementPhysics.AccelerationX /= 2;
            movementPhysics.AccelerationY /= 2;
        }


        private void StopObject()
        {
            MovementPhysics movementPhysics = moveableObject.MovementPhysics;

            movementPhysics.VelocityX = 0;
            movementPhysics.VelocityY = 0;
            movementPhysics.AccelerationX = 0;
            movementPhysics.AccelerationY = 0;
        }


        private Boolean CanMoveToLocation(Double velocityX, Double velocityY)
        {
            MovementPhysics movementPhysics = moveableObject.MovementPhysics;
            Int32 possibleNewPositionX = moveableObject.WorldPosition.PositionX + (Int32)velocityX;
            Int32 possibleNewPositionY = moveableObject.WorldPosition.PositionY + (Int32)velocityY;
            Int32 width = TileSet.DRAW_WIDTH;
            Int32 height = TileSet.DRAW_HEIGHT / 2;

            Int32 maxX = worldMapCell.GetMaxTileX();
            Int32 maxY = worldMapCell.GetMaxTileY();
            Rectangle tileBounds = default(Rectangle);
            TileSet tileSet = null;

            Rectangle objectBounds = moveableObject.GetCollisionAreaAtPosition(possibleNewPositionX, possibleNewPositionY);

            //tiles
            if (IgnoreAllCollisions == false)
            {
                for (Int32 x = 0; x < maxX; x++)
                {
                    for (Int32 y = 0; y < maxY; y++)
                    {
                        tileBounds = new Rectangle(x * TileSet.DRAW_WIDTH, y * TileSet.DRAW_HEIGHT, TileSet.DRAW_WIDTH, TileSet.DRAW_HEIGHT);

                        if (objectBounds.Intersects(tileBounds))
                        {
                            tileSet = worldMapCell.TilesLayer1[x][y];
                            if (tileSet.Passable == false) return false;

                            tileSet = worldMapCell.TilesLayer2[x][y];
                            if (tileSet?.Passable == false) return false;
                        }
                    }
                }
            }

            //Map Cell Edges
            if ((possibleNewPositionX + width) > (maxX * TileSet.DRAW_WIDTH)) return false;
            if (possibleNewPositionY > (maxY * TileSet.DRAW_HEIGHT)) return false;
            if ((possibleNewPositionX <= 1) && (velocityX < 0)) return false;
            
            if(moveableObject.GetType() == typeof(Player))
            {
                if (possibleNewPositionY <= 3) return false;
            }
            else
            {
                if (possibleNewPositionY <= TileSet.DRAW_HEIGHT) return false;
            }



            //npc's
            if (IgnoreAllCollisions == false)
            {
                foreach (NonPlayerCharacter npc in this.gameState.ActiveNonPlayerCharacters)
                {
                    if ((moveableObject != npc) && (IgnoreObjects.Contains(npc) == false))
                    {
                        if (objectBounds.Intersects(npc.GetCurrentCollisionArea()))
                        {
                            return false;
                        }
                    }
                }

                //player
                if ((moveableObject != gameState.Player) && (IgnoreObjects.Contains(gameState.Player) == false))
                {
                    if (objectBounds.Intersects(gameState.Player.GetCurrentCollisionArea()))
                    {
                        return false;
                    }
                }

                //enemies
                if ((moveableObject is IWeaponSprite) == false)
                {
                    foreach (Enemy enemy in this.gameState.ActiveEnemies)
                    {
                        if ((moveableObject != enemy) && (IgnoreObjects.Contains(enemy) == false))
                        {
                            if (objectBounds.Intersects(enemy.GetCurrentCollisionArea()))
                            {
                                return false;
                            }
                        }
                    }
                }
            }

            return true;
        }


        private void MoveObject(Double velocityX, Double velocityY)
        {
            MovementPhysics movementPhysics = moveableObject.MovementPhysics;

            moveableObject.WorldPosition.PositionX += (Int32)velocityX;
            moveableObject.WorldPosition.PositionY += (Int32)velocityY;
        }


        private void AdjustMotionX()
        {
            MovementPhysics movementPhysics = moveableObject.MovementPhysics;

            if ((movementPhysics.VelocityX > 0) && (movementPhysics.AccelerationX > 0))
            {
                //going right and speeding up
                movementPhysics.VelocityX += movementPhysics.AccelerationX;
            }
            else if ((movementPhysics.VelocityX < 0) && (movementPhysics.AccelerationX < 0))
            {
                //going left and speeding up
                movementPhysics.VelocityX += movementPhysics.AccelerationX;
            }
            else if ((movementPhysics.VelocityX > 0) && (movementPhysics.AccelerationX < 0))
            {
                //going right and slowing down
                movementPhysics.VelocityX += movementPhysics.AccelerationX;

                if (movementPhysics.VelocityX < 0)
                {
                    movementPhysics.VelocityX = 0;
                    movementPhysics.AccelerationX = 0;
                }
            }
            else if ((movementPhysics.VelocityX < 0) && (movementPhysics.AccelerationX > 0))
            {
                //going left and slowing down
                movementPhysics.VelocityX += movementPhysics.AccelerationX;

                if (movementPhysics.VelocityX > 0)
                {
                    movementPhysics.VelocityX = 0;
                    movementPhysics.AccelerationX = 0;
                }
            }
        }


        private void AdjustMotionY()
        {
            MovementPhysics movementPhysics = moveableObject.MovementPhysics;

            if ((movementPhysics.VelocityY > 0) && (movementPhysics.AccelerationY > 0))
            {
                //going down and speeding up
                movementPhysics.VelocityY += movementPhysics.AccelerationY;
            }
            else if ((movementPhysics.VelocityY < 0) && (movementPhysics.AccelerationY < 0))
            {
                //going up and speeding up
                movementPhysics.VelocityY += movementPhysics.AccelerationY;
            }
            else if ((movementPhysics.VelocityY > 0) && (movementPhysics.AccelerationY < 0))
            {
                //going down and slowing down
                movementPhysics.VelocityY += movementPhysics.AccelerationY;

                if (movementPhysics.VelocityY < 0)
                {
                    movementPhysics.VelocityY = 0;
                    movementPhysics.AccelerationY = 0;
                }
            }
            else if ((movementPhysics.VelocityY < 0) && (movementPhysics.AccelerationY > 0))
            {
                //going up and slowing down
                movementPhysics.VelocityY += movementPhysics.AccelerationY;

                if (movementPhysics.VelocityY > 0)
                {
                    movementPhysics.VelocityY = 0;
                    movementPhysics.AccelerationY = 0;
                }
            }
        }

    }
}
