using Barotrauma.Networking;
using Barotrauma.Particles;
using Barotrauma.Sounds;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;

namespace Barotrauma.Items.Components
{
    partial class Repairable : ItemComponent, IDrawableComponent
    {
        public GUIButton RepairButton { get; private set; }

        public GUIButton SabotageButton { get; private set; }

        public GUIButton TinkerButton { get; private set; }

        private GUIProgressBar progressBar;

        private GUITextBlock progressBarOverlayText;

        private GUILayoutGroup extraButtonContainer;

        private GUIComponent skillTextContainer;

        private readonly List<ParticleEmitter> particleEmitters = new List<ParticleEmitter>();
        //the corresponding particle emitter is active when the condition is within this range
        private readonly List<Vector2> particleEmitterConditionRanges = new List<Vector2>();

        private SoundChannel repairSoundChannel;

        private LocalizedString repairButtonText, repairingText;
        private LocalizedString sabotageButtonText, sabotagingText;
        private LocalizedString tinkerButtonText, tinkeringText;

        private FixActions requestStartFixAction;

        private bool qteSuccess;

        private float qteTimer;
        private const float QteDuration = 0.5f;
        private float qteCooldown;
        private const float QteCooldownDuration = 0.5f;

        public float FakeBrokenTimer;

        [Serialize("", IsPropertySaveable.No, description: "An optional description of the needed repairs displayed in the repair interface.")]
        public string Description
        {
            get;
            set;
        }

        public Vector2 DrawSize
        {
            //use the extents of the item as the draw size
            get { return Vector2.Zero; }
        }

        public override bool ShouldDrawHUD(Character character)
        {
            if (item.IsHidden) { return false; }
            if (!HasRequiredItems(character, false) || character.SelectedItem != item) { return false; }
            if (character.IsTraitor && item.ConditionPercentage > MinSabotageCondition) { return true; }
            if (item.ConditionPercentageRelativeToDefaultMaxCondition < RepairThreshold) { return true; }

            if (CurrentFixer == character)
            {
                if (item.Condition < item.MaxCondition)
                {
                    return true;
                }
            }
            if (IsTinkerable(character)) { return true; }

            return false;
        }

        partial void InitProjSpecific(ContentXElement element)
        {
            CreateGUI();
            foreach (var subElement in element.Elements())
            {
                switch (subElement.Name.ToString().ToLowerInvariant())
                {
                    case "emitter":
                    case "particleemitter":
                        particleEmitters.Add(new ParticleEmitter(subElement));
                        float minCondition = subElement.GetAttributeFloat("mincondition", 0.0f);
                        float maxCondition = subElement.GetAttributeFloat("maxcondition", 100.0f);

                        if (maxCondition < minCondition)
                        {
                            DebugConsole.ThrowError("Invalid damage particle configuration in the Repairable component of " + item.Name + ". MaxCondition needs to be larger than MinCondition.");
                            float temp = maxCondition;
                            maxCondition = minCondition;
                            minCondition = temp;
                        }
                        particleEmitterConditionRanges.Add(new Vector2(minCondition, maxCondition));

                        break;
                }
            }
        }
        
        private void RecreateGUI()
        {
            if (GuiFrame != null)
            {
                GuiFrame.ClearChildren();
                TryCreateDragHandle();
                CreateGUI();
            }
        }

        protected override void CreateGUI()
        {
            var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.75f), GuiFrame.RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter)
            {
                Stretch = true,
                RelativeSpacing = 0.05f,
                CanBeFocused = true
            };
            
            new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), paddedFrame.RectTransform),
                header, textAlignment: Alignment.TopCenter, font: GUIStyle.LargeFont);

            new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedFrame.RectTransform),
                Description, font: GUIStyle.SmallFont, wrap: true);

            new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedFrame.RectTransform),
                TextManager.Get("RequiredRepairSkills"), font: GUIStyle.SubHeadingFont);
            skillTextContainer = paddedFrame;
            for (int i = 0; i < RequiredSkills.Count; i++)
            {
                var skillText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), skillTextContainer.RectTransform),
                    "   - " + TextManager.AddPunctuation(':', TextManager.Get("SkillName." + RequiredSkills[i].Identifier), ((int) Math.Round(RequiredSkills[i].Level * SkillRequirementMultiplier)).ToString()),
                    font: GUIStyle.SmallFont)
                {
                    UserData = RequiredSkills[i]
                };
            }

            var progressBarHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.2f), paddedFrame.RectTransform), isHorizontal: true)
            {
                Stretch = true,
                RelativeSpacing = 0.02f
            };

            progressBar = new GUIProgressBar(new RectTransform(new Vector2(0.6f, 1.0f), progressBarHolder.RectTransform),
                color: GUIStyle.Green, barSize: 0.0f, style: "DeviceProgressBar");

            progressBarOverlayText = new GUITextBlock(new RectTransform(Vector2.One, progressBar.RectTransform), string.Empty, font: GUIStyle.SubHeadingFont, textAlignment: Alignment.Center)
            {
                IgnoreLayoutGroups = true
            };

            qteTimer = QteDuration;

            repairButtonText = TextManager.Get("RepairButton");
            repairingText = TextManager.Get("Repairing");
            RepairButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1.0f), progressBarHolder.RectTransform, Anchor.TopCenter), repairButtonText)
            {
                UserData = UIHighlightAction.ElementId.RepairButton,
                OnClicked = (btn, obj) =>
                {
                    requestStartFixAction = FixActions.Repair;
                    item.CreateClientEvent(this);
                    return true;
                },
                OnButtonDown = () =>
                {
                    QTEAction();
                    return true;
                }
            };
            RepairButton.TextBlock.AutoScaleHorizontal = true;
            progressBarHolder.RectTransform.MinSize = RepairButton.RectTransform.MinSize;
            RepairButton.RectTransform.MinSize = new Point((int)(RepairButton.TextBlock.TextSize.X * 1.2f), RepairButton.RectTransform.MinSize.Y);

            extraButtonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.15f), paddedFrame.RectTransform), isHorizontal: true)
            {
                IgnoreLayoutGroups = true,
                Stretch = true,
                AbsoluteSpacing = GUI.IntScale(5)
            };

            sabotageButtonText = TextManager.Get("SabotageButton");
            sabotagingText = TextManager.Get("Sabotaging");
            SabotageButton = new GUIButton(new RectTransform(Vector2.One, extraButtonContainer.RectTransform), sabotageButtonText, style: "GUIButtonSmall")
            {
                IgnoreLayoutGroups = true,
                Visible = false,
                OnClicked = (btn, obj) =>
                {
                    requestStartFixAction = FixActions.Sabotage;
                    item.CreateClientEvent(this);
                    return true;
                },
                OnButtonDown = () =>
                {
                    QTEAction();
                    return true;
                }
            };

            tinkerButtonText = TextManager.Get("TinkerButton").Fallback("Tinker");
            tinkeringText = TextManager.Get("Tinkering").Fallback("Tinkering");
            TinkerButton = new GUIButton(new RectTransform(Vector2.One, extraButtonContainer.RectTransform), tinkerButtonText, style: "GUIButtonSmall")
            {
                IgnoreLayoutGroups = true,
                Visible = false,
                OnClicked = (btn, obj) =>
                {
                    requestStartFixAction = FixActions.Tinker;
                    item.CreateClientEvent(this);
                    return true;
                }
            };

            extraButtonContainer.RectTransform.MinSize = new Point(0, SabotageButton.RectTransform.MinSize.Y);
        }

        partial void UpdateProjSpecific(float deltaTime)
        {
            if (item.IsHidden) { return; }
            if (FakeBrokenTimer > 0.0f)
            {
                item.FakeBroken = true;
                if (Character.Controlled == null || (Character.Controlled.CharacterHealth.GetAffliction("psychosis")?.Strength ?? 0.0f) <= 0.0f)
                {
                    FakeBrokenTimer = 0.0f;
                }
                else
                {
                    FakeBrokenTimer -= deltaTime;
                }
            }
            else
            {
                item.FakeBroken = false;
            }


            if (!GameMain.IsMultiplayer)
            {
                switch (requestStartFixAction)
                {
                    case FixActions.Repair:
                    case FixActions.Sabotage:
                    case FixActions.Tinker:
                        StartRepairing(Character.Controlled, requestStartFixAction);
                        requestStartFixAction = FixActions.None;
                        break;
                    default:
                        requestStartFixAction = FixActions.None;
                        break;
                }
            }

            float conditionPercentage = item.ConditionPercentageRelativeToDefaultMaxCondition;

            for (int i = 0; i < particleEmitters.Count; i++)
            {
                if ((conditionPercentage >= particleEmitterConditionRanges[i].X && conditionPercentage <= particleEmitterConditionRanges[i].Y) || FakeBrokenTimer > 0.0f)
                {
                    particleEmitters[i].Emit(deltaTime, item.WorldPosition, item.CurrentHull);
                }
            }

            if (CurrentFixer != null && CurrentFixer.SelectedItem == item)
            {
                if (repairSoundChannel == null || !repairSoundChannel.IsPlaying)
                {
                    repairSoundChannel = SoundPlayer.PlaySound("repair", item.WorldPosition, hullGuess: item.CurrentHull);
                }

                if (qteCooldown > 0.0f)
                {
                    qteCooldown -= deltaTime;
                    if (qteCooldown <= 0.0f)
                    {
                        qteTimer = QteDuration;
                    }
                }
                else
                {
                    qteTimer -= deltaTime * (qteTimer / QteDuration);
                    if (qteTimer < 0.0f) { qteTimer = QteDuration; }                
                }
            }
            else
            {
                repairSoundChannel?.FadeOutAndDispose();
                repairSoundChannel = null;
            }
        }
        
        public override void DrawHUD(SpriteBatch spriteBatch, Character character)
        {
            base.DrawHUD(spriteBatch, character);

            IsActive = true;
            
            float defaultMaxCondition = (item.MaxCondition / item.MaxRepairConditionMultiplier);

            progressBar.BarSize = item.Condition / defaultMaxCondition;
            progressBar.Color = ToolBox.GradientLerp(progressBar.BarSize, GUIStyle.Red, GUIStyle.Orange, GUIStyle.Green);

            Rectangle sliderRect = progressBar.GetSliderRect(1.0f);
            Color qteSliderColor = Color.White;
            if (qteCooldown > 0.0f)
            {
                qteSliderColor = qteSuccess ? GUIStyle.Green : GUIStyle.Red * 0.5f;
                progressBar.Color = ToolBox.GradientLerp(qteCooldown / QteCooldownDuration, progressBar.Color, qteSliderColor, Color.White);
            }
            else
            {
                if (qteTimer / QteDuration <= item.Condition / item.MaxCondition)
                {
                    qteSliderColor = Color.Lerp(qteSliderColor, GUIStyle.Green, 0.5f);
                }
            }

            progressBar.Parent.Parent.Parent.DrawManually(spriteBatch, true);
            GUI.DrawRectangle(spriteBatch,
                    new Rectangle(sliderRect.X + (int)((qteTimer / QteDuration) * sliderRect.Width), sliderRect.Y - 5, 2, sliderRect.Height + 10),
                    qteSliderColor, true);

            if (item.Condition > defaultMaxCondition)
            {
                float extraCondition = item.MaxCondition * (item.MaxRepairConditionMultiplier - 1.0f);
                progressBar.Color = ToolBox.GradientLerp((item.Condition - defaultMaxCondition) / extraCondition, GUIStyle.ColorReputationHigh, GUIStyle.ColorReputationVeryHigh);
                progressBarOverlayText.Visible = true;
                progressBarOverlayText.Text = $"{(int)Math.Round((item.Condition / defaultMaxCondition) * 100)}%";
            }
            else
            {
                progressBarOverlayText.Visible = false;
            }

            RepairButton.Enabled = (currentFixerAction == FixActions.None || CurrentFixer == character) && !item.IsFullCondition;
            RepairButton.Text = (currentFixerAction == FixActions.None || CurrentFixer != character || currentFixerAction != FixActions.Repair) ?
                repairButtonText :
                repairingText + new string('.', ((int)(Timing.TotalTime * 2.0f) % 3) + 1);

            SabotageButton.Visible = character.IsTraitor;
            SabotageButton.IgnoreLayoutGroups = !SabotageButton.Visible;
            SabotageButton.Enabled = (currentFixerAction == FixActions.None || (CurrentFixer == character && currentFixerAction != FixActions.Sabotage)) && character.IsTraitor && item.ConditionPercentage > MinSabotageCondition;
            SabotageButton.Text = (currentFixerAction == FixActions.None || CurrentFixer != character || currentFixerAction != FixActions.Sabotage || !character.IsTraitor) ?
                sabotageButtonText :
                sabotagingText + new string('.', ((int)(Timing.TotalTime * 2.0f) % 3) + 1);

            TinkerButton.Visible = IsTinkerable(character);
            TinkerButton.IgnoreLayoutGroups = !TinkerButton.Visible;
            TinkerButton.Enabled = (currentFixerAction == FixActions.None || (CurrentFixer == character && currentFixerAction != FixActions.Tinker)) && CanTinker(character);
            TinkerButton.Text = (currentFixerAction == FixActions.None || CurrentFixer != character || currentFixerAction != FixActions.Tinker) ?
                tinkerButtonText :
                tinkeringText + new string('.', ((int)(Timing.TotalTime * 2.0f) % 3) + 1);

            //System.Diagnostics.Debug.Assert(GuiFrame.GetChild(0) is GUILayoutGroup, "Repair UI hierarchy has changed, could not find skill texts");

            extraButtonContainer.Visible = SabotageButton.Visible || TinkerButton.Visible;
            extraButtonContainer.IgnoreLayoutGroups = !extraButtonContainer.Visible;

            foreach (GUIComponent c in skillTextContainer.Children)
            {
                if (c.UserData is not Skill skill) { continue; }
                GUITextBlock textBlock = (GUITextBlock)c;
                textBlock.TextColor = character.GetSkillLevel(skill.Identifier) < (skill.Level * SkillRequirementMultiplier) ? GUIStyle.Red : GUIStyle.TextColorNormal;
            }
        }

        public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1, Color? overrideColor = null)
        {
            if (GameMain.DebugDraw && Character.Controlled?.FocusedItem == item)
            {
                bool paused = !ShouldDeteriorate() && ForceDeteriorationTimer <= 0.0f;
                if (ForceDeteriorationTimer > 0.0f)
                {
                    GUI.DrawString(spriteBatch,
                        new Vector2(item.DrawPosition.X, -item.DrawPosition.Y), "Forced deterioration for " + ((int)ForceDeteriorationTimer) + " s",
                        Color.Red, Color.Black * 0.5f);

                }
                else if (deteriorationTimer > 0.0f)
                {
                    GUI.DrawString(spriteBatch,
                        new Vector2(item.DrawPosition.X, -item.DrawPosition.Y), "Deterioration delay " + ((int)deteriorationTimer) + (paused ? " [PAUSED]" : ""),
                        paused ? Color.Cyan : Color.Lime, Color.Black * 0.5f);
                }
                else
                {
                    GUI.DrawString(spriteBatch,
                        new Vector2(item.DrawPosition.X, -item.DrawPosition.Y), "Deteriorating at " + (int)(DeteriorationSpeed * 60.0f) + " units/min" + (paused ? " [PAUSED]" : ""),
                        paused ? Color.Cyan : GUIStyle.Red, Color.Black * 0.5f);
                }
                GUI.DrawString(spriteBatch,
                    new Vector2(item.DrawPosition.X, -item.DrawPosition.Y + 20), "Condition: " + (int)item.Condition + "/" + (int)item.MaxCondition,
                    GUIStyle.Orange);
                if (MaxStressDeteriorationMultiplier > 1.0f)
                {
                    GUI.DrawString(spriteBatch,
                        new Vector2(item.DrawPosition.X, -item.DrawPosition.Y + 40), "Stress multiplier: " + StressDeteriorationMultiplier.ToString("0.00"),
                        GUIStyle.Red);
                }
            }
        }

        protected override void RemoveComponentSpecific()
        {
            base.RemoveComponentSpecific();
            repairSoundChannel?.FadeOutAndDispose();
            repairSoundChannel = null;
        }

        private void QTEAction()
        {
            if (currentFixerAction == FixActions.Repair)
            {
                float defaultMaxCondition = item.MaxCondition / item.MaxRepairConditionMultiplier;
                qteSuccess = qteCooldown <= 0.0f && qteTimer / QteDuration <= item.Condition / defaultMaxCondition;
            }
            else
            {
                return;
            }

            if (!GameMain.IsMultiplayer) { RepairBoost(qteSuccess); }

            SoundPlayer.PlayUISound(qteSuccess ? GUISoundType.Increase : GUISoundType.Decrease);

            //on failure during cooldown reset cursor to beginning
            if (!qteSuccess && qteCooldown > 0.0f) { qteTimer = QteDuration; }
            qteCooldown = QteCooldownDuration;
            //this will be set on button down so we can reset it here
            requestStartFixAction = FixActions.None;
            item.CreateClientEvent(this);
        }

        public void ClientEventRead(IReadMessage msg, float sendingTime)
        {
            deteriorationTimer = msg.ReadSingle();
            ForceDeteriorationTimer = msg.ReadSingle();
            tinkeringDuration = msg.ReadSingle();
            tinkeringStrength = msg.ReadSingle();
            tinkeringPowersDevices = msg.ReadBoolean();
            ushort currentFixerID = msg.ReadUInt16();
            currentFixerAction = (FixActions)msg.ReadRangedInteger(0, 2);
            CurrentFixer = currentFixerID != 0 ? Entity.FindEntityByID(currentFixerID) as Character : null;

            if (CurrentFixer is null)
            {
                qteTimer = QteDuration;
                qteCooldown = 0.0f;
            }
            else
            {
                item.MaxRepairConditionMultiplier = GetMaxRepairConditionMultiplier(CurrentFixer);
            }
        }

        public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
        {
            msg.WriteRangedInteger((int)requestStartFixAction, 0, 2);
            msg.WriteBoolean(qteSuccess);
        }
    }
}
