using System.Collections.Generic;
using System.Collections.Immutable;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.Extensions;

namespace Barotrauma.Items.Components
{
    partial class Deconstructor : Powered, IServerSerializable, IClientSerializable
    {
        public GUIButton ActivateButton
        {
            get { return activateButton; }
        }
        private GUIButton activateButton;
        private GUIComponent inputInventoryHolder, outputInventoryHolder;
        private GUIListBox outputDisplayListBox;

        private GUIComponent inSufficientPowerWarning;

        private bool pendingState;

        private GUITextBlock infoArea;

        [Serialize("DeconstructorDeconstruct", IsPropertySaveable.Yes)]
        public string ActivateButtonText { get; set; }
        
        [Serialize("", IsPropertySaveable.Yes)]
        public string InfoText { get; set; }

        [Serialize(0.0f, IsPropertySaveable.Yes)]
        public float InfoAreaWidth { get; set; }

        [Serialize(true, IsPropertySaveable.Yes)]
        public bool ShowOutput { get; set; }

        partial void InitProjSpecific(XElement _)
        {
            CreateGUI();
        }

        public override bool RecreateGUIOnResolutionChange => true;

        protected override void OnResolutionChanged()
        {
            OnItemLoadedProjSpecific();
        }

        protected override void CreateGUI()
        {
            var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.88f), GuiFrame.RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter)
            {
                Stretch = true,
                RelativeSpacing = 0.08f
            };

            new GUITextBlock(new RectTransform(new Vector2(1f, 0.07f), paddedFrame.RectTransform) { MinSize = new Point(0, GUI.IntScale(25)) }, item.Prefab.Name, font: GUIStyle.SubHeadingFont)
            {
                TextAlignment = Alignment.Center,
                AutoScaleHorizontal = true
            };

            var topFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.375f), paddedFrame.RectTransform), style: null);

                // === INPUT LABEL === //
                var inputLabelArea = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.15f), topFrame.RectTransform, Anchor.TopCenter), childAnchor: Anchor.CenterLeft, isHorizontal: true);

                    var queueLabelLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.43f, 1f), inputLabelArea.RectTransform), childAnchor: Anchor.CenterLeft, isHorizontal: true)
                    {
                        Stretch = true,
                        RelativeSpacing = 0.05f
                    };
                        var queueLabel = new GUITextBlock(new RectTransform(Vector2.One, queueLabelLayout.RectTransform), TextManager.Get("deconstructor.inputqueue"), font: GUIStyle.SubHeadingFont) { Padding = Vector4.Zero };
                        queueLabel.RectTransform.Resize(new Point((int) queueLabel.Font.MeasureString(queueLabel.Text).X, queueLabel.RectTransform.Rect.Height));
                        new GUIFrame(new RectTransform(Vector2.One, queueLabelLayout.RectTransform), style: "HorizontalLine");

                    var inputLabelLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.57f, 1f), inputLabelArea.RectTransform), childAnchor: Anchor.CenterLeft, isHorizontal: true)
                    {
                        Stretch = true,
                        RelativeSpacing = 0.05f
                    };
                        var inputLabel = new GUITextBlock(new RectTransform(Vector2.One, inputLabelLayout.RectTransform), TextManager.Get("deconstructor.input", "uilabel.input"), font: GUIStyle.SubHeadingFont) { Padding = Vector4.Zero };
                        inputLabel.RectTransform.Resize(new Point((int) inputLabel.Font.MeasureString(inputLabel.Text).X, inputLabel.RectTransform.Rect.Height));
                        new GUIFrame(new RectTransform(Vector2.One, inputLabelLayout.RectTransform), style: "HorizontalLine");

                var inputArea = new GUILayoutGroup(new RectTransform(new Vector2(1f, 1f), topFrame.RectTransform, Anchor.CenterLeft), childAnchor: Anchor.BottomLeft, isHorizontal: true) { Stretch = true, RelativeSpacing = 0.05f };

                    // === INPUT SLOTS === //
                    inputInventoryHolder = new GUIFrame(new RectTransform(new Vector2(0.7f, 1f), inputArea.RectTransform), style: null);
                        new GUICustomComponent(new RectTransform(Vector2.One, inputInventoryHolder.RectTransform), DrawOverLay, null) { CanBeFocused = false };

                    // === ACTIVATE BUTTON === //
                    var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.4f, 0.8f), inputArea.RectTransform), childAnchor: Anchor.CenterLeft);
                        activateButton = new GUIButton(new RectTransform(new Vector2(0.95f, 0.8f), buttonContainer.RectTransform), TextManager.Get("DeconstructorDeconstruct"), style: "DeviceButton")
                        {
                            UserData = UIHighlightAction.ElementId.DeconstructButton,
                            TextBlock = { AutoScaleHorizontal = true },
                            OnClicked = OnActivateButtonClicked
                        };
                            inSufficientPowerWarning = new GUITextBlock(new RectTransform(Vector2.One, activateButton.RectTransform),
                                TextManager.Get("DeconstructorNoPower"), textColor: GUIStyle.Orange, textAlignment: Alignment.Center, color: Color.Black, style: "OuterGlow", wrap: true)
                                {
                                HoverColor = Color.Black, 
                                IgnoreLayoutGroups = true, 
                                Visible = false, 
                                CanBeFocused = false,
                                AutoScaleHorizontal = true
                            };

            // === OUTPUT AREA === //
            var bottomFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.375f), paddedFrame.RectTransform), style: null);

                // === OUTPUT LABEL === //
                var outputLabelArea = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.15f), bottomFrame.RectTransform, Anchor.TopCenter), childAnchor: Anchor.CenterLeft, isHorizontal: true)
                {
                    Stretch = true, 
                    RelativeSpacing = 0.05f
                };
                    var outputLabel = new GUITextBlock(new RectTransform(new Vector2(0f, 1.0f), outputLabelArea.RectTransform), TextManager.Get("uilabel.output"), font: GUIStyle.SubHeadingFont) { Padding = Vector4.Zero };
                    outputLabel.RectTransform.Resize(new Point((int) outputLabel.Font.MeasureString(outputLabel.Text).X, outputLabel.RectTransform.Rect.Height));
                    new GUIFrame(new RectTransform(Vector2.One, outputLabelArea.RectTransform), style: "HorizontalLine");

                var outputArea = new GUILayoutGroup(new RectTransform(new Vector2(1f, 1f), bottomFrame.RectTransform, Anchor.CenterLeft), childAnchor: Anchor.BottomLeft, isHorizontal: true) { Stretch = true, RelativeSpacing = 0.05f };

                // === OUTPUT SLOTS === //
                outputInventoryHolder = new GUIFrame(new RectTransform(new Vector2(1f - InfoAreaWidth, 1f), outputArea.RectTransform, Anchor.CenterLeft), style: null);

            if (ShowOutput)
            {
                GUILayoutGroup outputDisplayLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.25f), paddedFrame.RectTransform), childAnchor: Anchor.TopCenter);
                        GUILayoutGroup outDisplayTopGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.2f), outputDisplayLayout.RectTransform), isHorizontal: true);
                            GUITextBlock outDisplayBlock = new GUITextBlock(new RectTransform(Vector2.One, outDisplayTopGroup.RectTransform), TextManager.Get("deconstructor.output"), font: GUIStyle.SubHeadingFont) { Padding = Vector4.Zero };
                        GUILayoutGroup outDisplayBottomGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.975f, 0.8f), outputDisplayLayout.RectTransform), isHorizontal: true);
                            outputDisplayListBox = new GUIListBox(new RectTransform(new Vector2(1f, 1f), outDisplayBottomGroup.RectTransform), isHorizontal: true, style: null);
            }

            if (InfoAreaWidth >= 0.0f)
            {
                var infoAreaContainer = new GUILayoutGroup(new RectTransform(new Vector2(InfoAreaWidth, 0.8f), outputArea.RectTransform), childAnchor: Anchor.CenterLeft);
                infoArea = new GUITextBlock(new RectTransform(new Vector2(0.95f, 0.95f), infoAreaContainer.RectTransform), string.Empty, wrap: true);
            }

            ActivateButton.OnAddedToGUIUpdateList += (GUIComponent component) =>
            {
                activateButton.Enabled = true;
                if (string.IsNullOrEmpty(InfoText))
                {
                    infoArea.Text = string.Empty;
                }
                else
                {
                    infoArea.Text = TextManager.Get(InfoText).Fallback(InfoText);
                }
                
                if (IsActive)
                {
                    activateButton.Text = TextManager.Get("DeconstructorCancel");
                    infoArea.Text = string.Empty;
                    return;
                }
                
                bool outputsFound = false;
                foreach (var (inputItem, deconstructItem) in GetAvailableOutputs(checkRequiredOtherItems: true))
                {
                    outputsFound = true;
                    if (!string.IsNullOrEmpty(deconstructItem.ActivateButtonText))
                    {
                        LocalizedString buttonText = TextManager.Get(deconstructItem.ActivateButtonText).Fallback(deconstructItem.ActivateButtonText);
                        LocalizedString infoText =  string.Empty;
                        if (!string.IsNullOrEmpty(deconstructItem.InfoText))
                        {
                            infoText = TextManager.Get(deconstructItem.InfoText).Fallback(deconstructItem.InfoText);
                        }
                        inputItem.GetComponent<GeneticMaterial>()?.ModifyDeconstructInfo(this, ref buttonText, ref infoText);
                        activateButton.Text = buttonText;
                        infoArea.Text = infoText;
                        
                        return;
                    }
                }
                
                LocalizedString activateButtonText = TextManager.Get(ActivateButtonText);
                activateButton.Enabled = outputsFound || !InputContainer.Inventory.IsEmpty();
                activateButton.Text = activateButtonText;
                
                //no valid outputs found: check if we're missing some required items from the input slots and display a message about it if possible
                if (!outputsFound && infoArea != null)
                {
                    foreach (var (inputItem, deconstructItem) in GetAvailableOutputs(checkRequiredOtherItems: false))
                    {
                        LocalizedString infoText =  string.Empty;
                        if (deconstructItem.RequiredOtherItem.Any() && !string.IsNullOrEmpty(deconstructItem.InfoTextOnOtherItemMissing))
                        {
                            LocalizedString missingItemName = TextManager.Get("entityname." + deconstructItem.RequiredOtherItem.First());
                            infoText = TextManager.GetWithVariable(deconstructItem.InfoTextOnOtherItemMissing, "[itemname]", missingItemName);
                        }
                        
                        inputItem.GetComponent<GeneticMaterial>()?.ModifyDeconstructInfo(this, ref activateButtonText, ref infoText);
                        
                        activateButton.Text = activateButtonText;
                        infoArea.Text = infoText;
                    }
                }
            };
        }

        public override bool Select(Character character)
        {
            // TODO, This works fine as of now but if GUI.PreventElementOverlap ever gets fixed this block of code may become obsolete or detrimental.
            // Only do this if there's only one linked component. If you link more containers then may
            // GUI.PreventElementOverlap have mercy on your HUD layout
            if (GuiFrame != null && item.linkedTo.Count(entity => entity is Item { DisplaySideBySideWhenLinked: true }) == 1)
            {
                foreach (MapEntity linkedTo in item.linkedTo)
                {
                    if (!(linkedTo is Item { DisplaySideBySideWhenLinked: true } linkedItem)) { continue; }
                    if (!linkedItem.Components.Any()) { continue; }

                    var itemContainer = linkedItem.GetComponent<ItemContainer>();
                    if (itemContainer?.GuiFrame == null || itemContainer.AllowUIOverlap) { continue; }

                    // how much spacing do we want between the components
                    var padding = (int) (8 * GUI.Scale);
                    // Move the linked container to the right and move the deconstructor to the left
                    itemContainer.GuiFrame.RectTransform.AbsoluteOffset = new Point(GuiFrame.Rect.Width / -2 - padding, 0);
                    GuiFrame.RectTransform.AbsoluteOffset = new Point(itemContainer.GuiFrame.Rect.Width / 2 + padding, 0);
                }
            }
            return base.Select(character);
        }

        partial void OnItemSlotsChanged(ItemContainer container)
        {
            if (container.Inventory is null) { return; }
            RefreshOutputDisplay(container.Inventory.AllItems.ToImmutableArray());
        }

        private void RefreshOutputDisplay(ImmutableArray<Item> items)
        {
            const string outputItemCountUserData = "OutputItemCount";
            const string questionMarkUserData = "UnknownItemOutput";

            if (outputDisplayListBox is null || inputContainer.Inventory is null) { return; }

            Dictionary<Identifier, int> itemCounts = new Dictionary<Identifier, int>();
            Dictionary<Identifier, GUIComponent> children = new Dictionary<Identifier, GUIComponent>();

            bool addQuestionMark = false;

            foreach (GUIComponent child in outputDisplayListBox.Content.Children)
            {
                if (child.UserData is Identifier it)
                {
                    children.Add(it, child);
                }
            }

            if (outputDisplayListBox.Content.FindChild(questionMarkUserData) is { } foundChild)
            {
                outputDisplayListBox.RemoveChild(foundChild);
            }

            foreach (Item it in items)
            {
                if (it.Prefab.RandomDeconstructionOutput)
                {
                    addQuestionMark = true;
                    continue;
                }

                foreach (DeconstructItem deconstructItem in it.Prefab.DeconstructItems)
                {
                    if (!deconstructItem.IsValidDeconstructor(item)) { continue; }
                    float percentageHealth = it.Condition / it.MaxCondition;
                    if (percentageHealth < deconstructItem.MinCondition || percentageHealth > deconstructItem.MaxCondition) { continue; }
                    RegisterItem(deconstructItem.ItemIdentifier, deconstructItem.Amount);
                }

                /*if (it.OwnInventory is { } inventory)
                {
                    foreach (Item inventoryItems in inventory.AllItems)
                    {
                        RegisterItem(inventoryItems.Prefab.Identifier);
                    }
                }*/

                void RegisterItem(Identifier identifier, int amount = 1)
                {
                    if (itemCounts.ContainsKey(identifier))
                    {
                        itemCounts[identifier] += amount;
                        return;
                    }
                    itemCounts.Add(identifier, amount);
                }
            }

            foreach (var (it, child) in children)
            {
                if (!itemCounts.ContainsKey(it))
                {
                    outputDisplayListBox.RemoveChild(child);
                }
            }

            foreach (var (it, amount) in itemCounts)
            {
                if (!children.TryGetValue(it, out GUIComponent child))
                {
                    child = CreateOutputDisplayItem(it, outputDisplayListBox.Content);
                }

                if (child is null) { continue; }
                UpdateOutputDisplayItemCount(child, amount);
            }

            if (addQuestionMark)
            {
                CreateQuestionMark(outputDisplayListBox.Content);
            }

            static void CreateQuestionMark(GUIComponent parent)
            {
                GUIFrame itemFrame = new GUIFrame(new RectTransform(new Vector2(0.1f, 1f), parent.RectTransform), style: null)
                {
                    UserData = questionMarkUserData,
                    ToolTip = TextManager.Get("deconstructor.unknownitemsoutput")
                };

                GUIFrame questionMarkFrame = new GUIFrame(new RectTransform(Vector2.One, itemFrame.RectTransform, scaleBasis: ScaleBasis.Smallest, anchor: Anchor.Center), style: "GUIFrameListBox")
                {
                    CanBeFocused = false,
                };

                // question mark text
                new GUITextBlock(new RectTransform(Vector2.One, questionMarkFrame.RectTransform, anchor: Anchor.Center), text: "?", textAlignment: Alignment.Center, font: GUIStyle.LargeFont)
                {
                    CanBeFocused = false
                };
            }

            static GUIComponent CreateOutputDisplayItem(Identifier identifier, GUIComponent parent)
            {
                ItemPrefab prefab = ItemPrefab.Find(null, identifier);
                if (prefab is null) { return null; }

                GUIFrame itemFrame = new GUIFrame(new RectTransform(new Vector2(0.1f, 1f), parent.RectTransform), style: null)
                {
                    UserData = identifier,
                    ToolTip = prefab.CreateTooltipText()
                };

                Sprite icon = prefab.InventoryIcon ?? prefab.Sprite;
                Color iconColor = prefab.InventoryIcon is null ? prefab.SpriteColor : prefab.InventoryIconColor;

                GUIImage itemIcon = new GUIImage(new RectTransform(Vector2.One, itemFrame.RectTransform, scaleBasis: ScaleBasis.Smallest, anchor: Anchor.Center), sprite: icon, scaleToFit: true)
                {
                    Color = iconColor,
                    CanBeFocused = false
                };

                // item count text
                new GUITextBlock(new RectTransform(new Vector2(0.5f, 0.5f), itemIcon.RectTransform, anchor: Anchor.BottomRight), "", font: GUIStyle.Font, textAlignment: Alignment.BottomRight)
                {
                    UserData = outputItemCountUserData,
                    Shadow = true,
                    CanBeFocused = false,
                    Padding = Vector4.Zero,
                    TextColor = Color.White,
                };

                return itemFrame;
            }

            static void UpdateOutputDisplayItemCount(GUIComponent component, int count)
            {
                if (!(component.FindChild(outputItemCountUserData, recursive: true) is GUITextBlock textBlock)) { return; }

                textBlock.Text = TextManager.GetWithVariable("campaignstore.quantity", "[amount]", count.ToString());
            }
        }

        partial void OnItemLoadedProjSpecific()
        {
            inputContainer.AllowUIOverlap = true;
            inputContainer.Inventory.RectTransform = inputInventoryHolder.RectTransform;
            outputContainer.AllowUIOverlap = true;
            outputContainer.Inventory.RectTransform = outputInventoryHolder.RectTransform;

            inputContainer.Inventory.Locked = IsActive;
        }

        private void DrawOverLay(SpriteBatch spriteBatch, GUICustomComponent overlayComponent)
        {
            overlayComponent.RectTransform.SetAsLastChild();

            if (!(inputContainer?.Inventory?.visualSlots is { } visualSlots)) { return; }

            if (DeconstructItemsSimultaneously)
            {
                for (int i = 0; i < InputContainer.Inventory.Capacity; i++)
                {
                    if (InputContainer.Inventory.GetItemAt(i) == null) { continue; }
                    DrawProgressBar(InputContainer.Inventory.visualSlots[i]);
                }
            }
            else
            {
                DrawProgressBar(inputContainer.Inventory.visualSlots.Last());
            }

            void DrawProgressBar(VisualSlot slot)
            {
                GUI.DrawRectangle(spriteBatch,
                    new Rectangle(
                        slot.Rect.X, slot.Rect.Y + (int)(slot.Rect.Height * (1.0f - progressState)),
                        slot.Rect.Width, (int)(slot.Rect.Height * progressState)),
                    GUIStyle.Green * 0.5f, isFilled: true);
            }
        }

        public override void UpdateHUDComponentSpecific(Character character, float deltaTime, Camera cam)
        {
            inSufficientPowerWarning.Visible = IsActive && !HasPower;
        }

        private bool OnActivateButtonClicked(GUIButton button, object obj)
        {
            if (!IsActive)
            {
                //don't allow turning on if there's non-deconstructable items in the queue
                var disallowedItem = inputContainer.Inventory.FindItem(i => !i.AllowDeconstruct, recursive: false);
                if (disallowedItem != null && !DeconstructItemsSimultaneously)
                {
                    int index = inputContainer.Inventory.FindIndex(disallowedItem);
                    if (index >= 0 && index < inputContainer.Inventory.visualSlots.Length)
                    {
                        var slot = inputContainer.Inventory.visualSlots[index];
                        slot?.ShowBorderHighlight(GUIStyle.Red, 0.1f, 0.9f);
                    }
                    return true;
                }
            }

            if (GameMain.Client != null)
            {
                pendingState = !IsActive;
                item.CreateClientEvent(this);
            }
            else
            {
                SetActive(!IsActive, Character.Controlled);
            }
            return true;
        }

        public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
        {
            msg.WriteBoolean(pendingState);
        }

        public void ClientEventRead(IReadMessage msg, float sendingTime)
        {
            ushort userID = msg.ReadUInt16();
            Character user = userID == Entity.NullEntityID ? null : Entity.FindEntityByID(userID) as Character;
            SetActive(msg.ReadBoolean(), user);
            progressTimer = msg.ReadSingle();
        }
    }
}
