using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using ProjectZ.Base;
using ProjectZ.InGame.GameObjects.Base;
using ProjectZ.InGame.GameObjects.Base.CObjects;
using ProjectZ.InGame.GameObjects.Base.Components;
using ProjectZ.InGame.GameObjects.Base.Components.AI;
using ProjectZ.InGame.GameObjects.Base.Systems;
using ProjectZ.InGame.GameSystems;
using ProjectZ.InGame.Map;
using ProjectZ.InGame.Things;

namespace ProjectZ.InGame.GameObjects.Things
{
    internal class ObjMoveStoneBlackBar : GameObject
    {
        private readonly List<GameObject> _collidingObjects = new List<GameObject>();

        private readonly AiComponent _aiComponent;
        private readonly BodyComponent _body;
        private readonly CBox _box;

        private Vector2 _startPosition;
        private Vector2 _goalPosition;

        private readonly string _strKey;
        private readonly string _strKeyDir;
        private readonly string _strResetKey;
        private readonly int _allowedDirections;
        private readonly int _moveTime = 750;

        private int _moveDirection;
        private bool _freezePlayer;
        private bool _isResetting;

        // type 1 sets the key directly on push and resets it on spawn
        // used for the gravestone
        private int _type;

        public ObjMoveStoneBlackBar(Map.Map map, int posX, int posY, int moveDirections, string strKey,
            string spriteId, Rectangle collisionRectangle, int layer, int type, bool freezePlayer, string resetKey) : base(map, spriteId)
        {
            EntityPosition = new CPosition(posX, posY + 16, 0);
            EntitySize = new Rectangle(0, -22, 22, 22);

            _allowedDirections = moveDirections;
            _strKey = strKey;
            _strKeyDir = strKey + "_dir";
            _type = type;
            _freezePlayer = freezePlayer;
            _strResetKey = resetKey;

            _body = new BodyComponent(EntityPosition, 3, -13, 10, 10, 8)
            {
                IgnoreHeight = true,
                IgnoreHoles = true,
            };

            _startPosition = new Vector2(posX, posY + 16);

            // moves the stone
            var movingTrigger = new AiTriggerCountdown(_moveTime, MoveTick, MoveEnd);
            var movedState = new AiState { Init = InitMoved };

            _aiComponent = new AiComponent();
            _aiComponent.States.Add("idle", new AiState());
            _aiComponent.States.Add("moving", new AiState { Trigger = { movingTrigger } });
            _aiComponent.States.Add("moved", movedState);
            new AiFallState(_aiComponent, _body, null, null, 200);
            _aiComponent.ChangeState("idle");

            _box = new CBox(EntityPosition, collisionRectangle.X, collisionRectangle.Y,
                collisionRectangle.Width, collisionRectangle.Height, 16);

            var sprite = Resources.GetSprite(spriteId);

            if (_type == 16)
                AddComponent(UpdateComponent.Index, new UpdateComponent(Update));

            AddComponent(AiComponent.Index, _aiComponent);
            if (moveDirections != 0)
                AddComponent(BodyComponent.Index, _body);
            AddComponent(PushableComponent.Index, new PushableComponent(_box, OnPush) { InertiaTime = 450 });
            AddComponent(CollisionComponent.Index, new BoxCollisionComponent(_box, Values.CollisionTypes.Normal | Values.CollisionTypes.Hookshot));

            if (_type != 16 && _type != 17)
                AddComponent(DrawComponent.Index, new DrawSpriteComponent(spriteId, EntityPosition, new Vector2(0, -sprite.SourceRectangle.Height), layer));
            if (_type == 16 || _type == 17)
                AddComponent(DrawComponent.Index, new DrawComponent(Draw, 3, EntityPosition));

            if (!string.IsNullOrEmpty(_strResetKey))
                AddComponent(KeyChangeListenerComponent.Index, new KeyChangeListenerComponent(OnKeyChange));

            // set the key
            if (_type == 1 && _strKey != null)
                Game1.GameManager.SaveManager.SetString(_strKey, "0");
            if (!string.IsNullOrEmpty(_strKeyDir))
                Game1.GameManager.SaveManager.SetString(_strKeyDir, "-1");
        }

        private void OnKeyChange()
        {
            // check if the block should be moved back to the start position
            var keyState = Game1.GameManager.SaveManager.GetString(_strResetKey);
            if (keyState == "1" && _aiComponent.CurrentStateId == "moved")
                ResetToStart();
        }

        private void ResetToStart()
        {
            _isResetting = true;
            _goalPosition = _startPosition;
            _startPosition = EntityPosition.Position;

            _moveDirection = (_moveDirection + 2) % 4;

            if (!string.IsNullOrEmpty(_strKeyDir))
                Game1.GameManager.SaveManager.SetString(_strKeyDir, "-1");

            ToMoving();
        }

        private bool OnPush(Vector2 direction, PushableComponent.PushType type)
        {
            if (type == PushableComponent.PushType.Impact ||
                _aiComponent.CurrentStateId != "idle")
                return false;

            if (_type == 1 && Game1.GameManager.StoneGrabberLevel <= 0)
                return false;

            _moveDirection = AnimationHelper.GetDirection(direction);

            if (_allowedDirections != -1 && (_allowedDirections & (0x01 << _moveDirection)) == 0)
                return false;

            // only move if there is nothing blocking the way
            var pushVector = AnimationHelper.DirectionOffset[_moveDirection];
            var collidingRectangle = Box.Empty;
            if (Map.Objects.Collision(new Box(
                    EntityPosition.X + pushVector.X * 16,
                    EntityPosition.Y + pushVector.Y * 16 - 16, 0, 16, 16, 16),
                Box.Empty, Values.CollisionTypes.Normal, 0, 0, ref collidingRectangle))
                return true;

            _startPosition = EntityPosition.Position;

            _goalPosition = new Vector2(
                _startPosition.X + pushVector.X * 16,
                _startPosition.Y + pushVector.Y * 16);

            ToMoving();

            if (!string.IsNullOrEmpty(_strResetKey))
                Game1.GameManager.SaveManager.SetString(_strResetKey, "0");

            // set the key
            if (_type == 1 && !string.IsNullOrEmpty(_strKey))
                Game1.GameManager.SaveManager.SetString(_strKey, "1");

            if (_type == 1 && !string.IsNullOrEmpty(_strKeyDir))
                Game1.GameManager.SaveManager.SetString(_strKeyDir, _moveDirection.ToString());

            return true;
        }

        private void Update()
        {
            //if (EntityPosition.Position == _goalPosition || ((MapTransitionSystem)Game1.GameManager.GameSystems[typeof(MapTransitionSystem)]).IsTransitioning())
            //    return;

            //Game1.GameManager.MapManager.UpdateCameraX = false;
            //Game1.GameManager.MapManager.UpdateCameraY = false;
        }

        private void Draw(SpriteBatch spriteBatch)
        {
            if (_type == 16)
            {
                spriteBatch.Draw(Resources.SprWhite, new Rectangle((int)_startPosition.X - 320 + 16 - (int)(_moveAmount * 240), (int)_startPosition.Y - 300, 320, 900), Color.Black);
                spriteBatch.Draw(Resources.SprWhite, new Rectangle((int)_startPosition.X + 160 + 16 + (int)(_moveAmount * 240), (int)_startPosition.Y - 300, 320, 900), Color.Black);
            }

            if (_type == 17)
            {
                spriteBatch.Draw(Resources.SprWhite, new Rectangle((int)_startPosition.X - 300, (int)_startPosition.Y - 16 + (int)(_moveAmount * 160), 900, 240), Color.Black);
                spriteBatch.Draw(Resources.SprWhite, new Rectangle((int)_startPosition.X - 300, (int)_startPosition.Y - 128 - 16 - 240 - (int)(_moveAmount * 160), 900, 240), Color.Black);
            }
        }

        private void ToMoving()
        {
            Game1.GameManager.PlaySoundEffect("D378-17-11");
            _aiComponent.ChangeState("moving");
        }

        float _moveAmount;

        private void MoveTick(double time)
        {
            // the movement is fast in the beginning and slows down at the end
            var amount = (float)Math.Sin((_moveTime - time) / _moveTime * (Math.PI / 2f));
            _moveAmount = amount;

            Move(amount);

            if (!_isResetting && _freezePlayer)
                MapManager.ObjLink.FreezePlayer();
        }

        private void MoveEnd()
        {
            // finished moving
            Move(1);

            Map.Objects.RemoveObject(this);

            // set the key
            if (_type == 0 && !string.IsNullOrEmpty(_strKey))
                Game1.GameManager.SaveManager.SetString(_strKey, "1");
            // set the direction key
            if (_type == 0 && !string.IsNullOrEmpty(_strKeyDir))
                Game1.GameManager.SaveManager.SetString(_strKeyDir, _moveDirection.ToString());

            if (_isResetting)
            {
                _isResetting = false;
                _aiComponent.ChangeState("idle");
            }
            else
                _aiComponent.ChangeState("moved");

            // can fall into holes after finishing the movement animation
            _body.IgnoreHoles = false;
        }

        private void InitMoved()
        {
            // fall into the water
            if (_body.CurrentFieldState.HasFlag(MapStates.FieldStates.DeepWater))
            {
                Game1.GameManager.PlaySoundEffect("D360-14-0E");

                // spawn splash effect
                var fallAnimation = new ObjAnimator(Map,
                    (int)(_body.Position.X + _body.OffsetX + _body.Width / 2.0f),
                    (int)(_body.Position.Y + _body.OffsetY + _body.Height - 2),
                    Values.LayerPlayer, "Particles/fishingSplash", "idle", true);
                Map.Objects.SpawnObject(fallAnimation);

                Map.Objects.DeleteObjects.Add(this);
            }
        }

        private void Move(float amount)
        {
            var lastBox = _box.Box;

            EntityPosition.Set(Vector2.Lerp(_startPosition, _goalPosition, amount));

            // @HACK: this kind of stuff should be inside the movement system

            // check for colliding bodies and push them forward
            _collidingObjects.Clear();
            Map.Objects.GetComponentList(_collidingObjects,
                (int)EntityPosition.Position.X, (int)EntityPosition.Position.Y - 16, 17, 17, BodyComponent.Mask);

            foreach (var collidingObject in _collidingObjects)
            {
                if (collidingObject is ObjMoveStone)
                    continue;

                var body = (BodyComponent)collidingObject.Components[BodyComponent.Index];

                if (body.BodyBox.Box.Intersects(_box.Box) && !body.BodyBox.Box.Intersects(lastBox))
                {
                    var offset = Vector2.Zero;
                    if (_moveDirection == 0)
                        offset.X = _box.Box.Left - body.BodyBox.Box.Right - 0.05f;
                    else if (_moveDirection == 2)
                        offset.X = _box.Box.Right - body.BodyBox.Box.Left + 0.05f;
                    else if (_moveDirection == 1)
                        offset.Y = _box.Box.Back - body.BodyBox.Box.Front - 0.05f;
                    else if (_moveDirection == 3)
                        offset.Y = _box.Box.Front - body.BodyBox.Box.Back + 0.05f;

                    SystemBody.MoveBody(body, offset, body.CollisionTypes, false, false, false);
                    body.Position.NotifyListeners();
                }
            }
        }
    }
}