using System;
using System.Linq;
using Jitter.Collision.Shapes;
using Jitter.Dynamics;

namespace SuperBallBrawlers
{
    using Microsoft.Xna.Framework;

    using Protoinject;

    using Protogame;

    public class SuperBallBrawlersWorld : IWorld
    {
        private readonly I2DRenderUtilities _renderUtilities;

        private readonly IAssetManager _assetManager;
        private readonly IHierarchy _hierarchy;
        private readonly IPhysicsWorldControl _physicsWorldControl;
        private readonly IAudioUtilities _audioUtilities;

        private readonly FontAsset _uiFont;

        private readonly bool _missingPlatform;
        private TextureAsset _skyTexture;
        private TextureAsset _uiOverlayTexture;
        private FontAsset _defaultFont;

        private bool _movedToFullscreen;
        private TextureAsset _uiTopLeftTexture;
        private TextureAsset _uiTopRightTexture;
        private TextureAsset _uiBottomLeftTexture;
        private TextureAsset _uiBottomRightTexture;
        private TextureAsset _uiTimerTexture;
        private TextureAsset _uiFirst;
        private TextureAsset _uiSecond;
        private TextureAsset _uiThird;

        private int _step;
        private bool _audioOnceOffPlayed;
        private AudioAsset _audioTitleAsset;
        private AudioAsset _audioAudienceAsset;
        private TextureAsset _title;
        private float _textPulse;
        private TextureAsset _redpoint;
        private AudioAsset _musicMenu;
        private AudioAsset _musicInGame;
        private IAudioHandle _titleAudioInstance;
        private IAudioHandle _musicInGameAudioInstance;
        private IAudioHandle _musicMenuAudioInstance;

        public SuperBallBrawlersWorld(
            I2DRenderUtilities twoDRenderUtilities,
            IAssetManager assetManager,
            ILevelManager levelManager,
            IHierarchy hierarchy,
            IPhysicsWorldControl physicsWorldControl,
            IAudioUtilities audioUtilities)
        {
            _renderUtilities = twoDRenderUtilities;
            _assetManager = assetManager;
            _hierarchy = hierarchy;
            _physicsWorldControl = physicsWorldControl;
            _audioUtilities = audioUtilities;
            _uiFont = _assetManager.Get<FontAsset>("font.GameUI");
            _defaultFont = _assetManager.Get<FontAsset>("font.Default");
            _skyTexture = assetManager.Get<TextureAsset>("texture.Sky");
            _uiTopLeftTexture = assetManager.Get<TextureAsset>("texture.UITopLeft");
            _uiTopRightTexture = assetManager.Get<TextureAsset>("texture.UITopRight");
            _uiBottomLeftTexture = assetManager.Get<TextureAsset>("texture.UIBottomLeft");
            _uiBottomRightTexture = assetManager.Get<TextureAsset>("texture.UIBottomRight");
            _uiTimerTexture = assetManager.Get<TextureAsset>("texture.UITimerBack");
            _uiFirst = assetManager.Get<TextureAsset>("texture.First");
            _uiSecond = assetManager.Get<TextureAsset>("texture.Second");
            _uiThird = assetManager.Get<TextureAsset>("texture.Third");
            _title = assetManager.Get<TextureAsset>("texture.Title");
            _redpoint = assetManager.Get<TextureAsset>("texture.Redpoint");

            _audioTitleAsset = _assetManager.Get<AudioAsset>("sound.Title");
            _audioAudienceAsset = _assetManager.Get<AudioAsset>("sound.Audience");

            _musicMenu = _assetManager.Get<AudioAsset>("sound.MusicMenu");
            _musicInGame = _assetManager.Get<AudioAsset>("sound.MusicInGame");

            var levelAsset = _assetManager.Get<LevelAsset>("level.Basic");
            levelManager.Load(this, levelAsset);

            var initialEntities = this.GetEntitiesForWorld(hierarchy).ToArray();
            _missingPlatform = initialEntities.All(x => !(x is PlatformEntity));

            _physicsWorldControl.Gravity = new Vector3(0f, -100f, 0f);
            _physicsWorldControl.EnableSpeculativeContacts = false;
            //_physicsWorldControl.ConsiderAngularVelocityCallback = ConsiderAngularVelocityCallback;
            _physicsWorldControl.MaterialCoefficientMixing = ContactSettings.MaterialCoefficientMixingType.UseCallback;
            _physicsWorldControl.CalculateStaticFrictionCallback = CalculateStaticFrictionCallback;
            _physicsWorldControl.CalculateDynamicFrictionCallback = CalculateDynamicFrictionCallback;
            _physicsWorldControl.CalculateRestitutionCallback = CalculateRestitutionCallback;

            CurrentState = WorldState.GameInit;
            TargetState = WorldState.TitleScreen;
            StateTransition = 0;
        }

        public WorldState CurrentState { get; set; }

        public WorldState TargetState { get; set; }

        public float StateTransition { get; set; }

        public bool IsChangingStates => CurrentState != TargetState;

        private bool ConsiderAngularVelocityCallback(RigidBody body1, RigidBody body2)
        {
            // Only consider angular velocity if one of the bodies is static (i.e. the ground or ramps).
            return body1.IsStatic || body2.IsStatic;
        }

        private float CalculateStaticFrictionCallback(RigidBody body1, RigidBody body2)
        {
            if (body1.Shape is SphereShape && body2.Shape is SphereShape)
            {
                return 0.08f;
            }
            else if (body1.Shape is CylinderShape || body2.Shape is CylinderShape)
            {
                return 0.2f;
            }

            return 100f;
        }

        private float CalculateDynamicFrictionCallback(RigidBody body1, RigidBody body2)
        {
            return 0f;
        }

        private float CalculateRestitutionCallback(RigidBody body1, RigidBody body2)
        {
            if (body1.Shape is SphereShape && body2.Shape is SphereShape)
            {
                return 2f;
            }

            return 0.5f;
        }

        public void Dispose()
        {
        }

        public void RenderAbove(IGameContext gameContext, IRenderContext renderContext)
        {
        }

        public void RenderBelow(IGameContext gameContext, IRenderContext renderContext)
        {
            if (renderContext.IsFirstRenderPass())
            {
                gameContext.Graphics.GraphicsDevice.Clear(new Color(1, 1, 1, 1));
            }

            if (renderContext.IsCurrentRenderPass<I2DBatchedRenderPass>())
            {
                var renderPass = renderContext.GetCurrentRenderPass<I2DBatchedRenderPass>();
                if (renderPass.Name == "Sky")
                {
                    _renderUtilities.RenderTexture(
                        renderContext,
                        Vector2.Zero,
                        _skyTexture,
                        new Vector2(
                            renderContext.GraphicsDevice.PresentationParameters.BackBufferWidth,
                            renderContext.GraphicsDevice.PresentationParameters.BackBufferHeight));
                }
                else
                {
                    switch (CurrentState)
                    {
                        case WorldState.GameInit:
                            RenderTitleIn(gameContext, renderContext);
                            break;
                        case WorldState.TitleScreen:
                            RenderTitleOut(gameContext, renderContext);
                            break;
                        case WorldState.GamePlay:
                            RenderGamePlay(renderContext);
                            break;
                    }
                }
            }
        }

        private void RenderTitleIn(IGameContext gameContext, IRenderContext renderContext)
        {
            var x = renderContext.GraphicsDevice.PresentationParameters.BackBufferWidth / 2 - _title.Texture.Width / 2;
            var y1 = renderContext.GraphicsDevice.PresentationParameters.BackBufferHeight;
            var y2 = renderContext.GraphicsDevice.PresentationParameters.BackBufferHeight / 20;

            var y = MathHelper.SmoothStep(y1, y2, StateTransition);

            var x1 = -500;
            var x2 = 20;
            var xx = MathHelper.SmoothStep(x1, x2, StateTransition);

            RenderCredits(renderContext, xx);

            _renderUtilities.RenderTexture(
                renderContext,
                new Vector2(x, y),
                _title);
        }

        private void RenderTitleOut(IGameContext gameContext, IRenderContext renderContext)
        {
            var x = renderContext.GraphicsDevice.PresentationParameters.BackBufferWidth / 2 - _title.Texture.Width / 2;
            var y1 = renderContext.GraphicsDevice.PresentationParameters.BackBufferHeight / 20;
            var y2 = -(_title.Texture.Height + (renderContext.GraphicsDevice.PresentationParameters.BackBufferHeight / 20) + 120);

            var y = MathHelper.SmoothStep(y1, y2, StateTransition);

            var x1 = 20;
            var x2 = -500;
            var xx = MathHelper.SmoothStep(x1, x2, StateTransition);

            RenderCredits(renderContext, xx);

            _renderUtilities.RenderTexture(
                renderContext,
                new Vector2(x, y),
                _title);

            _textPulse++;
            var c = ((float)Math.Cos(((_textPulse % 60) / 60f) * MathHelper.TwoPi) + 1) / 2;

            _renderUtilities.RenderText(
                renderContext,
                new Vector2(renderContext.GraphicsDevice.PresentationParameters.BackBufferWidth / 2, y + _title.Texture.Height + (renderContext.GraphicsDevice.PresentationParameters.BackBufferHeight / 15)),
                "PRESS START",
                _uiFont,
                HorizontalAlignment.Center,
                VerticalAlignment.Center,
                new Color(new Vector4(0, 0, 0, c)),
                false);
        }

        private void RenderCredits(IRenderContext renderContext, float xx)
        {
            var logoY = 11 + 17;
            var creditsY = 80 + 17 + 50;

            _renderUtilities.RenderTexture(
                renderContext,
                new Vector2(xx, logoY),
                _redpoint);
            _renderUtilities.RenderText(
                renderContext,
                new Vector2(xx, logoY + 62 + 10),
                "https://redpoint.games",
                _defaultFont,
                textColor: Color.Blue,
                renderShadow: false);
            _renderUtilities.RenderText(
                renderContext,
                new Vector2(xx, creditsY),
                "Programming: June Rhodes",
                _defaultFont,
                textColor: Color.Black,
                renderShadow: false);
            _renderUtilities.RenderText(
                renderContext,
                new Vector2(xx, creditsY + 20),
                "Design: Josh Harvey",
                _defaultFont,
                textColor: Color.Black,
                renderShadow: false);
            _renderUtilities.RenderText(
                renderContext,
                new Vector2(xx, creditsY + 40),
                "Art: Liam Alkamraikhi",
                _defaultFont,
                textColor: Color.Black,
                renderShadow: false);
            _renderUtilities.RenderText(
                renderContext,
                new Vector2(xx, creditsY + 60),
                "Sound Effects: Abby Phillips",
                _defaultFont,
                textColor: Color.Black,
                renderShadow: false);
            _renderUtilities.RenderText(
                renderContext,
                new Vector2(xx, creditsY + 80),
                "Music: Andrew Kasapidis",
                _defaultFont,
                textColor: Color.Black,
                renderShadow: false);
            _renderUtilities.RenderText(
                renderContext,
                new Vector2(xx, creditsY + 100),
                "Voice Over: Kevin Powe",
                _defaultFont,
                textColor: Color.Black,
                renderShadow: false);
            _renderUtilities.RenderText(
                renderContext,
                new Vector2(xx, creditsY + 140),
                "Made at the Global Game Jam 2017",
                _defaultFont,
                textColor: Color.Black,
                renderShadow: false);

            var controlsY = creditsY + 190;
            _renderUtilities.RenderText(
                renderContext,
                new Vector2(xx, controlsY),
                "Left Analog Stick: Move Ball",
                _defaultFont,
                textColor: Color.Black,
                renderShadow: false);
            _renderUtilities.RenderText(
                renderContext,
                new Vector2(xx, controlsY + 20),
                "Right Analog Stick: Tilt Board",
                _defaultFont,
                textColor: Color.Black,
                renderShadow: false);

            _renderUtilities.RenderText(
                renderContext,
                new Vector2(xx, renderContext.GraphicsDevice.PresentationParameters.BackBufferHeight - 60),
                "Made with Protogame",
                _defaultFont,
                textColor: Color.Black,
                renderShadow: false);
            _renderUtilities.RenderText(
                renderContext,
                new Vector2(xx, renderContext.GraphicsDevice.PresentationParameters.BackBufferHeight - 60 + 20),
                "https://protogame.org",
                _defaultFont,
                textColor: Color.Blue,
                renderShadow: false);
        }

        private void RenderGamePlay(IRenderContext renderContext)
        {
            var w = renderContext.GraphicsDevice.PresentationParameters.BackBufferWidth;
            var h = renderContext.GraphicsDevice.PresentationParameters.BackBufferHeight;

            _renderUtilities.RenderTexture(
                renderContext,
                Vector2.Zero,
                _uiTopLeftTexture);
            _renderUtilities.RenderTexture(
                renderContext,
                new Vector2(w - _uiTopRightTexture.Texture.Width, 0),
                _uiTopRightTexture);
            _renderUtilities.RenderTexture(
                renderContext,
                new Vector2(0, h - _uiTopRightTexture.Texture.Height),
                _uiBottomLeftTexture);
            _renderUtilities.RenderTexture(
                renderContext,
                new Vector2(w - _uiTopRightTexture.Texture.Width, h - _uiTopRightTexture.Texture.Height),
                _uiBottomRightTexture);

            var entities = this.GetEntitiesForWorld(_hierarchy).ToArray();
            var p1 = entities.OfType<PlayerEntity>().FirstOrDefault(x => x.Index == 0);
            var p2 = entities.OfType<PlayerEntity>().FirstOrDefault(x => x.Index == 1);
            var p3 = entities.OfType<PlayerEntity>().FirstOrDefault(x => x.Index == 2);
            var p4 = entities.OfType<PlayerEntity>().FirstOrDefault(x => x.Index == 3);
            var orderedPlayers =
                entities.OfType<PlayerEntity>().OrderByDescending(x => x.Score).ToArray();

            var scoreScale = 60;

            var baseOffset = 20;

            this._renderUtilities.RenderText(
                renderContext,
                new Vector2(110 + 20, 40 + baseOffset),
                ((p1?.Score ?? 0) / scoreScale).ToString(),
                this._uiFont,
                textColor: Color.White,
                renderShadow: false,
                horizontalAlignment: HorizontalAlignment.Center,
                verticalAlignment: VerticalAlignment.Center);
            DrawMedal(renderContext, orderedPlayers, p1, new Vector2(20, 40 + baseOffset + 60));

            if (p1 != null && p1.Penalty > 0)
            {
                this._renderUtilities.RenderText(
                    renderContext,
                    new Vector2(110 + 20, 40 + baseOffset + 80 + p1.PenaltyCounter),
                    (p1.Penalty / scoreScale).ToString(),
                    this._uiFont,
                    textColor: Color.Red,
                    renderShadow: false,
                    horizontalAlignment: HorizontalAlignment.Center,
                    verticalAlignment: VerticalAlignment.Center);
                p1.PenaltyCounter++;
                if (p1.PenaltyCounter > 60)
                {
                    p1.PenaltyCounter = 0;
                    p1.Penalty = 0;
                }
            }

            this._renderUtilities.RenderText(
                renderContext,
                new Vector2(w - 75 + 20, 40 + baseOffset),
                ((p2?.Score ?? 0) / scoreScale).ToString(),
                this._uiFont,
                textColor: Color.White,
                renderShadow: false,
                horizontalAlignment: HorizontalAlignment.Center,
                verticalAlignment: VerticalAlignment.Center);
            DrawMedal(renderContext, orderedPlayers, p2, new Vector2(w - 75 + 20, 40 + baseOffset + 60));

            if (p2 != null && p2.Penalty > 0)
            {
                this._renderUtilities.RenderText(
                    renderContext,
                    new Vector2(w - 75 + 20, 40 + baseOffset + 80 + p2.PenaltyCounter),
                    (p2.Penalty / scoreScale).ToString(),
                    this._uiFont,
                    textColor: Color.Red,
                    renderShadow: false,
                    horizontalAlignment: HorizontalAlignment.Center,
                    verticalAlignment: VerticalAlignment.Center);
                p2.PenaltyCounter++;
                if (p2.PenaltyCounter > 60)
                {
                    p2.PenaltyCounter = 0;
                    p2.Penalty = 0;
                }
            }

            this._renderUtilities.RenderText(
                renderContext,
                new Vector2(110 + 20, h - 94 + 40 + baseOffset),
                ((p3?.Score ?? 0) / scoreScale).ToString(),
                this._uiFont,
                textColor: Color.White,
                renderShadow: false,
                horizontalAlignment: HorizontalAlignment.Center,
                verticalAlignment: VerticalAlignment.Center);
            DrawMedal(renderContext, orderedPlayers, p3, new Vector2(20, h - 94 + 40 + baseOffset - 80 - 40));

            if (p3 != null && p3.Penalty > 0)
            {
                this._renderUtilities.RenderText(
                    renderContext,
                    new Vector2(110 + 20, h - 94 + 40 + baseOffset - 80 - p3.PenaltyCounter),
                    (p3.Penalty / scoreScale).ToString(),
                    this._uiFont,
                    textColor: Color.Red,
                    renderShadow: false,
                    horizontalAlignment: HorizontalAlignment.Center,
                    verticalAlignment: VerticalAlignment.Center);
                p3.PenaltyCounter++;
                if (p3.PenaltyCounter > 60)
                {
                    p3.PenaltyCounter = 0;
                    p3.Penalty = 0;
                }
            }

            this._renderUtilities.RenderText(
                renderContext,
                new Vector2(w - 75 + 20, h - 94 + 40 + baseOffset),
                ((p4?.Score ?? 0) / scoreScale).ToString(),
                this._uiFont,
                textColor: Color.White,
                renderShadow: false,
                horizontalAlignment: HorizontalAlignment.Center,
                verticalAlignment: VerticalAlignment.Center);
            DrawMedal(renderContext, orderedPlayers, p4, new Vector2(w - 75 + 20, h - 94 + 40 + baseOffset - 80 - 40));

            if (p4 != null && p4.Penalty > 0)
            {
                this._renderUtilities.RenderText(
                    renderContext,
                    new Vector2(w - 75 + 20, h - 94 + 40 + baseOffset - 80 - p4.PenaltyCounter),
                    (p4.Penalty / scoreScale).ToString(),
                    this._uiFont,
                    textColor: Color.Red,
                    renderShadow: false,
                    horizontalAlignment: HorizontalAlignment.Center,
                    verticalAlignment: VerticalAlignment.Center);
                p4.PenaltyCounter++;
                if (p4.PenaltyCounter > 60)
                {
                    p4.PenaltyCounter = 0;
                    p4.Penalty = 0;
                }
            }
        }

        private void DrawMedal(IRenderContext renderContext, PlayerEntity[] orderedPlayers, PlayerEntity playerEntity, Vector2 pos)
        {
            if (orderedPlayers[0] == playerEntity)
            {
                _renderUtilities.RenderTexture(
                    renderContext,
                    pos,
                    _uiFirst);
            }
            if (orderedPlayers[1] == playerEntity)
            {
                _renderUtilities.RenderTexture(
                    renderContext,
                    pos,
                    _uiSecond);
            }
            if (orderedPlayers[2] == playerEntity)
            {
                _renderUtilities.RenderTexture(
                    renderContext,
                    pos,
                    _uiThird);
            }
        }

        public void Update(IGameContext gameContext, IUpdateContext updateContext)
        {
            _step++;

            if (_step > 120)
            {
                if (!IsChangingStates && CurrentState == WorldState.GameInit)
                {
                    TargetState = WorldState.TitleScreen;
                }

                if (CurrentState != TargetState)
                {
                    StateTransition += 1 / 60f;

                    if (StateTransition >= 1f)
                    {
                        CurrentState = TargetState;
                        StateTransition = 0f;
                    }
                }

                if (_musicInGameAudioInstance != null)
                {
                    if (IsChangingStates || CurrentState == WorldState.TitleScreen)
                    {
                        if (TargetState == WorldState.GamePlay)
                        {
                            _musicInGameAudioInstance.Volume = StateTransition;
                            _musicMenuAudioInstance.Volume = 1 - StateTransition;

                            if (_musicMenuAudioInstance.Volume < 0.1f)
                            {
                                _musicMenuAudioInstance.Stop();
                            }
                            if (!_musicInGameAudioInstance.IsPlaying)
                            {
                                _musicInGameAudioInstance.Loop();
                            }
                        }
                        else if (CurrentState == WorldState.GamePlay)
                        {
                            _musicInGameAudioInstance.Volume = 1 - StateTransition;
                            _musicMenuAudioInstance.Volume = StateTransition;

                            if (_musicInGameAudioInstance.Volume < 0.1f)
                            {
                                _musicInGameAudioInstance.Stop();
                            }
                            if (!_musicMenuAudioInstance.IsPlaying)
                            {
                                _musicMenuAudioInstance.Loop();
                            }
                        }
                        else if (CurrentState == WorldState.TitleScreen)
                        {
                            _musicMenuAudioInstance.Volume = MathHelper.Clamp(_musicMenuAudioInstance.Volume + 1 / 60f, 0, 1);
                            if (_musicMenuAudioInstance.Volume >= 1)
                            {
                                _musicMenuAudioInstance.Volume = 1;
                            }
                        }
                    }
                }
                else if (_titleAudioInstance != null && !_titleAudioInstance.IsPlaying && _musicInGameAudioInstance == null)
                {
                    _musicInGameAudioInstance = _audioUtilities.GetHandle(_musicInGame);
                    _musicInGameAudioInstance.Volume = 0f;
                    _musicMenuAudioInstance = _audioUtilities.Loop(_musicMenu);
                    _musicMenuAudioInstance.Volume = 0f;
                }

                switch (CurrentState)
                {
                    case WorldState.GameInit:
                        if (!_audioOnceOffPlayed)
                        {
                            _titleAudioInstance = _audioUtilities.Play(_audioTitleAsset);
                            //_audioUtilities.Loop(_audioAudienceAsset);
                            _audioOnceOffPlayed = true;
                        }
                    break;
                case WorldState.GamePlay:
                    var entities = this.GetEntitiesForWorld(_hierarchy).ToArray();
                    var tiltRequests = entities.OfType<PlayerEntity>().Select(x => x.TiltRequest).ToArray();
                    var avgX = MathHelper.Clamp(tiltRequests.Select(x => x.X / 2).DefaultIfEmpty(0).Sum(), -1, 1);
                    var avgY = MathHelper.Clamp(tiltRequests.Select(x => x.Y / 2).DefaultIfEmpty(0).Sum(), -1, 1);

                    var platform = entities.OfType<PlatformEntity>().FirstOrDefault();
                    if (platform != null)
                    {
                        platform.TiltRequest = new Vector2(avgX, avgY) * 0.5f;
                    }
                    break;
                }
            }
        }
    }
}
