using System;
using Microsoft.Xna.Framework;
using Protogame;
using Protogame.ATFLevelEditor;

namespace SuperBallBrawlers
{
    public class PlatformEntity : ComponentizedEntity
    {
        private readonly IFirstPersonCamera _firstPersonCamera;
        private readonly IPhysicsWorldControl _physicsWorldControl;
        private readonly Physical3DCylinderComponent _physicsComponent;

        private float _height;
        private float _radius;
        private float _cameraFOVDegrees;
        private float _cameraUpDistance;
        private float _cameraAwayDistance;

        private Vector2 _currentTilt = Vector2.Zero;

        public PlatformEntity(
            IEditorQuery<PlatformEntity> editorQuery,
            Render3DModelComponent renderModelComponent,
            Physical3DCylinderComponent physicalCylinderComponent,
            IAssetManager assetManager,
            IFirstPersonCamera firstPersonCamera,
            IPhysicsWorldControl physicsWorldControl)
        {
            _firstPersonCamera = firstPersonCamera;
            _physicsWorldControl = physicsWorldControl;
            editorQuery.DeclareAsEntity(this);
            editorQuery.MapTransform(this, x => Transform.Assign(x));
            editorQuery.UseModelForRendering(this, "model\\Platform.dae");
            
            editorQuery.MapCustom(this, "height", "Height", x => _height = x, 1f);
            editorQuery.MapCustom(this, "radius", "Radius", x => _radius = x, 100f);
            editorQuery.MapCustom(this, "cameraFOVDegrees", "Camera FOV Degrees", x => _cameraFOVDegrees = x, 60f);
            editorQuery.MapCustom(this, "cameraUpDistance", "Camera Up Distance", x => _cameraUpDistance = x, 30f);
            editorQuery.MapCustom(this, "cameraAwayDistance", "Camera Away Distance", x => _cameraAwayDistance = x, 20f);

            _cameraFOVDegrees = 60;
            _cameraUpDistance = 50f;
            _cameraAwayDistance = 40f;

            if (editorQuery.Mode != EditorQueryMode.BakingSchema)
            {
                renderModelComponent.Model = assetManager.Get<ModelAsset>("model.Platform");

                physicalCylinderComponent.Height = _height;
                physicalCylinderComponent.Radius = _radius;
                physicalCylinderComponent.Static = true;

                _physicsComponent = physicalCylinderComponent;

                RegisterComponent(renderModelComponent);
                RegisterComponent(physicalCylinderComponent);
            }
        }

        public Vector2 TiltRequest { get; set; }

        public override void Update(IGameContext gameContext, IUpdateContext updateContext)
        {
            base.Update(gameContext, updateContext);

            _currentTilt = Vector2.Lerp(_currentTilt, TiltRequest, 0.05f);

            var mat = _physicsComponent.RigidBodies[0].Material;
            mat.KineticFriction = 0.2f;
            mat.StaticFriction = 0f;
            mat.Restitution = 0.5f;
        }

        public override void Prerender(IGameContext gameContext, IRenderContext renderContext)
        {
            if (renderContext.IsCurrentRenderPass<I3DRenderPass>())
            {
                var tiltAmount = 45;

                var awayDistance = -(_cameraAwayDistance - 11.5f);
                var upDistance = _cameraUpDistance;
                
                var basePosition = FinalTransform.AbsolutePosition + new Vector3(-11.5f, 0, 0);
                var upPosition = Vector3.Up;

                var world = (SuperBallBrawlersWorld)gameContext.World;
                switch (world.CurrentState)
                {
                    case WorldState.GameInit:
                    {
                        var by1 = 100;
                        var by2 = 10;
                        var by = MathHelper.SmoothStep(by1, by2, world.StateTransition);
                        basePosition += new Vector3(0, by, 0);
                        break;
                    }
                    case WorldState.TitleScreen:
                    {
                        var by1 = 10;
                        var by2 = 0;
                        var by = MathHelper.SmoothStep(by1, by2, world.StateTransition);
                        basePosition += new Vector3(0, by, 0);
                        break;
                    }
                    case WorldState.GamePlay:
                        break;
                }

                var length = Math.Sqrt(Math.Pow(upDistance, 2) + Math.Pow(awayDistance, 2));
                var angle = Math.Atan2(upDistance, awayDistance);

                switch (world.CurrentState)
                {
                    case WorldState.GameInit:
                        angle += MathHelper.ToRadians(45);
                        break;
                    case WorldState.TitleScreen:
                        angle += MathHelper.ToRadians(MathHelper.SmoothStep(45, -_currentTilt.Y * tiltAmount, world.StateTransition));
                        break;
                    case WorldState.GamePlay:
                        angle += MathHelper.ToRadians(-_currentTilt.Y * tiltAmount);
                        break;
                }

                var adjPosition = new Vector3((float)(Math.Cos(angle) * length), (float)(Math.Sin(angle) * length), 0);

                var manipulateX = MathHelper.ToRadians(-_currentTilt.X * tiltAmount);
                var tiltMatrix = Matrix.CreateFromYawPitchRoll(0, manipulateX, 0);
                adjPosition = Vector3.Transform(adjPosition, tiltMatrix);
                upPosition = Vector3.Transform(upPosition, tiltMatrix);

                var gravityLength = 100f;
                var gravityXAngle = MathHelper.ToRadians(-_currentTilt.X * tiltAmount);
                var gravityYAngle = MathHelper.ToRadians(_currentTilt.Y * tiltAmount);
                var baseGravity = new Vector3(
                    (float)(Math.Sin(gravityYAngle) * gravityLength),
                    (float)(Math.Sqrt(Math.Pow(Math.Cos(gravityXAngle) * gravityLength, 2) * 0.5 + Math.Pow(Math.Cos(gravityYAngle) * gravityLength, 2) * 0.5)),
                    (float)(Math.Sin(gravityXAngle) * gravityLength));
                
                _physicsWorldControl.Gravity = -baseGravity;

                _firstPersonCamera.Apply(
                    renderContext,
                    basePosition + adjPosition,
                    basePosition,
                    upPosition,
                    MathHelper.ToRadians(_cameraFOVDegrees));
            }

            base.Prerender(gameContext, renderContext);
        }
    }
}
