using Barotrauma.Extensions;
using Barotrauma.Items.Components;
using FarseerPhysics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Barotrauma
{
    partial class CharacterHUD
    {        
        const float BossHealthBarDuration = 120.0f;

        abstract class BossProgressBar
        {
            public float FadeTimer;

            public readonly GUIComponent TopContainer;
            public readonly GUIComponent SideContainer;

            public readonly GUIProgressBar TopHealthBar;
            public readonly GUIProgressBar SideHealthBar;

            public abstract bool Completed { get; }

            public abstract bool Interrupted { get; }

            public abstract float State { get; }

            public abstract string NumberToDisplay { get; }

            public abstract Color Color { get; }

            public BossProgressBar(LocalizedString label)
            {
                FadeTimer = BossHealthBarDuration;

                TopContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.18f, 0.03f), HUDFrame.RectTransform, Anchor.TopCenter)
                {
                    MinSize = new Point(100, 50),
                    RelativeOffset = new Vector2(0.0f, 0.01f)
                }, isHorizontal: false, childAnchor: Anchor.TopCenter);
                new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.4f), TopContainer.RectTransform), label, textAlignment: Alignment.Center, textColor: GUIStyle.Red);
                TopHealthBar = new GUIProgressBar(new RectTransform(new Vector2(1.0f, 0.6f), TopContainer.RectTransform)
                {
                    MinSize = new Point(100, HUDLayoutSettings.HealthBarArea.Size.Y)
                }, barSize: 0.0f, style: "CharacterHealthBarCentered")
                {
                    Color = GUIStyle.Red
                };
                CreateNumberText(TopHealthBar);

                SideContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), bossHealthContainer.RectTransform)
                {
                    MinSize = new Point(80, 60)
                }, isHorizontal: false, childAnchor: Anchor.TopRight);
                new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.3f), SideContainer.RectTransform), label, textAlignment: Alignment.CenterRight, textColor: GUIStyle.Red);
                SideHealthBar = new GUIProgressBar(new RectTransform(new Vector2(1.0f, 0.7f), SideContainer.RectTransform), barSize: 0.0f, style: "CharacterHealthBar")
                {
                    Color = GUIStyle.Red
                };
                CreateNumberText(SideHealthBar);

                TopContainer.Visible = SideContainer.Visible = false;
                TopContainer.CanBeFocused = false;
                TopContainer.Children.ForEach(c => c.CanBeFocused = false);
                SideContainer.CanBeFocused = false;
                SideContainer.Children.ForEach(c => c.CanBeFocused = false);

                void CreateNumberText(GUIComponent parent)
                {
                    new GUITextBlock(new RectTransform(Vector2.One, parent.RectTransform)
                        { AbsoluteOffset = new Point(2) }, 
                        string.Empty, textAlignment: Alignment.Center, textColor: GUIStyle.TextColorDark)
                    {
                        TextGetter = () => NumberToDisplay
                    };
                    new GUITextBlock(new RectTransform(Vector2.One, parent.RectTransform), 
                        string.Empty, textAlignment: Alignment.Center, textColor: GUIStyle.TextColorBright)
                    {
                        TextGetter = () => NumberToDisplay
                    };
                }
            }

            public abstract bool IsDuplicate(object targetObject);
        }

        class BossHealthBar : BossProgressBar
        {
            public readonly Character Character;

            public override float State => Character.Vitality / Character.MaxVitality;

            public override bool Completed => Character.IsDead;

            public override bool Interrupted => Character.Removed || !Character.Enabled;

            public override Color Color => 
                Character.CharacterHealth.GetAfflictionStrength(AfflictionPrefab.PoisonType) > 0 || Character.CharacterHealth.GetAfflictionStrength(AfflictionPrefab.ParalysisType) > 0 ? 
                    GUIStyle.HealthBarColorPoisoned : GUIStyle.Red;

            public override string NumberToDisplay => string.Empty;

            public BossHealthBar(Character character) : base(character.DisplayName)
            {
                Character = character;
            }

            public override bool IsDuplicate(object targetObject)
            {
                return targetObject is Character character && Character == character;
            }
        }

        class MissionProgressBar : BossProgressBar
        {
            public readonly Mission Mission;

            public override float State => Mission.State / (float)Mission.Prefab.MaxProgressState;

            public override bool Completed => Mission.State >= Mission.Prefab.MaxProgressState;

            public override bool Interrupted => Mission.Failed || GameMain.GameSession?.Missions == null || !GameMain.GameSession.Missions.Contains(Mission);

            public override Color Color => GUIStyle.Red;

            public override string NumberToDisplay => Mission.Prefab.ShowProgressInNumbers ?
                $"{Mission.State}/{Mission.Prefab.MaxProgressState}" :
                string.Empty;

            public MissionProgressBar(Mission mission) : base(mission.Prefab.ProgressBarLabel)
            {
                Mission = mission;
            }

            public override bool IsDuplicate(object targetObject)
            {
                return targetObject is Mission mission && Mission == mission;
            }
        }

        private static readonly Dictionary<ISpatialEntity, int> orderIndicatorCount = new Dictionary<ISpatialEntity, int>();
        const float ItemOverlayDelay = 1.0f;
        private static Item focusedItem;
        private static float focusedItemOverlayTimer;
        
        private static readonly List<Item> brokenItems = new List<Item>();
        private static float brokenItemsCheckTimer;

        private static readonly List<BossProgressBar> bossProgressBars = new List<BossProgressBar>();

        private static readonly Dictionary<Identifier, LocalizedString> cachedHudTexts = new Dictionary<Identifier, LocalizedString>();
        private static LanguageIdentifier cachedHudTextLanguage = LanguageIdentifier.None;

        private static GUILayoutGroup bossHealthContainer;

        private static GUIFrame hudFrame;
        public static GUIFrame HUDFrame
        {

            get
            {
                if (hudFrame == null)
                {
                    hudFrame = new GUIFrame(new RectTransform(GUI.Canvas.RelativeSize, GUI.Canvas), style: null)
                    {
                        CanBeFocused = false
                    };
                    bossHealthContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.15f, 0.5f), hudFrame.RectTransform, Anchor.CenterRight)
                    {
                        RelativeOffset = new Vector2(0.005f, 0.0f)
                    })
                    {
                        AbsoluteSpacing = GUI.IntScale(10)
                    };
                }
                return hudFrame;
            }
        }

        public static bool RecreateHudTexts { get; set; } = true;
        private static bool lastHudTextsContextual;
        private static float timeHealthWindowClosed;

        public static bool IsCampaignInterfaceOpen =>
            GameMain.GameSession?.Campaign != null && 
            (GameMain.GameSession.Campaign.ShowCampaignUI || GameMain.GameSession.Campaign.ForceMapUI);

        public static bool ShouldDrawInventory(Character character)
        {
            var controller = character.SelectedItem?.GetComponent<Controller>();

            return 
                character?.Inventory != null && 
                !character.Removed && !character.IsKnockedDown &&
                (controller?.User != character || !controller.HideHUD || Screen.Selected.IsEditor) &&
                !IsCampaignInterfaceOpen &&
                !ConversationAction.FadeScreenToBlack;
        }

        public static LocalizedString GetCachedHudText(string textTag, InputType keyBind)
        {
            if (cachedHudTextLanguage != GameSettings.CurrentConfig.Language)
            {
                cachedHudTexts.Clear();
            }
            Identifier key = (textTag + keyBind).ToIdentifier();
            if (cachedHudTexts.TryGetValue(key, out LocalizedString text)) { return text; }
            text = TextManager.GetWithVariable(textTag, "[key]", GameSettings.CurrentConfig.KeyMap.KeyBindText(keyBind)).Value;
            cachedHudTexts.Add(key, text);
            cachedHudTextLanguage = GameSettings.CurrentConfig.Language;
            return text;
        }
        
        public static void AddToGUIUpdateList(Character character)
        {
            if (GUI.DisableHUD) { return; }
            
            if (!character.IsIncapacitated && character.Stun <= 0.0f && !IsCampaignInterfaceOpen)
            {
                if (character.Inventory != null)
                {
                    for (int i = 0; i < character.Inventory.Capacity; i++)
                    {
                        var item = character.Inventory.GetItemAt(i);
                        if (item == null || character.Inventory.SlotTypes[i] == InvSlotType.Any) { continue; }

                        foreach (ItemComponent ic in item.Components)
                        {
                            if (ic.DrawHudWhenEquipped) { ic.AddToGUIUpdateList(); }
                        }
                    }
                }

                if (character.Params.CanInteract && character.SelectedCharacter != null)
                {
                    character.SelectedCharacter.CharacterHealth.AddToGUIUpdateList();
                }
            }

            HUDFrame.AddToGUIUpdateList();
        }

        public static void Update(float deltaTime, Character character, Camera cam)
        {
            UpdateBossProgressBars(deltaTime);

            if (GUI.DisableHUD)
            {
                if (character.Inventory != null && !LockInventory(character))
                {
                    character.Inventory.UpdateSlotInput();
                }
                return;
            }

            if (!character.IsIncapacitated && character.Stun <= 0.0f && !IsCampaignInterfaceOpen)
            {
                if (character.Info != null && !character.ShouldLockHud() && character.SelectedCharacter == null && Screen.Selected != GameMain.SubEditorScreen)
                {
                    bool mouseOnPortrait = MouseOnCharacterPortrait() && GUI.MouseOn == null;
                    bool healthWindowOpen = CharacterHealth.OpenHealthWindow != null || timeHealthWindowClosed < 0.2f;
                    if (mouseOnPortrait && !healthWindowOpen && PlayerInput.PrimaryMouseButtonClicked() && Inventory.DraggingItems.None())
                    {
                        CharacterHealth.OpenHealthWindow = character.CharacterHealth;
                    }
                }

                if (character.Inventory != null)
                {
                    if (!LockInventory(character))
                    {
                        character.Inventory.Update(deltaTime, cam);
                    }
                    else
                    {
                        character.Inventory.ClearSubInventories();
                    }
                }

                if (character.Params.CanInteract && character.SelectedCharacter != null && character.SelectedCharacter.Inventory != null)
                {
                    if (character.SelectedCharacter.CanInventoryBeAccessed)
                    {
                        character.SelectedCharacter.Inventory.Update(deltaTime, cam);
                    }
                    character.SelectedCharacter.CharacterHealth.UpdateHUD(deltaTime);
                }

                Inventory.UpdateDragging();
            }

            if (focusedItem != null)
            {
                if (character.FocusedItem != null)
                {
                    focusedItemOverlayTimer = Math.Min(focusedItemOverlayTimer + deltaTime, ItemOverlayDelay + 1.0f);
                }
                else
                {
                    focusedItemOverlayTimer = Math.Max(focusedItemOverlayTimer - deltaTime, 0.0f);
                    if (focusedItemOverlayTimer <= 0.0f)
                    {
                        focusedItem = null;
                        RecreateHudTexts = true;
                    }
                }
            }

            if (brokenItemsCheckTimer > 0.0f)
            {
                brokenItemsCheckTimer -= deltaTime;
            }
            else
            {
                brokenItems.Clear();
                brokenItemsCheckTimer = 1.0f;
                foreach (Item item in Item.ItemList)
                {
                    if (item.Submarine == null || item.Submarine.TeamID != character.TeamID || item.Submarine.Info.IsWreck) { continue; }
                    if (!item.Repairables.Any(r => r.IsBelowRepairIconThreshold)) { continue; }
                    if (Submarine.VisibleEntities != null && !Submarine.VisibleEntities.Contains(item)) { continue; }

                    Vector2 diff = item.WorldPosition - character.WorldPosition;
                    if (Submarine.CheckVisibility(character.SimPosition, character.SimPosition + ConvertUnits.ToSimUnits(diff)) == null)
                    {
                        brokenItems.Add(item); 
                    }                   
                }
            }

            if (CharacterHealth.OpenHealthWindow != null)
            {
                timeHealthWindowClosed = 0.0f;
            }
            else
            {
                timeHealthWindowClosed += deltaTime;
            }
        }
        
        public static void Draw(SpriteBatch spriteBatch, Character character, Camera cam)
        {
            if (GUI.DisableHUD) { return; }
            
            character.CharacterHealth.Alignment = Alignment.Right;           

            if (GameMain.GameSession?.CrewManager != null)
            {
                orderIndicatorCount.Clear();
                foreach (CrewManager.ActiveOrder activeOrder in GameMain.GameSession.CrewManager.ActiveOrders)
                {
                    if (!DrawIcon(activeOrder.Order)) { continue; }

                    if (activeOrder.FadeOutTime.HasValue)
                    {
                        DrawOrderIndicator(spriteBatch, cam, character, activeOrder.Order, iconAlpha: MathHelper.Clamp(activeOrder.FadeOutTime.Value / 10.0f, 0.2f, 1.0f));
                    }
                    else
                    {
                        float iconAlpha = GetDistanceBasedIconAlpha(activeOrder.Order.TargetSpatialEntity, maxDistance: 450.0f);
                        if (iconAlpha <= 0.0f) { continue; }
                        DrawOrderIndicator(spriteBatch, cam, character, activeOrder.Order,
                            iconAlpha: iconAlpha, createOffset: false, scaleMultiplier: 0.5f, overrideAlpha: true);
                    }
                }

                if (character.GetCurrentOrderWithTopPriority() is Order currentOrder && DrawIcon(currentOrder))
                {
                    DrawOrderIndicator(spriteBatch, cam, character, currentOrder, 1.0f);
                }

                static bool DrawIcon(Order o) =>
                    o != null &&
                    (!(o.TargetEntity is Item i) ||
                     o.DrawIconWhenContained ||
                     i.GetRootInventoryOwner() == i);
            }

            if (GameMain.GameSession != null)
            {
                foreach (var mission in GameMain.GameSession.Missions)
                {
                    if (!mission.DisplayTargetHudIcons) { continue; }
                    foreach (var target in mission.HudIconTargets)
                    {
                        if (target.Submarine != character.Submarine) { continue; }
                        float alpha = GetDistanceBasedIconAlpha(target, maxDistance: mission.Prefab.HudIconMaxDistance);
                        if (alpha <= 0.0f) { continue; }
                        GUI.DrawIndicator(spriteBatch, target.DrawPosition, cam, 100.0f, mission.Prefab.HudIcon, mission.Prefab.HudIconColor * alpha);
                    }
                }
            }

            foreach (Character.ObjectiveEntity objectiveEntity in character.ActiveObjectiveEntities)
            {
                DrawObjectiveIndicator(spriteBatch, cam, character, objectiveEntity, 1.0f);
            }

            foreach (Item brokenItem in brokenItems)
            {
                if (!brokenItem.IsInteractable(character)) { continue; }
                float alpha = GetDistanceBasedIconAlpha(brokenItem);
                if (alpha <= 0.0f) { continue; }
                GUI.DrawIndicator(spriteBatch, brokenItem.DrawPosition, cam, 100.0f, GUIStyle.BrokenIcon.Value.Sprite, 
                    Color.Lerp(GUIStyle.Red, GUIStyle.Orange * 0.5f, brokenItem.Condition / brokenItem.MaxCondition) * alpha);
            }

            float GetDistanceBasedIconAlpha(ISpatialEntity target, float maxDistance = 1000.0f)
            {
                float dist = Vector2.Distance(character.WorldPosition, target.WorldPosition);
                return Math.Min((maxDistance - dist) / maxDistance * 2.0f, 1.0f);
            }

            if (!character.IsIncapacitated && character.Stun <= 0.0f && !IsCampaignInterfaceOpen && (!character.IsKeyDown(InputType.Aim) || character.HeldItems.None(it => it?.GetComponent<Sprayer>() != null)))
            {
                if (character.FocusedCharacter != null && character.FocusedCharacter.CanBeSelected)
                {
                    DrawCharacterHoverTexts(spriteBatch, cam, character);
                }

                if (character.FocusedItem != null)
                {
                    if (focusedItem != character.FocusedItem)
                    {
                        focusedItemOverlayTimer = Math.Min(1.0f, focusedItemOverlayTimer);
                        RecreateHudTexts = true;
                    }
                    focusedItem = character.FocusedItem;
                }

                if (focusedItem != null && focusedItemOverlayTimer > ItemOverlayDelay)
                {
                    Vector2 circlePos = cam.WorldToScreen(focusedItem.DrawPosition);
                    float circleSize = Math.Max(focusedItem.Rect.Width, focusedItem.Rect.Height) * 1.5f;
                    circleSize = MathHelper.Clamp(circleSize, 45.0f, 100.0f) * Math.Min((focusedItemOverlayTimer - 1.0f) * 5.0f, 1.0f);
                    if (circleSize > 0.0f)
                    {
                        Vector2 scale = new Vector2(circleSize / GUIStyle.FocusIndicator.FrameSize.X);
                        GUIStyle.FocusIndicator.Draw(spriteBatch,
                            (int)((focusedItemOverlayTimer - 1.0f) * GUIStyle.FocusIndicator.FrameCount * 3.0f),
                            circlePos,
                            Color.LightBlue * 0.3f,
                            origin: GUIStyle.FocusIndicator.FrameSize.ToVector2() / 2,
                            rotate: (float)Timing.TotalTime,
                            scale: scale);
                    }

                    if (!GUI.DisableItemHighlights && !Inventory.DraggingItemToWorld)
                    {
                        bool hudTextsContextual = PlayerInput.IsShiftDown();
                        if (RecreateHudTexts || lastHudTextsContextual != hudTextsContextual)
                        {
                            RecreateHudTexts = true;
                            lastHudTextsContextual = hudTextsContextual;
                        }
                        var hudTexts = focusedItem.GetHUDTexts(character, RecreateHudTexts);
                        RecreateHudTexts = false;

                        int dir = Math.Sign(focusedItem.WorldPosition.X - character.WorldPosition.X);

                        Vector2 textSize = GUIStyle.Font.MeasureString(hudTexts.First().Text);
                        Vector2 largeTextSize = GUIStyle.SubHeadingFont.MeasureString(hudTexts.First().Text);

                        Vector2 startPos = cam.WorldToScreen(focusedItem.DrawPosition);
                        startPos.Y -= (hudTexts.Count + 1) * textSize.Y;
                        if (focusedItem.Sprite != null)
                        {
                            startPos.X += (int)(circleSize * 0.4f * dir);
                            startPos.Y -= (int)(circleSize * 0.4f);
                        }

                        Vector2 textPos = startPos;
                        if (dir == -1) { textPos.X -= largeTextSize.X; }

                        float alpha = MathHelper.Clamp((focusedItemOverlayTimer - ItemOverlayDelay) * 2.0f, 0.0f, 1.0f);

                        GUI.DrawString(spriteBatch, textPos, hudTexts.First().Text, hudTexts.First().Color * alpha, Color.Black * alpha * 0.7f, 2, font: GUIStyle.SubHeadingFont, ForceUpperCase.No);
                        startPos.X += dir * 10.0f * GUI.Scale;
                        textPos.X += dir * 10.0f * GUI.Scale;
                        textPos.Y += largeTextSize.Y;
                        foreach (ColoredText coloredText in hudTexts.Skip(1))
                        {
                            if (dir == -1) textPos.X = (int)(startPos.X - GUIStyle.SmallFont.MeasureString(coloredText.Text).X);
                            GUI.DrawString(spriteBatch, textPos, coloredText.Text, coloredText.Color * alpha, Color.Black * alpha * 0.7f, 2, GUIStyle.SmallFont);
                            textPos.Y += textSize.Y;
                        }
                    }                    
                }

                foreach (HUDProgressBar progressBar in character.HUDProgressBars.Values)
                {
                    progressBar.Draw(spriteBatch, cam);
                }

                void DrawInteractionIcon(Entity entity, Identifier iconStyle)
                {
                    if (entity == null || entity.Removed) { return; }

                    Hull currentHull = entity switch
                    {
                        Character character => character.CurrentHull,
                        Item item => item.CurrentHull,
                        _ => null
                    };
                    Range<float> visibleRange = new Range<float>(currentHull == Character.Controlled.CurrentHull ? 500.0f : 100.0f, float.PositiveInfinity);
                    LocalizedString label = null;
                    if (entity is Character characterEntity)
                    {
                        if (characterEntity.IsDead || characterEntity.IsIncapacitated) { return; }
                        if (characterEntity?.CampaignInteractionType == CampaignMode.InteractionType.Examine)
                        {
                            //TODO: we could probably do better than just hardcoding
                            //a check for InteractionType.Examine here.

                            if (Vector2.DistanceSquared(character.Position, entity.Position) > 500f * 500f) { return; }

                            var body = Submarine.CheckVisibility(character.SimPosition, entity.SimPosition, ignoreLevel: true);
                            if (body != null && body.UserData != entity) { return; }

                            visibleRange = new Range<float>(-100f, 500f);
                        }
                        label = characterEntity?.Info?.Title;
                    }

                    if (GUIStyle.GetComponentStyle(iconStyle) is not GUIComponentStyle style) { return; }

                    float dist = Vector2.Distance(character.WorldPosition, entity.WorldPosition);
                    float distFactor = 1.0f - MathUtils.InverseLerp(1000.0f, 3000.0f, dist);
                    float alpha = MathHelper.Lerp(0.3f, 1.0f, distFactor);
                    GUI.DrawIndicator(
                        spriteBatch,
                        entity.WorldPosition,
                        cam,
                        visibleRange,
                        style.GetDefaultSprite(),
                        style.Color * alpha,
                        label: label);
                }

                foreach (Character npc in Character.CharacterList)
                {
                    if (npc.CampaignInteractionType == CampaignMode.InteractionType.None) { continue; }
                    DrawInteractionIcon(npc, ("CampaignInteractionIcon." + npc.CampaignInteractionType).ToIdentifier());
                }

                if (GameMain.GameSession?.GameMode is TutorialMode tutorialMode && tutorialMode.Tutorial is not null)
                {
                    foreach (var (entity, iconStyle) in tutorialMode.Tutorial.Icons)
                    {
                        DrawInteractionIcon(entity, iconStyle);
                    }
                }

                foreach (Item item in Item.ItemList)
                {
                    if (item.IconStyle is null || item.Submarine != character.Submarine) { continue; }
                    if (Vector2.DistanceSquared(character.Position, item.Position) > 500f * 500f) { continue; }
                    var body = Submarine.CheckVisibility(character.SimPosition, item.SimPosition, ignoreLevel: true);
                    if (body != null && body.UserData as Item != item) { continue; }
                    GUI.DrawIndicator(spriteBatch, item.WorldPosition + new Vector2(0f, item.RectHeight * 0.65f), cam, new Range<float>(-100f, 500.0f), item.IconStyle.GetDefaultSprite(), item.IconStyle.Color, createOffset: false);
                }
            }

            if (character.SelectedItem != null && 
                (character.CanInteractWith(character.SelectedItem) || Screen.Selected == GameMain.SubEditorScreen))
            {
                character.SelectedItem.DrawHUD(spriteBatch, cam, character);
            }
            if (character.Inventory != null)
            {
                foreach (Item item in character.Inventory.AllItems)
                {
                    if (character.HasEquippedItem(item))
                    {
                        item.DrawHUD(spriteBatch, cam, character);
                    }
                }
            }

            if (IsCampaignInterfaceOpen) { return; }

            if (character.Inventory != null)
            {
                for (int i = 0; i < character.Inventory.Capacity; i++)
                {
                    var item = character.Inventory.GetItemAt(i);
                    if (item == null || character.Inventory.SlotTypes[i] == InvSlotType.Any) { continue; }
                    //if the item is also equipped in another slot we already went through, don't draw the hud again
                    bool duplicateFound = false;
                    for (int j = 0; j < i; j++)
                    {
                        if (character.Inventory.SlotTypes[j] != InvSlotType.Any && character.Inventory.GetItemAt(j) == item)
                        {
                            duplicateFound = true;
                            break;
                        }
                    }
                    if (duplicateFound) { continue; }
                    foreach (ItemComponent ic in item.Components)
                    {
                        if (ic.DrawHudWhenEquipped) { ic.DrawHUD(spriteBatch, character); }
                    }
                }
            }

            bool mouseOnPortrait = false;
            if (character.Stun <= 0.1f && !character.IsDead)
            {
                bool wiringMode = Screen.Selected == GameMain.SubEditorScreen && GameMain.SubEditorScreen.WiringMode;
                if (CharacterHealth.OpenHealthWindow == null && character.SelectedCharacter == null && !wiringMode)
                {
                    if (character.Info != null && !character.ShouldLockHud())
                    {
                        character.Info.DrawBackground(spriteBatch);
                        character.Info.DrawJobIcon(spriteBatch,
                            new Rectangle(
                                (int)(HUDLayoutSettings.BottomRightInfoArea.X + HUDLayoutSettings.BottomRightInfoArea.Width * 0.05f),
                                (int)(HUDLayoutSettings.BottomRightInfoArea.Y + HUDLayoutSettings.BottomRightInfoArea.Height * 0.1f),
                                (int)(HUDLayoutSettings.BottomRightInfoArea.Width / 2),
                                (int)(HUDLayoutSettings.BottomRightInfoArea.Height * 0.7f)), character.Info.IsDisguisedAsAnother);
                        float yOffset = (GameMain.GameSession?.Campaign is MultiPlayerCampaign ? -10 : 4) * GUI.Scale;
                        character.Info.DrawPortrait(spriteBatch, HUDLayoutSettings.PortraitArea.Location.ToVector2(), new Vector2(-12 * GUI.Scale, yOffset), targetWidth: HUDLayoutSettings.PortraitArea.Width, true, character.Info.IsDisguisedAsAnother);
                        character.Info.DrawForeground(spriteBatch);
                    }
                    mouseOnPortrait = MouseOnCharacterPortrait() && !character.ShouldLockHud();
                    if (mouseOnPortrait)
                    {
                        GUIStyle.UIGlow.Draw(spriteBatch, HUDLayoutSettings.BottomRightInfoArea, GUIStyle.Green * 0.5f);
                    }
                }
                if (ShouldDrawInventory(character))
                {
                    character.Inventory.Locked = character == Character.Controlled && LockInventory(character);
                    character.Inventory.DrawOwn(spriteBatch);
                    character.Inventory.CurrentLayout = CharacterHealth.OpenHealthWindow == null && character.SelectedCharacter == null ?
                        CharacterInventory.Layout.Default :
                        CharacterInventory.Layout.Right;
                }
            }

            if (!character.IsIncapacitated && character.Stun <= 0.0f)
            {
                if (character.Params.CanInteract && character.SelectedCharacter != null && character.SelectedCharacter.Inventory != null)
                {
                    if (character.SelectedCharacter.CanInventoryBeAccessed)
                    {
                        character.SelectedCharacter.Inventory.Locked = false;
                        character.SelectedCharacter.Inventory.CurrentLayout = CharacterInventory.Layout.Left;
                        character.SelectedCharacter.Inventory.DrawOwn(spriteBatch);
                    }
                    if (CharacterHealth.OpenHealthWindow == character.SelectedCharacter.CharacterHealth)
                    {
                        character.SelectedCharacter.CharacterHealth.Alignment = Alignment.Left;
                        //character.SelectedCharacter.CharacterHealth.DrawStatusHUD(spriteBatch);
                    }
                }
                else if (character.Inventory != null)
                {
                    //character.Inventory.CurrentLayout = (CharacterHealth.OpenHealthWindow == null) ? Alignment.Center : Alignment.Left;
                }
            }

            if (mouseOnPortrait)
            {
                GUIComponent.DrawToolTip(
                    spriteBatch,
                    character.Info?.Job == null ? character.DisplayName : character.DisplayName + " (" + character.Info.Job.Name + ")",
                    HUDLayoutSettings.PortraitArea);
            }
        }

        public static bool MouseOnCharacterPortrait()
        {
            if (Character.Controlled == null) { return false; }
            if (CharacterHealth.OpenHealthWindow != null || Character.Controlled.SelectedCharacter != null) { return false; }
            return HUDLayoutSettings.BottomRightInfoArea.Contains(PlayerInput.MousePosition);
        }

        private static void DrawCharacterHoverTexts(SpriteBatch spriteBatch, Camera cam, Character character)
        {
            var allItems = character.Inventory?.AllItems;
            if (allItems != null)
            {
                foreach (Item item in allItems)
                {
                    var statusHUD = item?.GetComponent<StatusHUD>();
                    if (statusHUD != null && statusHUD.IsActive && statusHUD.VisibleCharacters.Contains(character.FocusedCharacter))
                    {
                        return;
                    }
                }
            }

            float dist = Vector2.Distance(character.FocusedCharacter.DrawPosition, character.DrawPosition);
            Vector2 startPos = character.DrawPosition + (character.FocusedCharacter.DrawPosition - character.DrawPosition) / dist * Math.Min(dist, Character.MaxDragDistance);
            startPos = cam.WorldToScreen(startPos);

            string focusName = character.FocusedCharacter.Info == null ? character.FocusedCharacter.DisplayName : character.FocusedCharacter.Info.DisplayName;
            Vector2 textPos = startPos;
            Vector2 textSize = GUIStyle.Font.MeasureString(focusName);
            Vector2 largeTextSize = GUIStyle.SubHeadingFont.MeasureString(focusName);

            textPos -= new Vector2(textSize.X / 2, textSize.Y);

            Color nameColor = character.FocusedCharacter.GetNameColor();
            GUI.DrawString(spriteBatch, textPos, focusName, nameColor, Color.Black * 0.7f, 2, GUIStyle.SubHeadingFont, ForceUpperCase.No);
            textPos.Y += GUIStyle.SubHeadingFont.MeasureString(focusName).Y;

            if (character.FocusedCharacter.Info?.Title != null && 
                !character.FocusedCharacter.Info.Title.IsNullOrEmpty() && 
                character.FocusedCharacter.TeamID != CharacterTeamType.Team1)
            {
                GUI.DrawString(spriteBatch, textPos, character.FocusedCharacter.Info.Title, nameColor, Color.Black * 0.7f, 2, GUIStyle.SubHeadingFont, ForceUpperCase.No);
                textPos.Y += GUIStyle.SubHeadingFont.MeasureString(character.FocusedCharacter.Info.Title.Value).Y;
            }
            textPos.X += 10.0f * GUI.Scale;

            if (!character.FocusedCharacter.IsIncapacitated && character.FocusedCharacter.IsPet)
            {
                GUI.DrawString(spriteBatch, textPos, GetCachedHudText("PlayHint", InputType.Use),
                    GUIStyle.Green, Color.Black, 2, GUIStyle.SmallFont);
                textPos.Y += largeTextSize.Y;
            }

            if (character.FocusedCharacter.CanBeDragged)
            {
                string text = character.CanEat ? "EatHint" : "GrabHint";
                GUI.DrawString(spriteBatch, textPos, GetCachedHudText(text, InputType.Grab),
                    GUIStyle.Green, Color.Black, 2, GUIStyle.SmallFont);
                textPos.Y += largeTextSize.Y;
            }

            if (!character.DisableHealthWindow &&
                character.IsFriendly(character.FocusedCharacter) && 
                character.FocusedCharacter.CharacterHealth.UseHealthWindow &&
                character.CanInteractWith(character.FocusedCharacter, 160f, false))
            {
                GUI.DrawString(spriteBatch, textPos, GetCachedHudText("HealHint", InputType.Health),
                    GUIStyle.Green, Color.Black, 2, GUIStyle.SmallFont);
                textPos.Y += textSize.Y;
            }
            if (!character.FocusedCharacter.CustomInteractHUDText.IsNullOrEmpty() && character.FocusedCharacter.AllowCustomInteract)
            {
                GUI.DrawString(spriteBatch, textPos, character.FocusedCharacter.CustomInteractHUDText, GUIStyle.Green, Color.Black, 2, GUIStyle.SmallFont);
                textPos.Y += textSize.Y;
            }
        }

        public static void ShowBossHealthBar(Character character, float damage)
        {
            if (character == null || character.IsDead || character.Removed) { return; }
            if (bossProgressBars.Any(b => b.IsDuplicate(character))) { return; }
            AddBossProgressBar(new BossHealthBar(character));
        }

        public static void ShowMissionProgressBar(Mission mission)
        {
            if (mission == null || mission.Completed || mission.Failed) { return; }
            if (bossProgressBars.Any(b => b.IsDuplicate(mission))) { return; }
            AddBossProgressBar(new MissionProgressBar(mission));
        }

        public static void ClearBossProgressBars()
        {
            for (int i = bossProgressBars.Count - 1; i>= 0; i--)
            {
                RemoveBossProgressBar(bossProgressBars[i]);
            }
            bossProgressBars.Clear();
        }

        private static void RemoveBossProgressBar(BossProgressBar progressBar)
        {
            progressBar.SideContainer.Parent?.RemoveChild(progressBar.SideContainer);
            progressBar.TopContainer.Parent?.RemoveChild(progressBar.TopContainer);
            bossProgressBars.Remove(progressBar);
        }

        private static void AddBossProgressBar(BossProgressBar progressBar)
        {
            var healthBarMode = GameMain.NetworkMember?.ServerSettings.ShowEnemyHealthBars ?? GameSettings.CurrentConfig.ShowEnemyHealthBars;
            if (healthBarMode == EnemyHealthBarMode.HideAll)
            {
                return;
            }
            if (bossProgressBars.Count > 5)
            {
                BossProgressBar oldestHealthBar = bossProgressBars.First();
                foreach (var bar in bossProgressBars)
                {
                    if (bar.TopHealthBar.BarSize < oldestHealthBar.TopHealthBar.BarSize)
                    {
                        oldestHealthBar = bar;
                    }
                }
                oldestHealthBar.FadeTimer = Math.Min(oldestHealthBar.FadeTimer, 1.0f);
            }
            bossProgressBars.Add(progressBar);
        }

        public static void UpdateBossProgressBars(float deltaTime)
        {
            var healthBarMode = GameMain.NetworkMember?.ServerSettings.ShowEnemyHealthBars ?? GameSettings.CurrentConfig.ShowEnemyHealthBars;

            for (int i = 0; i < bossProgressBars.Count; i++)
            {
                var bossHealthBar = bossProgressBars[i];

                bool showTopBar = i == bossProgressBars.Count - 1;
                if (showTopBar && !bossHealthBar.TopContainer.Visible)
                {
                    bossHealthBar.SideContainer.SetAsLastChild();
                    SetColor(bossHealthBar, bossHealthBar.SideContainer, 0);
                }

                bossHealthBar.TopContainer.Visible = showTopBar;
                bossHealthBar.SideContainer.Visible = !bossHealthBar.TopContainer.Visible;

                bossHealthBar.TopHealthBar.BarSize = bossHealthBar.SideHealthBar.BarSize = bossHealthBar.State;
                float alpha = Math.Min(bossHealthBar.FadeTimer, 1.0f);

                if (bossHealthBar.TopContainer.Visible)
                {
                    SetColor(bossHealthBar, bossHealthBar.TopContainer, alpha);
                }
                if (bossHealthBar.SideContainer.Visible)
                {
                    SetColor(bossHealthBar, bossHealthBar.SideContainer, alpha);
                }

                static void SetColor(BossProgressBar bossHealthBar, GUIComponent container, float alpha)
                {
                    foreach (var component in container.GetAllChildren())
                    {
                        component.Color = new Color(bossHealthBar.Color, (byte)(alpha * 255));
                        if (component is GUITextBlock textBlock)
                        {
                            textBlock.TextColor = new Color(bossHealthBar.Completed ? Color.Gray : textBlock.TextColor, (byte)(alpha * 255));
                        }
                    }
                }

                if (bossHealthBar.Interrupted)
                {
                    bossHealthBar.FadeTimer = Math.Min(bossHealthBar.FadeTimer, 1.0f);
                }
                else if (bossHealthBar.Completed)
                {
                    bossHealthBar.FadeTimer = Math.Min(bossHealthBar.FadeTimer, 5.0f);
                }
                bossHealthBar.FadeTimer -= deltaTime;
            }
            for (int i = bossProgressBars.Count - 1; i >= 0 ; i--)
            {
                var bossHealthBar = bossProgressBars[i];
                if (bossHealthBar.FadeTimer <= 0 || healthBarMode == EnemyHealthBarMode.HideAll)
                {
                    bossHealthBar.SideContainer.Parent?.RemoveChild(bossHealthBar.SideContainer);
                    bossHealthBar.TopContainer.Parent?.RemoveChild(bossHealthBar.TopContainer);
                    bossProgressBars.RemoveAt(i);
                    bossHealthContainer.Recalculate();
                }
            }
        }

        private static bool LockInventory(Character character)
        {
            if (character?.Inventory == null || !character.AllowInput || character.LockHands || IsCampaignInterfaceOpen) { return true; }
            return character.ShouldLockHud();
        }

        /// <param name="overrideAlpha">Override the distance-based alpha value with the iconAlpha parameter value</param>
        private static void DrawOrderIndicator(SpriteBatch spriteBatch, Camera cam, Character character, Order order,
            float iconAlpha = 1.0f, bool createOffset = true, float scaleMultiplier = 1.0f, bool overrideAlpha = false)
        {
            if (order?.SymbolSprite == null) { return; }
            if (order.IsReport && order.OrderGiver != character && !order.HasAppropriateJob(character)) { return; }

            ISpatialEntity target = order.ConnectedController?.Item ?? order.TargetSpatialEntity;
            if (target == null) { return; }

            //don't show the indicator if far away and not inside the same sub
            //prevents exploiting the indicators in locating the sub
            if (character.Submarine != target.Submarine && 
                Vector2.DistanceSquared(character.WorldPosition, target.WorldPosition) > 1000.0f * 1000.0f)
            {
                return;
            }

            if (!orderIndicatorCount.ContainsKey(target)) { orderIndicatorCount.Add(target, 0); }

            Vector2 drawPos = target is Entity ? (target as Entity).DrawPosition :
                target.Submarine == null ? target.Position : target.Position + target.Submarine.DrawPosition;
            drawPos += Vector2.UnitX * order.SymbolSprite.size.X * 1.5f * orderIndicatorCount[target];
            GUI.DrawIndicator(spriteBatch, drawPos, cam, 100.0f, order.SymbolSprite, order.Color * iconAlpha,
                createOffset: createOffset, scaleMultiplier: scaleMultiplier, overrideAlpha: overrideAlpha ? (float?)iconAlpha : null);

            orderIndicatorCount[target] = orderIndicatorCount[target] + 1;
        }        

        private static void DrawObjectiveIndicator(SpriteBatch spriteBatch, Camera cam, Character character, Character.ObjectiveEntity objectiveEntity, float iconAlpha = 1.0f)
        {
            if (objectiveEntity == null) return;

            Vector2 drawPos = objectiveEntity.Entity.WorldPosition;// + Vector2.UnitX * objectiveEntity.Sprite.size.X * 1.5f;
            GUI.DrawIndicator(spriteBatch, drawPos, cam, 100.0f, objectiveEntity.Sprite, objectiveEntity.Color * iconAlpha);
        }

        static partial void RecreateHudTextsIfControllingProjSpecific(Character character)
        {
            if (character == Character.Controlled)
            {
                RecreateHudTexts = true;
            }
        }

        static partial void RecreateHudTextsIfFocusedProjSpecific(params Item[] items)
        {
            foreach (var item in items)
            {
                if (item == Character.Controlled?.FocusedItem)
                {
                    RecreateHudTexts = true;
                    break;
                }
            }
        }
    }
}
