using System;
using Jitter.Dynamics;
using Jitter.LinearMath;
using Microsoft.Xna.Framework;
using Protogame;
using Protoinject;
using Material = Protogame.Material;

namespace SuperBallBrawlers
{
    public class PlayerEntity : ComponentizedEntity
    {
        private readonly Physical3DSphereComponent _physicsComponent;
        private readonly IAudioUtilities _audioUtilities;
        private readonly IEntityFactory _entityFactory;
        private readonly IHierarchy _hierarchy;
        private readonly string _texture;
        private readonly int _index;

        private Vector3 _startPosition;
        private Vector2 _nextTiltRequest;

        private bool _wantsMovement = false;
        private bool _isKeyboardControlled = false;
        private AudioAsset _audioBallClash;
        private AudioAsset _audioBallRollingLoopable;
        private IAudioHandle _audioBallRollingHandle;

        private float _volumeMultiplier = 1f;
        private AudioAsset _audioBallOnBall;
        private AudioAsset _audioBallOffTable;

        public PlayerEntity(
            Render3DModelComponent modelComponent,
            Physical3DSphereComponent physicsComponent,
            IAssetManager assetManager,
            IAudioUtilities audioUtilities,
            IEntityFactory entityFactory,
            IHierarchy hierarchy,
            string texture, 
            int index)
        {
            modelComponent.Model = assetManager.Get<ModelAsset>("model.BaseBall");

            _audioBallClash = assetManager.Get<AudioAsset>("sound.BallClash");
            _audioBallOnBall = assetManager.Get<AudioAsset>("sound.BallOnBall");
            _audioBallOffTable = assetManager.Get<AudioAsset>("sound.Pop");
            _audioBallRollingLoopable = assetManager.Get<AudioAsset>("sound.BallRollLoopable");

            if (!string.IsNullOrWhiteSpace(texture))
            {
                var textureAsset = assetManager.TryGet<TextureAsset>(texture);
                if (textureAsset != null)
                {
                    modelComponent.OverrideMaterial = new Material(modelComponent.ModelInstance.Material);
                    modelComponent.OverrideMaterial.TextureDiffuse = new MaterialTexture
                    {
                        TextureAsset = textureAsset
                    };
                }
            }

            _physicsComponent = physicsComponent;
            _audioUtilities = audioUtilities;
            _entityFactory = entityFactory;
            _hierarchy = hierarchy;
            _texture = texture;
            _physicsComponent.Radius = 1.5f;
            _index = index;
            _physicsComponent.Target = PhysicsTarget.ComponentOwner;
            _physicsComponent.Mass = 0.1f;

            TiltRequest = Vector2.Zero;
            Score = 0;

            _audioBallRollingHandle = _audioUtilities.Loop(_audioBallRollingLoopable);
            _audioBallRollingHandle.Volume = 0f;
        }

        public Vector2 TiltRequest { get; set; }
        public int Index => _index;
        public int Score { get; set; }
        public int Penalty { get; set; }
        public int PenaltyCounter { get; set; }

        public void SetStartPosition(Vector3 position)
        {
            _startPosition = Transform.LocalPosition = position;
        }

        public void ResetState()
        {
            Score = 0;
            Penalty = 0;
            PenaltyCounter = 0;
            Transform.LocalPosition = _startPosition;
            _physicsComponent.RigidBodies[0].LinearVelocity = JVector.Zero;
            _physicsComponent.RigidBodies[0].AngularVelocity = JVector.Zero;
        }

        public override void Update(IGameContext gameContext, IUpdateContext updateContext)
        {
            var world = (SuperBallBrawlersWorld)gameContext.World;
            var shouldHandleScoring = false;
            if (!(!world.IsChangingStates && world.CurrentState == WorldState.GamePlay))
            {
                // Don't update unless in gameplay.
                Score = 0;
                Penalty = 0;
                PenaltyCounter = 0;
            }
            else
            {
                Score++;
                shouldHandleScoring = true;
            }

            var mat = _physicsComponent.RigidBodies[0].Material;
            mat.KineticFriction = 0.08f;
            mat.StaticFriction = 0f;
            mat.Restitution = 2f;//0.98f;

            var linVec = _physicsComponent.RigidBodies[0].LinearVelocity.ToXNAVector();
            var angVec = _physicsComponent.RigidBodies[0].AngularVelocity.ToXNAVector();

            if (linVec.Length() >= 0.0001f)
            {
                var dragForceMagnitude = linVec.LengthSquared() * 0.0005f;
                var dragForceVector = dragForceMagnitude * -Vector3.Normalize(linVec);

                _physicsComponent.RigidBodies[0].AddForce(dragForceVector.ToJitterVector());
            }
            
            if (angVec.Length() >= 0.0001f)
            {
                var dragForceMagnitude = angVec.LengthSquared() * (_wantsMovement ? 0.001f : 0.01f);
                var dragForceVector = dragForceMagnitude * -Vector3.Normalize(angVec);
                
                _physicsComponent.RigidBodies[0].AddTorque(dragForceVector.ToJitterVector());
            }

            _audioBallRollingHandle.Volume = MathHelper.Clamp(angVec.Length() / 150, 0, 1) * _volumeMultiplier;

            _physicsComponent.RigidBodies[0].IsActive = true;

            _wantsMovement = false;

            if (FinalTransform.AbsolutePosition.Y < -2)
            {
                var momentum = _physicsComponent.RigidBodies[0].LinearVelocity;

                var worldNode = _hierarchy.Lookup(gameContext.World);
                _hierarchy.MoveNode(worldNode, _hierarchy.Lookup(_entityFactory.CreatePlayerPoppedEntity("model.PopBallNX", _texture, Transform.LocalPosition, momentum, Vector3.Up)));
                _hierarchy.MoveNode(worldNode, _hierarchy.Lookup(_entityFactory.CreatePlayerPoppedEntity("model.PopBallNY", _texture, Transform.LocalPosition, momentum, Vector3.Left)));
                _hierarchy.MoveNode(worldNode, _hierarchy.Lookup(_entityFactory.CreatePlayerPoppedEntity("model.PopBallNZ", _texture, Transform.LocalPosition, momentum, Vector3.Forward)));
                _hierarchy.MoveNode(worldNode, _hierarchy.Lookup(_entityFactory.CreatePlayerPoppedEntity("model.PopBallPX", _texture, Transform.LocalPosition, momentum, Vector3.Down)));
                _hierarchy.MoveNode(worldNode, _hierarchy.Lookup(_entityFactory.CreatePlayerPoppedEntity("model.PopBallPY", _texture, Transform.LocalPosition, momentum, Vector3.Right)));
                _hierarchy.MoveNode(worldNode, _hierarchy.Lookup(_entityFactory.CreatePlayerPoppedEntity("model.PopBallPZ", _texture, Transform.LocalPosition, momentum, Vector3.Backward)));

                Transform.LocalPosition = new Vector3(0, 2, 0);
                _physicsComponent.RigidBodies[0].LinearVelocity = JVector.Zero;
                _physicsComponent.RigidBodies[0].AngularVelocity = JVector.Zero;
                if (shouldHandleScoring)
                {
                    Penalty = Math.Max(60, (int) (Score * 0.25f));
                    Score -= Penalty;
                    if (Score < 0)
                    {
                        Score = 0;
                    }
                }
                _audioUtilities.Play(_audioBallOffTable);
            }

            TiltRequest = _nextTiltRequest;
            _nextTiltRequest = Vector2.Zero;
            
            base.Update(gameContext, updateContext);
        }

        public override bool Handle(IGameContext context, IEventEngine<IGameContext> eventEngine, Event @event)
        {
            float x = 0;
            float y = 0;
            float tx = 0;
            float ty = 0;

            var gamePadEvent = @event as GamePadThumbstickActiveEvent;
            if (gamePadEvent != null)
            {
                if (gamePadEvent.GamePadIndex == _index)
                {
                    x = gamePadEvent.GamePadState.ThumbSticks.Left.X;
                    y = -gamePadEvent.GamePadState.ThumbSticks.Left.Y;
                    tx = gamePadEvent.GamePadState.ThumbSticks.Right.X;
                    ty = -gamePadEvent.GamePadState.ThumbSticks.Right.Y;
                    _isKeyboardControlled = false;
                }
            }

            if (_index == 0)
            {
                var keyHeldEvent = @event as KeyHeldEvent;
                if (keyHeldEvent != null)
                {
                    if (keyHeldEvent.Key == Microsoft.Xna.Framework.Input.Keys.Escape)
                    {
                        // TODO: Put this somewhere more sensible.
                        context.Game.Exit();
                    }
                    if (keyHeldEvent.Key == Microsoft.Xna.Framework.Input.Keys.Left)
                    {
                        x -= 1;
                        _isKeyboardControlled = true;
                    }
                    if (keyHeldEvent.Key == Microsoft.Xna.Framework.Input.Keys.Right)
                    {
                        x += 1;
                        _isKeyboardControlled = true;
                    }
                    if (keyHeldEvent.Key == Microsoft.Xna.Framework.Input.Keys.Up)
                    {
                        y -= 1;
                        _isKeyboardControlled = true;
                    }
                    if (keyHeldEvent.Key == Microsoft.Xna.Framework.Input.Keys.Down)
                    {
                        y += 1;
                        _isKeyboardControlled = true;
                    }
                    if (keyHeldEvent.Key == Microsoft.Xna.Framework.Input.Keys.A)
                    {
                        tx -= 1;
                        _isKeyboardControlled = true;
                    }
                    if (keyHeldEvent.Key == Microsoft.Xna.Framework.Input.Keys.D)
                    {
                        tx += 1;
                        _isKeyboardControlled = true;
                    }
                    if (keyHeldEvent.Key == Microsoft.Xna.Framework.Input.Keys.W)
                    {
                        ty -= 1;
                        _isKeyboardControlled = true;
                    }
                    if (keyHeldEvent.Key == Microsoft.Xna.Framework.Input.Keys.S)
                    {
                        ty += 1;
                        _isKeyboardControlled = true;
                    }
                }
            }

            _nextTiltRequest += new Vector2(tx, ty);

            if (Math.Abs(x) > 0.001f || Math.Abs(y) > 0.001f)
            {
                _physicsComponent.RigidBodies[0].AddForce((new Vector3(-y, 0, x) * 10f).ToJitterVector());
                _physicsComponent.RigidBodies[0].AddTorque((new Vector3(x, 0, y) * 10f).ToJitterVector());
                _wantsMovement = true;
            }

            return base.Handle(context, eventEngine, @event);
        }

        public override void CollisionBegin(IGameContext gameContext, IServerContext serverContext, IUpdateContext updateContext, object owner1,
            object owner2, RigidBody body1, RigidBody body2)
        {
            if (owner1 == this && owner2 is PlayerEntity)
            {
                _audioUtilities.Play(_audioBallOnBall);
            }

            if ((owner1 == this && owner2 is PlatformEntity) ||
                (owner1 is PlatformEntity && owner2 == this))
            {
                if (_physicsComponent.RigidBodies[0].LinearVelocity.Length() > 100f || Math.Abs(_physicsComponent.RigidBodies[0].LinearVelocity.Y) > 20f)
                {
                    _audioUtilities.Play(_audioBallClash);
                }

                _volumeMultiplier = 1f;
            }

            base.CollisionBegin(gameContext, serverContext, updateContext, owner1, owner2, body1, body2);
        }

        public override void CollisionEnd(IGameContext gameContext, IServerContext serverContext, IUpdateContext updateContext, object owner1,
            object owner2, RigidBody body1, RigidBody body2)
        {
            if ((owner1 == this && owner2 is PlatformEntity) ||
                (owner1 is PlatformEntity && owner2 == this))
            {
                _volumeMultiplier = 0f;
            }

            base.CollisionEnd(gameContext, serverContext, updateContext, owner1, owner2, body1, body2);
        }
    }
}
