using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using OpenDiablo2.Common;
using OpenDiablo2.Common.Attributes;
using OpenDiablo2.Common.Enums;
using OpenDiablo2.Common.Interfaces;
using OpenDiablo2.Core.UI;

namespace OpenDiablo2.Scenes
{
    enum eHero
    {
        Barbarian,
        Necromancer,
        Paladin,
        Assassin,
        Sorceress,
        Amazon,
        Druid
    }

    enum eHeroStance
    {
        Idle,
        IdleSelected,
        Approaching,
        Selected,
        Retreating
    }

    class HeroRenderInfo
    {
        public ISprite IdleSprite, IdleSelectedSprite, ForwardWalkSprite, ForwardWalkSpriteOverlay, SelectedSprite, SelectedSpriteOverlay, BackWalkSprite, BackWalkSpriteOverlay;
        public eHeroStance Stance;
        public long ForwardWalkTimeMs, BackWalkTimeMs;
        public long SpecialFrameTime;
        public Rectangle SelectionBounds = new Rectangle();
    }

    [Scene("Select Hero Class")]
    public sealed class SelectHeroClass : IScene
    {
        static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

        private readonly IRenderWindow renderWindow;
        private readonly IPaletteProvider paletteProvider;
        private readonly IMPQProvider mpqProvider;
        private readonly IMouseInfoProvider mouseInfoProvider;
        private readonly IMusicProvider musicProvider;
        private readonly ISceneManager sceneManager;
        private readonly ITextDictionary textDictionary;

        private bool showEntryUi = false;
        private eHero? selectedHero = null;
        private float secondTimer;
        private ISprite backgroundSprite, campfireSprite;
        private IFont headingFont, heroDescFont;
        private ILabel headingLabel, heroClassLabel, heroDesc1Label, heroDesc2Label, heroDesc3Label;
        private Button exitButton, okButton;
        private Dictionary<eHero, HeroRenderInfo> heroRenderInfo = new Dictionary<eHero, HeroRenderInfo>();

        public SelectHeroClass(
            IRenderWindow renderWindow,
            IPaletteProvider paletteProvider,
            IMPQProvider mpqProvider,
            IMouseInfoProvider mouseInfoProvider,
            IMusicProvider musicProvider,
            ISceneManager sceneManager,
            Func<eButtonType, Button> createButton,
            ITextDictionary textDictionary
            )
        {
            this.renderWindow = renderWindow;
            this.paletteProvider = paletteProvider;
            this.mpqProvider = mpqProvider;
            this.mouseInfoProvider = mouseInfoProvider;
            this.sceneManager = sceneManager;
            this.textDictionary = textDictionary;


            backgroundSprite = renderWindow.LoadSprite(ResourcePaths.CharacterSelectBackground, Palettes.Fechar);
            campfireSprite = renderWindow.LoadSprite(ResourcePaths.CharacterSelectCampfire, Palettes.Fechar, new Point(380, 335));
            campfireSprite.Blend = true;

            heroRenderInfo[eHero.Barbarian] = new HeroRenderInfo
            {
                Stance = eHeroStance.Idle,
                IdleSprite = renderWindow.LoadSprite(ResourcePaths.CharacterSelectBarbarianUnselected, Palettes.Fechar, new Point(400, 330)),
                IdleSelectedSprite = renderWindow.LoadSprite(ResourcePaths.CharacterSelectBarbarianUnselectedH, Palettes.Fechar, new Point(400, 330)),
                ForwardWalkSprite = renderWindow.LoadSprite(ResourcePaths.CharacterSelectBarbarianForwardWalk, Palettes.Fechar, new Point(400, 330)),
                ForwardWalkSpriteOverlay = renderWindow.LoadSprite(ResourcePaths.CharacterSelectBarbarianForwardWalkOverlay, Palettes.Fechar, new Point(400, 330)),
                SelectedSprite = renderWindow.LoadSprite(ResourcePaths.CharacterSelectBarbarianSelected, Palettes.Fechar, new Point(400, 330)),
                BackWalkSprite = renderWindow.LoadSprite(ResourcePaths.CharacterSelectBarbarianBackWalk, Palettes.Fechar, new Point(400, 330)),
                SelectionBounds = new Rectangle(364, 201, 90, 170),
                ForwardWalkTimeMs = 3000,
                BackWalkTimeMs = 1000
            };

            heroRenderInfo[eHero.Sorceress] = new HeroRenderInfo
            {
                Stance = eHeroStance.Idle,
                IdleSprite = renderWindow.LoadSprite(ResourcePaths.CharacterSelecSorceressUnselected, Palettes.Fechar, new Point(626, 352)),
                IdleSelectedSprite = renderWindow.LoadSprite(ResourcePaths.CharacterSelecSorceressUnselectedH, Palettes.Fechar, new Point(626, 352)),
                ForwardWalkSprite = renderWindow.LoadSprite(ResourcePaths.CharacterSelecSorceressForwardWalk, Palettes.Fechar, new Point(626, 352)),
                ForwardWalkSpriteOverlay = renderWindow.LoadSprite(ResourcePaths.CharacterSelecSorceressForwardWalkOverlay, Palettes.Fechar, new Point(626, 352)),
                SelectedSprite = renderWindow.LoadSprite(ResourcePaths.CharacterSelecSorceressSelected, Palettes.Fechar, new Point(626, 352)),
                SelectedSpriteOverlay = renderWindow.LoadSprite(ResourcePaths.CharacterSelecSorceressSelectedOverlay, Palettes.Fechar, new Point(626, 352)),
                BackWalkSprite = renderWindow.LoadSprite(ResourcePaths.CharacterSelecSorceressBackWalk, Palettes.Fechar, new Point(626, 352)),
                BackWalkSpriteOverlay = renderWindow.LoadSprite(ResourcePaths.CharacterSelecSorceressBackWalkOverlay, Palettes.Fechar, new Point(626, 352)),
                SelectionBounds = new Rectangle(580, 240, 65, 160),
                ForwardWalkTimeMs = 3000,
                BackWalkTimeMs = 1200
            };
            heroRenderInfo[eHero.Sorceress].SelectedSpriteOverlay.Blend = true;
            heroRenderInfo[eHero.Sorceress].ForwardWalkSpriteOverlay.Blend = true;
            heroRenderInfo[eHero.Sorceress].BackWalkSpriteOverlay.Blend = true;


            heroRenderInfo[eHero.Necromancer] = new HeroRenderInfo
            {
                Stance = eHeroStance.Idle,
                IdleSprite = renderWindow.LoadSprite(ResourcePaths.CharacterSelectNecromancerUnselected, Palettes.Fechar, new Point(300, 335)),
                IdleSelectedSprite = renderWindow.LoadSprite(ResourcePaths.CharacterSelectNecromancerUnselectedH, Palettes.Fechar, new Point(300, 335)),
                ForwardWalkSprite = renderWindow.LoadSprite(ResourcePaths.CharacterSelecNecromancerForwardWalk, Palettes.Fechar, new Point(300, 335)),
                ForwardWalkSpriteOverlay = renderWindow.LoadSprite(ResourcePaths.CharacterSelecNecromancerForwardWalkOverlay, Palettes.Fechar, new Point(300, 335)),
                SelectedSprite = renderWindow.LoadSprite(ResourcePaths.CharacterSelecNecromancerSelected, Palettes.Fechar, new Point(300, 335)),
                SelectedSpriteOverlay = renderWindow.LoadSprite(ResourcePaths.CharacterSelecNecromancerSelectedOverlay, Palettes.Fechar, new Point(300, 335)),
                BackWalkSprite = renderWindow.LoadSprite(ResourcePaths.CharacterSelecNecromancerBackWalk, Palettes.Fechar, new Point(300, 335)),
                BackWalkSpriteOverlay = renderWindow.LoadSprite(ResourcePaths.CharacterSelecNecromancerBackWalkOverlay, Palettes.Fechar, new Point(300, 335)),
                SelectionBounds = new Rectangle(265, 220, 55, 175),
                ForwardWalkTimeMs = 3000,
                BackWalkTimeMs = 1500,
            };
            heroRenderInfo[eHero.Necromancer].ForwardWalkSpriteOverlay.Blend = true;
            heroRenderInfo[eHero.Necromancer].BackWalkSpriteOverlay.Blend = true;
            heroRenderInfo[eHero.Necromancer].SelectedSpriteOverlay.Blend = true;

            heroRenderInfo[eHero.Paladin] = new HeroRenderInfo
            {
                Stance = eHeroStance.Idle,
                IdleSprite = renderWindow.LoadSprite(ResourcePaths.CharacterSelectPaladinUnselected, Palettes.Fechar, new Point(521, 338)),
                IdleSelectedSprite = renderWindow.LoadSprite(ResourcePaths.CharacterSelectPaladinUnselectedH, Palettes.Fechar, new Point(521, 338)),
                ForwardWalkSprite = renderWindow.LoadSprite(ResourcePaths.CharacterSelecPaladinForwardWalk, Palettes.Fechar, new Point(521, 338)),
                ForwardWalkSpriteOverlay = renderWindow.LoadSprite(ResourcePaths.CharacterSelecPaladinForwardWalkOverlay, Palettes.Fechar, new Point(521, 338)),
                SelectedSprite = renderWindow.LoadSprite(ResourcePaths.CharacterSelecPaladinSelected, Palettes.Fechar, new Point(521, 338)),
                BackWalkSprite = renderWindow.LoadSprite(ResourcePaths.CharacterSelecPaladinBackWalk, Palettes.Fechar, new Point(521, 338)),
                SelectionBounds = new Rectangle(490, 210, 65, 180),
                ForwardWalkTimeMs = 4000,
                BackWalkTimeMs = 1300
            };

            heroRenderInfo[eHero.Amazon] = new HeroRenderInfo
            {
                Stance = eHeroStance.Idle,
                IdleSprite = renderWindow.LoadSprite(ResourcePaths.CharacterSelectAmazonUnselected, Palettes.Fechar, new Point(100, 339)),
                IdleSelectedSprite = renderWindow.LoadSprite(ResourcePaths.CharacterSelectAmazonUnselectedH, Palettes.Fechar, new Point(100, 339)),
                ForwardWalkSprite = renderWindow.LoadSprite(ResourcePaths.CharacterSelecAmazonForwardWalk, Palettes.Fechar, new Point(100, 339)),
                //ForwardWalkSpriteOverlay = renderWindow.LoadSprite(ResourcePaths.CharacterSelecAmazonForwardWalkOverlay, Palettes.Fechar, new Point(100, 339)),
                SelectedSprite = renderWindow.LoadSprite(ResourcePaths.CharacterSelecAmazonSelected, Palettes.Fechar, new Point(100, 339)),
                BackWalkSprite = renderWindow.LoadSprite(ResourcePaths.CharacterSelecAmazonBackWalk, Palettes.Fechar, new Point(100, 339)),
                SelectionBounds = new Rectangle(70, 220, 55, 200),
                ForwardWalkTimeMs = 2600,
                BackWalkTimeMs = 1500
            };

            heroRenderInfo[eHero.Assassin] = new HeroRenderInfo
            {
                Stance = eHeroStance.Idle,
                IdleSprite = renderWindow.LoadSprite(ResourcePaths.CharacterSelectAssassinUnselected, Palettes.Fechar, new Point(231, 365)),
                IdleSelectedSprite = renderWindow.LoadSprite(ResourcePaths.CharacterSelectAssassinUnselectedH, Palettes.Fechar, new Point(231, 365)),
                ForwardWalkSprite = renderWindow.LoadSprite(ResourcePaths.CharacterSelectAssassinForwardWalk, Palettes.Fechar, new Point(231, 365)),
                SelectedSprite = renderWindow.LoadSprite(ResourcePaths.CharacterSelectAssassinSelected, Palettes.Fechar, new Point(231, 365)),
                BackWalkSprite = renderWindow.LoadSprite(ResourcePaths.CharacterSelectAssassinBackWalk, Palettes.Fechar, new Point(231, 365)),
                SelectionBounds = new Rectangle(175, 235, 50, 180),
                ForwardWalkTimeMs = 3000,
                BackWalkTimeMs = 1500
            };

            heroRenderInfo[eHero.Druid] = new HeroRenderInfo
            {
                Stance = eHeroStance.Idle,
                IdleSprite = renderWindow.LoadSprite(ResourcePaths.CharacterSelectDruidUnselected, Palettes.Fechar, new Point(720, 370)),
                IdleSelectedSprite = renderWindow.LoadSprite(ResourcePaths.CharacterSelectDruidUnselectedH, Palettes.Fechar, new Point(720, 370)),
                ForwardWalkSprite = renderWindow.LoadSprite(ResourcePaths.CharacterSelectDruidForwardWalk, Palettes.Fechar, new Point(720, 370)),
                SelectedSprite = renderWindow.LoadSprite(ResourcePaths.CharacterSelectDruidSelected, Palettes.Fechar, new Point(720, 370)),
                BackWalkSprite = renderWindow.LoadSprite(ResourcePaths.CharacterSelectDruidBackWalk, Palettes.Fechar, new Point(720, 370)),
                SelectionBounds = new Rectangle(680, 220, 70, 195),
                ForwardWalkTimeMs = 3000,
                BackWalkTimeMs = 1500
            };

            headingFont = renderWindow.LoadFont(ResourcePaths.Font30, Palettes.Units);
            heroDescFont = renderWindow.LoadFont(ResourcePaths.Font16, Palettes.Units);

            headingLabel = renderWindow.CreateLabel(headingFont);
            headingLabel.Text = textDictionary.Translate("strSelectHeroClass");
            headingLabel.Location = new Point(400 - (headingLabel.TextArea.Width / 2), 17);

            heroClassLabel = renderWindow.CreateLabel(headingFont);
            heroClassLabel.Text = "";
            heroClassLabel.Location = new Point(400 - (heroClassLabel.TextArea.Width / 2), 65);

            heroDesc1Label = renderWindow.CreateLabel(heroDescFont);
            heroDesc2Label = renderWindow.CreateLabel(heroDescFont);
            heroDesc3Label = renderWindow.CreateLabel(heroDescFont);

            exitButton = createButton(eButtonType.Medium);
            exitButton.Text = "EXIT";
            exitButton.Location = new Point(30, 540);
            exitButton.OnActivate = OnExitClicked;

            okButton = createButton(eButtonType.Medium);
            okButton.Text = "OK";
            okButton.Location = new Point(630, 540);
            okButton.OnActivate = OnOkclicked;
            okButton.Enabled = false;

        }

        private void OnOkclicked()
        {
            
        }

        private void OnExitClicked()
        {
            var heros = Enum.GetValues(typeof(eHero)).Cast<eHero>();
            foreach(var hero in heros)
            {
                heroRenderInfo[hero].SpecialFrameTime = 0;
                heroRenderInfo[hero].Stance = eHeroStance.Idle;
            }
            showEntryUi = false;

            sceneManager.ChangeScene("Main Menu");
        }

        public void Render()
        {
            renderWindow.Draw(backgroundSprite, 4, 3, 0);

            RenderHeros();

            renderWindow.Draw(campfireSprite, (int)(campfireSprite.TotalFrames * secondTimer));
            renderWindow.Draw(headingLabel);
            if (selectedHero.HasValue)
            {
                renderWindow.Draw(heroClassLabel);
                renderWindow.Draw(heroDesc1Label);
                renderWindow.Draw(heroDesc2Label);
                renderWindow.Draw(heroDesc3Label);
            }

            exitButton.Render();

            if (showEntryUi)
                okButton.Render();
        }

        private void RenderHeros()
        {
            var heros = Enum.GetValues(typeof(eHero)).Cast<eHero>();
            foreach (var hero in heros)
                if (heroRenderInfo[hero].Stance == eHeroStance.Idle || heroRenderInfo[hero].Stance == eHeroStance.IdleSelected)
                    RenderHero(hero);

            foreach (var hero in heros)
                if (heroRenderInfo[hero].Stance != eHeroStance.Idle && heroRenderInfo[hero].Stance != eHeroStance.IdleSelected)
                    RenderHero(hero);
        }

        private void RenderHero(eHero hero)
        {
            var renderInfo = heroRenderInfo[hero];
            switch (renderInfo.Stance)
            {
                case eHeroStance.Idle:
                    renderWindow.Draw(renderInfo.IdleSprite, (int)(renderInfo.IdleSprite.TotalFrames * secondTimer));
                    break;
                case eHeroStance.IdleSelected:
                    renderWindow.Draw(renderInfo.IdleSelectedSprite, (int)(renderInfo.IdleSelectedSprite.TotalFrames * secondTimer));
                    break;
                case eHeroStance.Approaching:
                    {
                        var framePct = (float)renderInfo.SpecialFrameTime / (float)renderInfo.ForwardWalkTimeMs;
                        renderWindow.Draw(renderInfo.ForwardWalkSprite, (int)(renderInfo.ForwardWalkSprite.TotalFrames * framePct));
                        if (renderInfo.ForwardWalkSpriteOverlay != null)
                            renderWindow.Draw(renderInfo.ForwardWalkSpriteOverlay, (int)(renderInfo.ForwardWalkSpriteOverlay.TotalFrames * framePct));
                    }
                    break;
                case eHeroStance.Selected:
                    {
                        var framePct = (float)renderInfo.SpecialFrameTime / (float)1000;
                        renderWindow.Draw(renderInfo.SelectedSprite, (int)(renderInfo.SelectedSprite.TotalFrames * framePct));
                        if (renderInfo.SelectedSpriteOverlay != null)
                            renderWindow.Draw(renderInfo.SelectedSpriteOverlay, (int)(renderInfo.SelectedSpriteOverlay.TotalFrames * framePct));
                    }
                    break;
                case eHeroStance.Retreating:
                    {
                        var framePct = (float)renderInfo.SpecialFrameTime / (float)renderInfo.BackWalkTimeMs;
                        renderWindow.Draw(renderInfo.BackWalkSprite, (int)(renderInfo.BackWalkSprite.TotalFrames * framePct));
                        if (renderInfo.BackWalkSpriteOverlay != null)
                            renderWindow.Draw(renderInfo.BackWalkSpriteOverlay, (int)(renderInfo.BackWalkSpriteOverlay.TotalFrames * framePct));
                    }
                    break;
            }

        }

        public void Update(long ms)
        {
            float seconds = ((float)ms / 1500f);
            secondTimer += seconds;
            while (secondTimer >= 1f)
                secondTimer -= 1f;

            // Don't update hero selection if one of them is walking to or from the campfire
            var canSelect = heroRenderInfo.All(x => x.Value.Stance == eHeroStance.Idle || x.Value.Stance == eHeroStance.IdleSelected || x.Value.Stance == eHeroStance.Selected);

            foreach (var hero in Enum.GetValues(typeof(eHero)).Cast<eHero>())
                UpdateHeroSelectionHover(hero, ms, canSelect);

            if (selectedHero.HasValue && heroRenderInfo.All(x => x.Value.Stance == eHeroStance.Idle))
            {
                selectedHero = null;
            }

            exitButton.Update();
            okButton.Update();
        }

        private void UpdateHeroSelectionHover(eHero hero, long ms, bool canSelect)
        {
            var renderInfo = heroRenderInfo[hero];
            if (renderInfo.Stance == eHeroStance.Approaching)
            {
                renderInfo.SpecialFrameTime += ms;
                if (renderInfo.SpecialFrameTime >= renderInfo.ForwardWalkTimeMs)
                {
                    renderInfo.Stance = eHeroStance.Selected;
                    renderInfo.SpecialFrameTime = 0;
                }

                return;
            }

            if (renderInfo.Stance == eHeroStance.Retreating)
            {
                renderInfo.SpecialFrameTime += ms;
                if (renderInfo.SpecialFrameTime >= renderInfo.BackWalkTimeMs)
                {
                    renderInfo.Stance = eHeroStance.Idle;
                    renderInfo.SpecialFrameTime = 0;
                }

                return;
            }

            if (renderInfo.Stance == eHeroStance.Selected)
            {
                renderInfo.SpecialFrameTime += ms;
                while (renderInfo.SpecialFrameTime >= 1000)
                    renderInfo.SpecialFrameTime -= 1000;
                return;
            }

            if (!canSelect)
                return;

            // No need to highlight a hero if they are next to the campfire
            if (renderInfo.Stance == eHeroStance.Selected)
                return;

            var mouseX = mouseInfoProvider.MouseX;
            var mouseY = mouseInfoProvider.MouseY;

            var b = renderInfo.SelectionBounds;
            var mouseHover = (mouseX >= b.Left) && (mouseX <= b.Left + b.Width) && (mouseY >= b.Top) && (mouseY <= b.Top + b.Height);

            if (mouseHover && mouseInfoProvider.LeftMouseDown)
            {
                showEntryUi = true;
                renderInfo.Stance = eHeroStance.Approaching;
                renderInfo.SpecialFrameTime = 0;


                foreach(var ri in heroRenderInfo)
                {
                    if (ri.Value.Stance != eHeroStance.Selected)
                        continue;

                    ri.Value.Stance = eHeroStance.Retreating;
                    ri.Value.SpecialFrameTime = 0;
                    break;
                }

                selectedHero = hero;
                UpdateHeroText();

                return;
            }

            heroRenderInfo[hero].Stance = mouseHover ? eHeroStance.IdleSelected : eHeroStance.Idle;

            if (selectedHero == null && mouseHover)
            {
                selectedHero = hero;
                UpdateHeroText();
            }

        }

        private void UpdateHeroText()
        {
            if (selectedHero == null)
                return;

            switch (selectedHero.Value)
            {
                case eHero.Barbarian:
                    heroClassLabel.Text = textDictionary.Translate("strBarbarian");
                    heroDesc1Label.Text = "He is unequaled in close-quarters";
                    heroDesc2Label.Text = "combat and mastery of weapons.";
                    heroDesc3Label.Text = "";
                    break;
                case eHero.Necromancer:
                    heroClassLabel.Text = textDictionary.Translate("strNecromancer");
                    heroDesc1Label.Text = "Summoning undead minions and cursing";
                    heroDesc2Label.Text = "his enemies are his specialties.";
                    heroDesc3Label.Text = "";
                    break;
                case eHero.Paladin:
                    heroClassLabel.Text = textDictionary.Translate("strPaladin");
                    heroDesc1Label.Text = "He is a natural party leader, holy";
                    heroDesc2Label.Text = "man, and blessed warrior.";
                    heroDesc3Label.Text = "";
                    break;
                case eHero.Assassin:
                    heroClassLabel.Text = textDictionary.Translate("strAssassin");
                    heroDesc1Label.Text = "Schooled in the Material Arts. Her";
                    heroDesc2Label.Text = "mind and body are deadly weapons.";
                    heroDesc3Label.Text = "";
                    break;
                case eHero.Sorceress:
                    heroClassLabel.Text = textDictionary.Translate("strSorceress");
                    heroClassLabel.Text = textDictionary.Translate("strAmazon");
                    heroDesc1Label.Text = "She has mastered the elemental";
                    heroDesc2Label.Text = "magicks -- fire, lightning, and ice.";
                    heroDesc3Label.Text = "";
                    break;
                case eHero.Amazon:
                    heroClassLabel.Text = textDictionary.Translate("strAmazon");
                    heroDesc1Label.Text = "Skilled with the spear and the bow,";
                    heroDesc2Label.Text = "she is a very versatile fighter.";
                    heroDesc3Label.Text = "";
                    break;
                case eHero.Druid:
                    heroClassLabel.Text = textDictionary.Translate("strDruid");
                    heroDesc1Label.Text = "Commanding the forces of nature, he";
                    heroDesc2Label.Text = "summons wild beasts and raging";
                    heroDesc3Label.Text = "storms to his side.";
                    break;
            }

            heroClassLabel.Location = new Point(400 - (heroClassLabel.TextArea.Width / 2), 65);
            heroDesc1Label.Location = new Point(400 - (heroDesc1Label.TextArea.Width / 2), 100);
            heroDesc2Label.Location = new Point(400 - (heroDesc2Label.TextArea.Width / 2), 115);
            heroDesc3Label.Location = new Point(400 - (heroDesc3Label.TextArea.Width / 2), 130);
        }

        public void Dispose()
        {
            backgroundSprite.Dispose();
            campfireSprite.Dispose();
            headingFont.Dispose();
            headingLabel.Dispose();
        }
    }
}
