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

namespace Barotrauma
{
    class ChatBox
    {
        public const string RadioChatString = "r; ";

        private readonly GUIListBox chatBox;
        private Point screenResolution;

        public readonly ChatManager ChatManager = new ChatManager();

        public bool IsSinglePlayer { get; private set; }
        
        private bool _toggleOpen = true;
        public bool ToggleOpen
        {
            get => _toggleOpen;
            set => SetToggleOpenState(value, setPreference: true);
        }
        
        public static ChatBox GetChatBox()
        {
            if (GameMain.GameSession?.GameMode is not GameMode gameMode) { return null; }
            return gameMode.IsSinglePlayer ? GameMain.GameSession.CrewManager?.ChatBox : GameMain.Client?.ChatBox;
        }
        
        public static void AutoHideChatBox() => SetChatBoxOpen(false);
        
        private void SetToggleOpenState(bool value, bool setPreference = true)
        {
            _toggleOpen = value;
            if (setPreference)
            {
                PreferChatBoxOpen = value;
            }
            if (value) { hideableElements.Visible = true; }
        }
        
        public static void ResetChatBoxOpenState() => GetChatBox()?.ResetOpenState();
        
        public void ResetOpenState() => SetOpen(PreferChatBoxOpen);
        
        private static void SetChatBoxOpen(bool isOpen) => GetChatBox()?.SetOpen(isOpen);
        
        private void SetOpen(bool value) => SetToggleOpenState(value, setPreference: false);
        
        private float openState;

        public static bool PreferChatBoxOpen = true;

        public bool CloseAfterMessageSent;

        private float prevUIScale;

        private readonly GUIFrame channelSettingsFrame;
        private readonly GUITextBlock radioJammedWarning;
        private readonly GUITextBox channelText;
        private readonly GUILayoutGroup channelPickerContent;
        private readonly GUIButton memButton;
        private WifiComponent prevRadio;

        private bool channelMemPending;

        //individual message texts that pop up when the chatbox is hidden
        const float PopupMessageDuration = 5.0f;
        private readonly List<GUIComponent> popupMessages = new List<GUIComponent>();

        public GUITextBox.OnEnterHandler OnEnterMessage
        {
            get { return InputBox.OnEnterPressed; }
            set { InputBox.OnEnterPressed = value; }
        }

        public GUIFrame GUIFrame { get; private set; }

        public GUITextBox InputBox { get; private set; }

        private GUIButton toggleButton;
        
        public GUIButton ToggleButton
        {
            get => toggleButton;
            set
            {
                if (toggleButton != null)
                {
                    toggleButton.RectTransform.Parent = null;
                }
                toggleButton = value;
            }
        }

        private readonly GUIButton showNewMessagesButton;

        private readonly GUIFrame hideableElements;

        public const int ToggleButtonWidthRaw = 30;
        private int popupMessageOffset;

        private GUIDropDown ChatModeDropDown { get; set; }

        public ChatBox(GUIComponent parent, bool isSinglePlayer)
        {
            this.IsSinglePlayer = isSinglePlayer;

            screenResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);

            int toggleButtonWidth = (int)(ToggleButtonWidthRaw * GUI.Scale);
            GUIFrame = new GUIFrame(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.ChatBoxArea, parent.RectTransform), style: null);

            hideableElements = new GUIFrame(new RectTransform(Vector2.One, GUIFrame.RectTransform), style: null);

            var chatBoxHolder = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.875f), hideableElements.RectTransform), style: "ChatBox");
            chatBox = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.95f), chatBoxHolder.RectTransform, Anchor.CenterRight), style: null);

            // channel settings -----------------------------------------------------------------------------

            channelSettingsFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.2f), chatBoxHolder.RectTransform, Anchor.TopCenter, Pivot.BottomCenter) { MinSize = new Point(0, 25) }, style: "GUIFrameBottom");
            var channelSettingsContent = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.9f), channelSettingsFrame.RectTransform, Anchor.Center), isHorizontal: true, childAnchor: Anchor.CenterLeft)
            {
                Stretch = true,
                CanBeFocused = true,
                RelativeSpacing = 0.01f
            };

            radioJammedWarning = new GUITextBlock(new RectTransform(Vector2.One, channelSettingsFrame.RectTransform), TextManager.Get("radiojammedwarning"),
                textColor: GUIStyle.Orange, color: Color.Black,
                textAlignment: Alignment.Center, style: "OuterGlow")
            {
                ToolTip = TextManager.Get("hint.radiojammed")
            };

            var buttonLeft = new GUIButton(new RectTransform(new Vector2(0.1f, 0.8f), channelSettingsContent.RectTransform), style: "DeviceButton")
            {
                PlaySoundOnSelect = false,
                OnClicked = (btn, userdata) =>
                {
                    if (Character.Controlled != null && ChatMessage.CanUseRadio(Character.Controlled, out WifiComponent radio))
                    {
                        SetChannel(radio.Channel - 1, setText: true);
                        SoundPlayer.PlayUISound(GUISoundType.PopupMenu);
                    }
                    return true;
                }
            };
            var arrowIcon = new GUIImage(new RectTransform(new Vector2(0.4f), buttonLeft.RectTransform, Anchor.Center), style: "GUIButtonHorizontalArrow", scaleToFit: true)
            {
                Color = new Color(51, 59, 46),
                SpriteEffects = Microsoft.Xna.Framework.Graphics.SpriteEffects.FlipHorizontally
            };
            arrowIcon.HoverColor = arrowIcon.PressedColor = arrowIcon.SelectedColor = arrowIcon.Color;

            channelText = new GUITextBox(new RectTransform(new Vector2(0.25f, 0.8f), channelSettingsContent.RectTransform), style: "DigitalFrameLight", textAlignment: Alignment.Center, font: GUIStyle.DigitalFont)
            {
                textFilterFunction = text => 
                {
                    var str = new string(text.Where(c => char.IsNumber(c)).ToArray());
                    if (str.Length > 4) { str = str.Substring(0, 4); }
                    return str;
                },
                OnEnterPressed = (tb, text) =>
                {
                    tb.Deselect();
                    return true;
                }
            };
            Vector2 textSize = channelText.Font.MeasureString("0000");
            channelText.TextBlock.ToolTip = TextManager.Get("currentradiochannel");
            channelText.TextBlock.TextScale = Math.Min(channelText.Rect.Height / textSize.Y * 0.9f, 1.0f);
            channelText.OnDeselected += (sender, key) =>
            {
                int.TryParse(channelText.Text, out int newChannel);
                SetChannel(newChannel, setText: true);
                SoundPlayer.PlayUISound(GUISoundType.PopupMenu);
            };

            var buttonRight = new GUIButton(new RectTransform(new Vector2(0.1f, 0.8f), channelSettingsContent.RectTransform), style: "DeviceButton")
            {
                PlaySoundOnSelect = false,
                OnClicked = (btn, userdata) =>
                {
                    if (Character.Controlled != null && ChatMessage.CanUseRadio(Character.Controlled, out WifiComponent radio))
                    {
                        SetChannel(radio.Channel + 1, setText: true);
                        SoundPlayer.PlayUISound(GUISoundType.PopupMenu);
                    }
                    return true;
                }
            };
            arrowIcon = new GUIImage(new RectTransform(new Vector2(0.4f), buttonRight.RectTransform, Anchor.Center), style: "GUIButtonHorizontalArrow", scaleToFit: true)
            {
                Color = new Color(51, 59, 46)
            };
            arrowIcon.HoverColor = arrowIcon.PressedColor = arrowIcon.PressedColor = arrowIcon.Color;

            var channelPicker = new GUIFrame(new RectTransform(new Vector2(0.4f, 0.6f), channelSettingsContent.RectTransform), "InnerFrame");
            channelPickerContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), channelPicker.RectTransform, Anchor.Center), isHorizontal: true, childAnchor: Anchor.CenterLeft)
            {
                Stretch = true
            };
            for (int i = 0; i < 10; i++)
            {
                new GUIButton(new RectTransform(new Vector2(0.1f, 1.0f), channelPickerContent.RectTransform), i.ToString(), style: "GUITextBlock")
                {
                    TextColor = new Color(51, 59, 46),
                    SelectedTextColor = GUIStyle.Green,
                    UserData = i,
                    PlaySoundOnSelect = false,
                    OnClicked = (btn, userdata) =>
                    {
                        if (Character.Controlled != null && ChatMessage.CanUseRadio(Character.Controlled, out WifiComponent radio))
                        {
                            int index = (int)userdata;                        
                            if (channelMemPending)
                            {
                                int.TryParse(channelText.Text, out int newChannel);
                                SetChannelMemory(index, newChannel);
                                btn.ToolTip = TextManager.GetWithVariables("radiochannelpreset",
                                    ("[index]", index.ToString()),
                                    ("[channel]", radio.GetChannelMemory(index).ToString()));
                                channelMemPending = false;
                                channelPickerContent.Children.First().CanBeFocused = true;
                                memButton.Enabled = true;
                                channelPickerContent.Flash(GUIStyle.Green);
                                channelText.Flash(GUIStyle.Green);
                            }
                            SetChannel(radio.GetChannelMemory(index), setText: true);
                            SoundPlayer.PlayUISound(GUISoundType.PopupMenu);
                        }
                        return true;
                    }
                };
            }

            memButton = new GUIButton(new RectTransform(new Vector2(0.2f, 0.9f), channelSettingsContent.RectTransform), style: "DeviceButton", text: TextManager.Get("saveradiochannelbutton"))
            {
                ToolTip = TextManager.Get("saveradiochannelbuttontooltip"),
                OnClicked = (btn, userdata) =>
                {
                    channelMemPending = true;
                    //don't allow storing a value in the first preset
                    channelPickerContent.Children.First().CanBeFocused = false;
                    foreach (GUIComponent channelButton in channelPickerContent.Children)
                    {
                        channelButton.Selected = false;
                    }
                    btn.Enabled = false;
                    return true;
                }
            };

            // ---------------------------------------------------------------------------------------------

            var bottomContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.125f), hideableElements.RectTransform, Anchor.BottomLeft), isHorizontal: true)
            {
                Stretch = true
            };

            var dropdownRt = new RectTransform(new Vector2(0.1f, 1.0f), bottomContainer.RectTransform)
            {
                // The chat mode selection dropdown will take a maximum of 45% of the horizontal space
                MaxSize = new Point((int)(0.45f * bottomContainer.RectTransform.NonScaledSize.X), int.MaxValue)
            };
            var chatModes = new ChatMode[] { ChatMode.Local, ChatMode.Radio };
            ChatModeDropDown = new GUIDropDown(dropdownRt, elementCount: chatModes.Length, dropAbove: true)
            {
                OnSelected = (component, userdata) =>
                {
                    GameMain.ActiveChatMode = (ChatMode)userdata;
                    if (InputBox != null && InputBox.Text.StartsWith(RadioChatString) && GameMain.ActiveChatMode == ChatMode.Local)
                    {
                        string text = InputBox.Text;
                        InputBox.Text = text.Remove(0, RadioChatString.Length);
                    }
                    return true;
                }
            };
            float longestDropDownOption = 0.0f;
            foreach (ChatMode mode in chatModes)
            {
                var text = TextManager.Get($"chatmode.{mode}");
                ChatModeDropDown.AddItem(text, userData: mode);
                if (ChatModeDropDown.ListBox.Content.GetChildByUserData(mode) is GUITextBlock textBlock)
                {
                    if (textBlock.TextSize.X > longestDropDownOption)
                    {
                        longestDropDownOption = textBlock.TextSize.X;
                    }
                }
            }
            ChatModeDropDown.SelectItem(GameMain.ActiveChatMode);

            float minDropDownWidth = longestDropDownOption + ChatModeDropDown.Padding.X +
               (ChatModeDropDown.DropDownIcon?.RectTransform.NonScaledSize.X ?? 0) +
               (ChatModeDropDown.DropDownIcon?.RectTransform.AbsoluteOffset.X ?? 0) * 2;
            ChatModeDropDown.RectTransform.MinSize = new Point(
                Math.Max((int)minDropDownWidth, ChatModeDropDown.RectTransform.MinSize.X),
                ChatModeDropDown.RectTransform.MinSize.Y);

            InputBox = new GUITextBox(new RectTransform(new Vector2(0.9f, 1.0f), bottomContainer.RectTransform),
                style: "ChatTextBox")
            {
                OverflowClip = true,
                Font = GUIStyle.SmallFont,
                MaxTextLength = ChatMessage.MaxLength
            };

            ChatManager.RegisterKeys(InputBox, ChatManager);
            
            InputBox.OnDeselected += (gui, Keys) =>
            {
                ChatManager.Clear();
                if (GUIFrame.IsParentOf(GUI.MouseOn))
                {
                    CloseAfterMessageSent = false;
                    return;
                }
                ChatMessage.GetChatMessageCommand(InputBox.Text, out var message);
                if (string.IsNullOrEmpty(message))
                {
                    if (CloseAfterMessageSent)
                    {
                        _toggleOpen = false;
                        CloseAfterMessageSent = false;
                    }
                }
            };

            var chatSendButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.7f), InputBox.RectTransform, Anchor.CenterRight, scaleBasis: ScaleBasis.BothHeight), style: "GUIButtonToggleRight");
            chatSendButton.OnClicked += (GUIButton btn, object userdata) =>
            {
                InputBox.OnEnterPressed(InputBox, InputBox.Text);
                return true;
            };
            chatSendButton.RectTransform.AbsoluteOffset = new Point((int)(InputBox.Rect.Height * 0.15f), 0);
            InputBox.TextBlock.RectTransform.MaxSize 
                = new Point((int)(InputBox.Rect.Width - chatSendButton.Rect.Width * 1.25f - InputBox.TextBlock.Padding.X - chatSendButton.RectTransform.AbsoluteOffset.X), int.MaxValue);

            showNewMessagesButton = new GUIButton(new RectTransform(new Vector2(1f, 0.075f), GUIFrame.RectTransform, Anchor.BottomCenter) { RelativeOffset = new Vector2(0.0f, 0.125f) }, TextManager.Get("chat.shownewmessages"));
            showNewMessagesButton.OnClicked += (GUIButton btn, object userdata) =>
            {
                chatBox.ScrollBar.BarScrollValue = 1f;
                showNewMessagesButton.Visible = false;
                return true;
            };

            showNewMessagesButton.Visible = false;
            SetToggleOpenState(GameSettings.CurrentConfig.ChatOpen, setPreference: true);
        }

        public void Toggle()
        {
            ToggleOpen = !ToggleOpen;
            CloseAfterMessageSent = false;
        }

        public bool TypingChatMessage(GUITextBox textBox, string text)
        {
            string command = ChatMessage.GetChatMessageCommand(text, out _);
            if (IsSinglePlayer)
            {
                //radio is the only allowed special message type in single player
                if (command != "r" && command != "radio")
                {
                    command = "";
                }
            }

            Color textColor;
            switch (command)
            {
                case "r":
                case "radio":
                    textColor = ChatMessage.MessageColor[(int)ChatMessageType.Radio];
                    break;
                case "d":
                case "dead":
                    textColor = ChatMessage.MessageColor[(int)ChatMessageType.Dead];
                    break;
                default:
                    if (Character.Controlled != null && (Character.Controlled.IsDead || Character.Controlled.SpeechImpediment >= 100.0f))
                    {
                        textColor = ChatMessage.MessageColor[(int)ChatMessageType.Dead];
                    }
                    else if (command != "") //PMing
                    {
                        textColor = ChatMessage.MessageColor[(int)ChatMessageType.Private];
                    }
                    else if (GameMain.ActiveChatMode == ChatMode.Radio)
                    {
                        textColor = ChatMessage.MessageColor[(int)ChatMessageType.Radio];
                    }
                    else
                    {
                        textColor = ChatMessage.MessageColor[(int)ChatMessageType.Default];
                    }
                    break;
            }
            textBox.TextColor = textBox.TextBlock.SelectedTextColor = textColor;

            return true;
        }

        public void AddMessage(ChatMessage message)
        {
            if (GameMain.IsSingleplayer)
            {
                var should = GameMain.LuaCs.Hook.Call<bool?>("chatMessage", message.Text, message.SenderClient, message.Type, message);
                if (should != null && should.Value) { return; }
            }

            while (chatBox.Content.CountChildren > 60)
            {
                chatBox.RemoveChild(chatBox.Content.Children.First());
            }

            float prevSize = chatBox.BarSize;

            string displayedText = message.TranslatedText;

            string senderName = "";
            Color senderColor = Color.White;
            if (!string.IsNullOrWhiteSpace(message.SenderName))
            {
                senderName = (message.Type == ChatMessageType.Private ? "[PM] " : "") + message.SenderName;
            }
            if (message.SenderCharacter?.Info?.Job != null)
            {
                senderColor = Color.Lerp(message.SenderCharacter.Info.Job.Prefab.UIColor, Color.White, 0.25f);
            }

            var msgHolder = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.0f), chatBox.Content.RectTransform, Anchor.TopCenter), style: null,
                    color: ((chatBox.Content.CountChildren % 2) == 0) ? Color.Transparent : Color.Black * 0.1f);

            GUITextBlock senderNameTimestamp = new GUITextBlock(new RectTransform(new Vector2(0.98f, 0.0f), msgHolder.RectTransform) { AbsoluteOffset = new Point((int)(5 * GUI.Scale), 0) },
                ChatMessage.GetTimeStamp(), textColor: Color.LightGray, font: GUIStyle.SmallFont, textAlignment: Alignment.TopLeft, style: null)
            {
                CanBeFocused = true
            };
            if (!string.IsNullOrEmpty(senderName))
            {
                var senderNameBlock = new GUIButton(new RectTransform(new Vector2(0.8f, 1.0f), senderNameTimestamp.RectTransform) { AbsoluteOffset = new Point((int)(senderNameTimestamp.TextSize.X), 0) },
                    senderName, textAlignment: Alignment.TopLeft, style: null, color: Color.Transparent)
                {
                    TextBlock =
                    {
                        Padding = Vector4.Zero
                    },
                    Font = GUIStyle.SmallFont,
                    CanBeFocused = true,
                    ForceUpperCase = ForceUpperCase.No,
                    UserData = message.SenderClient,
                    PlaySoundOnSelect = false,
                    OnClicked = (_, o) =>
                    {
                        if (!(o is Client client)) { return false; }
                        if (GameMain.NetLobbyScreen != null)
                        {
                            GameMain.NetLobbyScreen.SelectPlayer(client);
                            SoundPlayer.PlayUISound(GUISoundType.Select);
                        }
                        return true;
                    },
                    OnSecondaryClicked = (_, o) =>
                    {
                        if (!(o is Client client)) { return false; }
                        NetLobbyScreen.CreateModerationContextMenu(client);
                        return true;
                    },
                    Text = senderName
                };

                senderNameBlock.RectTransform.NonScaledSize = senderNameBlock.TextBlock.TextSize.ToPoint();
                senderNameBlock.TextBlock.OverrideTextColor(senderColor);
                if (senderNameBlock.UserData != null)
                {
                    senderNameBlock.TextBlock.HoverTextColor = Color.White;
                }
            }

            var msgText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), msgHolder.RectTransform)
                { AbsoluteOffset = new Point((int)(10 * GUI.Scale), senderNameTimestamp == null ? 0 : senderNameTimestamp.Rect.Height) },
                RichString.Rich(displayedText), textColor: message.Color, font: GUIStyle.SmallFont, textAlignment: Alignment.TopLeft, style: null, wrap: true,
                color: ((chatBox.Content.CountChildren % 2) == 0) ? Color.Transparent : Color.Black * 0.1f)
            {
                UserData = message.SenderName,
                CanBeFocused = false
            };
            msgText.CalculateHeightFromText();
            if (msgText.RichTextData != null)
            {
                foreach (var data in msgText.RichTextData)
                {
                    var clickableArea = new GUITextBlock.ClickableArea()
                    {
                        Data = data
                    };
                    if (GameMain.NetLobbyScreen != null && GameMain.NetworkMember != null)
                    {
                        clickableArea.OnClick = GameMain.NetLobbyScreen.SelectPlayer;
                        clickableArea.OnSecondaryClick = GameMain.NetLobbyScreen.ShowPlayerContextMenu;
                    }
                    msgText.ClickableAreas.Add(clickableArea);
                }
            }

            if (message is OrderChatMessage orderChatMsg &&
                Character.Controlled != null &&
                orderChatMsg.TargetCharacter == Character.Controlled)
            {
                msgHolder.Flash(Color.OrangeRed * 0.6f, flashDuration: 5.0f);
            }
            else
            {
                msgHolder.Flash(Color.Yellow * 0.6f);
            }
            msgHolder.RectTransform.SizeChanged += Recalculate;
            Recalculate();
            void Recalculate()
            {
                msgHolder.RectTransform.SizeChanged -= Recalculate;
                //resize the holder to match the size of the message and add some spacing
                msgText.RectTransform.MaxSize = new Point(msgHolder.Rect.Width - msgText.RectTransform.AbsoluteOffset.X, int.MaxValue);
                senderNameTimestamp.RectTransform.MaxSize = new Point(msgHolder.Rect.Width - senderNameTimestamp.RectTransform.AbsoluteOffset.X, int.MaxValue);
                msgHolder.Children.ForEach(c => (c as GUITextBlock)?.CalculateHeightFromText());
                msgHolder.RectTransform.Resize(new Point(msgHolder.Rect.Width, msgHolder.Children.Sum(c => c.Rect.Height) + (int)(10 * GUI.Scale)), resizeChildren: false);
                msgHolder.RectTransform.SizeChanged += Recalculate;
                chatBox.RecalculateChildren();
                chatBox.UpdateScrollBarSize();
            }

            CoroutineManager.StartCoroutine(UpdateMessageAnimation(msgHolder, 0.5f));

            chatBox.UpdateScrollBarSize();
                       
            if (chatBox.ScrollBar.Visible && chatBox.ScrollBar.BarScroll < 1f)
            {
                showNewMessagesButton.Visible = true;
            }

            if (message.Type == ChatMessageType.Server && message.ChangeType != PlayerConnectionChangeType.None)
            {
                TabMenu.StorePlayerConnectionChangeMessage(message);
            }

            if (!ToggleOpen)
            {
                var popupMsg = new GUIFrame(new RectTransform(Vector2.One, GUIFrame.RectTransform), style: "GUIToolTip")
                {
                    UserData = 0.0f,
                    CanBeFocused = false
                };
                var content = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), popupMsg.RectTransform, Anchor.Center));
                Vector2 senderTextSize = Vector2.Zero;
                if (!string.IsNullOrEmpty(senderName))
                {
                    var senderText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform),
                        senderName, textColor: senderColor, style: null, font: GUIStyle.SmallFont)
                    {
                        CanBeFocused = false
                    };
                    senderTextSize = senderText.Font.MeasureString(senderText.WrappedText);
                    senderText.RectTransform.MinSize = new Point(0, senderText.Rect.Height);
                }
                var msgPopupText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform),
                    RichString.Rich(displayedText), textColor: message.Color, font: GUIStyle.SmallFont, textAlignment: Alignment.BottomLeft, style: null, wrap: true)
                {
                    CanBeFocused = false
                };
                msgPopupText.RectTransform.MinSize = new Point(0, msgPopupText.Rect.Height);
                Vector2 msgSize = msgPopupText.Font.MeasureString(msgPopupText.WrappedText);
                int textWidth = (int)Math.Max(msgSize.X + msgPopupText.Padding.X + msgPopupText.Padding.Z, senderTextSize.X) + 10;
                popupMsg.RectTransform.Resize(new Point((int)(textWidth / content.RectTransform.RelativeSize.X) , (int)((senderTextSize.Y + msgSize.Y) / content.RectTransform.RelativeSize.Y)), resizeChildren: true);
                popupMsg.RectTransform.IsFixedSize = true;
                content.Recalculate();
                popupMessages.Add(popupMsg);
            }

            if ((prevSize == 1.0f && chatBox.BarScroll == 0.0f) || (prevSize < 1.0f && chatBox.BarScroll == 1.0f)) { chatBox.BarScroll = 1.0f; }

            GUISoundType soundType = GUISoundType.ChatMessage;
            if (message.Type == ChatMessageType.Radio)
            {
                soundType = GUISoundType.RadioMessage;
            }
            else if (message.Type == ChatMessageType.Dead)
            {
                soundType = GUISoundType.DeadMessage;
            }

            SoundPlayer.PlayUISound(soundType);
        }

        public void SetVisibility(bool visible)
        {
            GUIFrame.Parent.Visible = visible;
        }

        private IEnumerable<CoroutineStatus> UpdateMessageAnimation(GUIComponent message, float animDuration)
        {
            float timer = 0.0f;
            while (timer < animDuration)
            {
                timer += CoroutineManager.DeltaTime;
                float wavePhase = timer / animDuration * MathHelper.TwoPi;
                message.RectTransform.ScreenSpaceOffset =
                    new Point((int)(Math.Sin(wavePhase) * (1.0f - timer / animDuration) * 50.0f), 0);
                yield return CoroutineStatus.Running;
            }
            message.RectTransform.ScreenSpaceOffset = Point.Zero;
            yield return CoroutineStatus.Success;
        }

        private void SetUILayout()
        {
            GUIFrame.RectTransform.AbsoluteOffset = Point.Zero;
            GUIFrame.RectTransform.RelativeOffset = new Vector2(
                HUDLayoutSettings.ChatBoxArea.X / (float)GameMain.GraphicsWidth,
                HUDLayoutSettings.ChatBoxArea.Y / (float)GameMain.GraphicsHeight);
            GUIFrame.RectTransform.NonScaledSize = HUDLayoutSettings.ChatBoxArea.Size;

            int toggleButtonWidth = (int)(ToggleButtonWidthRaw * GUI.Scale);
            GUIFrame.RectTransform.NonScaledSize -= new Point(toggleButtonWidth, 0);
            GUIFrame.RectTransform.AbsoluteOffset += new Point(toggleButtonWidth, 0);

            popupMessageOffset = GameMain.GameSession.CrewManager.ReportButtonFrame.Rect.Width + GUIFrame.Rect.Width + (int)(35 * GUI.Scale);
        }

        public void Update(float deltaTime)
        {
            if (GameMain.GraphicsWidth != screenResolution.X || GameMain.GraphicsHeight != screenResolution.Y || prevUIScale != GUI.Scale)
            {
                SetUILayout();
                screenResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
                prevUIScale = GUI.Scale;
            }

            if (showNewMessagesButton.Visible && chatBox.ScrollBar.BarScroll == 1f)
            {
                showNewMessagesButton.Visible = false;
            }

            if (Screen.Selected == GameMain.GameScreen && GUI.KeyboardDispatcher.Subscriber == null)
            {
                if (PlayerInput.KeyHit(InputType.ToggleChatMode))
                {
                    try
                    {
                        var mode = GameMain.ActiveChatMode switch
                        {
                            ChatMode.Local => ChatMode.Radio,
                            ChatMode.Radio => ChatMode.Local,
                            _ => throw new NotImplementedException()
                        };
                        ChatModeDropDown.SelectItem(mode);
                        // TODO: Play a sound?
                    }
                    catch (NotImplementedException)
                    {
                        DebugConsole.ThrowError($"Error toggling chat mode: not implemented for current mode \"{GameMain.ActiveChatMode}\"");
                    }
                }
                else if (PlayerInput.KeyHit(InputType.ChatBox))
                {
                    Toggle();
                }
            }

            if (ToggleButton != null)
            {
                ToggleButton.Selected = ToggleOpen;
                ToggleButton.RectTransform.AbsoluteOffset = new Point(GUIFrame.Rect.Right, GUIFrame.Rect.Y + HUDLayoutSettings.ChatBoxArea.Height - ToggleButton.Rect.Height);
            }

            if (Character.Controlled != null && ChatMessage.CanUseRadio(Character.Controlled, out WifiComponent radio, ignoreJamming: true))
            {
                if (prevRadio != radio)
                {
                    foreach (GUIComponent presetButton in channelPickerContent.Children)
                    {
                        int index = (int)presetButton.UserData;
                        presetButton.ToolTip = TextManager.GetWithVariables("radiochannelpreset",
                            ("[index]", index.ToString()),
                            ("[channel]", radio.GetChannelMemory(index).ToString()));
                    }
                    SetChannel(radio.Channel, setText: true);
                    prevRadio = radio;
                }
                if (channelMemPending)
                {
                    if (channelPickerContent.FlashTimer <= 0)
                    {
                        channelPickerContent.Flash(GUIStyle.Green, flashRectInflate: new Vector2(GUI.Scale * 5.0f));
                    }
                    if (PlayerInput.PrimaryMouseButtonClicked() && !GUI.IsMouseOn(channelPickerContent))
                    {
                        channelPickerContent.Children.First().CanBeFocused = true;
                        channelMemPending = false;
                        memButton.Enabled = true;
                        SetChannel(radio.Channel, setText: true);
                    }
                }
                channelSettingsFrame.Visible = true;
                radioJammedWarning.Visible = radio is { JamTimer: > 0 };
            }
            else
            {
                radioJammedWarning.Visible = channelSettingsFrame.Visible = false;
                channelPickerContent.Children.First().CanBeFocused = true;
                channelMemPending = false;
                memButton.Enabled = true;
            }

            if (ToggleOpen)
            {
                GUIFrame.CanBeFocused = true;
                openState += deltaTime * 5.0f;
                //delete all popup messages when the chatbox is open
                foreach (var popupMsg in popupMessages)
                {
                    popupMsg.Parent.RemoveChild(popupMsg);
                }
                popupMessages.Clear();
            }
            else
            {
                GUIFrame.CanBeFocused = false;
                openState -= deltaTime * 5.0f;

                int yOffset = 0;
                foreach (var popupMsg in popupMessages)
                {
                    float msgTimer = (float)popupMsg.UserData;

                    int targetYOffset = (int)MathHelper.Lerp(popupMsg.RectTransform.ScreenSpaceOffset.Y, yOffset, deltaTime * 10.0f);

                    if (popupMsg == popupMessages.First())
                    {
                        popupMsg.UserData = msgTimer + deltaTime * Math.Max(popupMessages.Count / 2, 1);
                        if (msgTimer > PopupMessageDuration)
                        {
                            //move the message out of the screen and delete it
                            popupMsg.RectTransform.ScreenSpaceOffset =
                                new Point((int)MathHelper.SmoothStep(popupMessageOffset, 10, (msgTimer - PopupMessageDuration) * 5.0f), targetYOffset);
                            if (msgTimer > PopupMessageDuration + 0.2f)
                            {
                                popupMsg.Parent?.RemoveChild(popupMsg);
                            }
                        }
                    }
                    if (msgTimer < PopupMessageDuration)
                    {
                        if (popupMsg != popupMessages.First()) { popupMsg.UserData = Math.Min(msgTimer + deltaTime, 1.0f); }
                        //move the message on the screen
                        popupMsg.RectTransform.ScreenSpaceOffset = new Point(
                            (int)MathHelper.SmoothStep(0, popupMessageOffset, msgTimer * 5.0f), targetYOffset);
                    }
                    yOffset += popupMsg.Rect.Height + GUI.IntScale(10);
                }

                popupMessages.RemoveAll(p => p.Parent == null);

                //make the first popup message visible
                /*var popupMsg = popupMessages.Count > 0 ? popupMessages.Peek() : null;
                if (popupMsg != null)
                {
                    popupMsg.Visible = true;
                    //popup messages appear and disappear faster when there's more pending messages
                    popupMessageTimer += deltaTime * popupMessages.Count * popupMessages.Count;
                    if (popupMessageTimer > PopupMessageDuration)
                    {
                        //move the message out of the screen and delete it
                        popupMsg.RectTransform.ScreenSpaceOffset =
                            new Point((int)MathHelper.SmoothStep(popupMessageOffset, 10, (popupMessageTimer - PopupMessageDuration) * 5.0f), 0);
                        if (popupMessageTimer > PopupMessageDuration + 1.0f)
                        {
                            popupMessageTimer = 0.0f;
                            popupMsg.Parent.RemoveChild(popupMsg);
                            popupMessages.Dequeue();
                        }
                    }
                    else
                    {
                        //move the message on the screen
                        popupMsg.RectTransform.ScreenSpaceOffset = new Point( 
                            (int)MathHelper.SmoothStep(0, popupMessageOffset, popupMessageTimer * 5.0f), 0);
                    }
                }*/
            }
            openState = MathHelper.Clamp(openState, 0.0f, 1.0f);
            int hiddenBoxOffset = -(GUIFrame.Rect.Width);
            GUIFrame.RectTransform.AbsoluteOffset =
                new Point((int)MathHelper.SmoothStep(hiddenBoxOffset, 0, openState), 0);
            hideableElements.Visible = openState > 0.0f;
        }

        private void SetChannel(int channel, bool setText)
        {
            if (Character.Controlled != null && ChatMessage.CanUseRadio(Character.Controlled, out WifiComponent radio))
            {
                radio.Channel = channel;
                GameMain.Client?.CreateEntityEvent(radio.Item, new Item.ChangePropertyEventData(radio.SerializableProperties["channel".ToIdentifier()], radio));

                if (setText)
                {
                    string text = radio.Channel.ToString().PadLeft(4, '0');
                    if (channelText.Text != text) { channelText.Text = text; }

                }
                if (!channelMemPending)
                {
                    foreach (GUIComponent channelButton in channelPickerContent.Children)
                    {
                        int buttonIndex = (int)channelButton.UserData;
                        channelButton.Selected = radio.GetChannelMemory(buttonIndex) == channel;
                    }
                }
            }
        }
        
        private void SetChannelMemory(int index, int channel)
        {
            if (Character.Controlled != null && ChatMessage.CanUseRadio(Character.Controlled, out WifiComponent radio))
            {
                radio.SetChannelMemory(index, channel);
                radio.Item.CreateClientEvent(radio);
            }
        }

        public void ApplySelectionInputs() => ApplySelectionInputs(InputBox, true, ChatKeyStates.GetChatKeyStates());

        public struct ChatKeyStates
        {
            public bool ActiveChatKeyHit { get; set; }
            public bool LocalChatKeyHit { get; set; }
            public bool RadioChatKeyHit { get; set; }
            public bool AnyHit => ActiveChatKeyHit || LocalChatKeyHit || RadioChatKeyHit;

            private ChatKeyStates(bool active, bool local, bool radio)
            {
                ActiveChatKeyHit = active;
                LocalChatKeyHit = local;
                RadioChatKeyHit = radio;
            }

            public static ChatKeyStates GetChatKeyStates()
            {
                return new ChatKeyStates(PlayerInput.KeyHit(InputType.ActiveChat),
                    PlayerInput.KeyHit(InputType.Chat),
                    PlayerInput.KeyHit(InputType.RadioChat) && (Character.Controlled == null || Character.Controlled.SpeechImpediment < 100));
            }

            public (bool active, bool local, bool radio) Deconstruct()
            {
                return (ActiveChatKeyHit, LocalChatKeyHit, RadioChatKeyHit);
            }
        }

        public void ApplySelectionInputs(GUITextBox inputBox, bool selectInputBox, ChatKeyStates chatKeyStates)
        {
            inputBox ??= InputBox;
            var (activeChatKeyHit, localChatKeyHit, radioChatKeyHit) = chatKeyStates.Deconstruct();
            if (localChatKeyHit || (activeChatKeyHit && GameMain.ActiveChatMode == ChatMode.Local))
            {
                ChatModeDropDown.SelectItem(ChatMode.Local);
                inputBox.AddToGUIUpdateList();
                GUIFrame.Flash(Color.DarkGreen, 0.5f);
                if (!ToggleOpen)
                {
                    CloseAfterMessageSent = !ToggleOpen;
                    ToggleOpen = true;
                }
                if (selectInputBox)
                {
                    inputBox.Select(inputBox.Text.Length);
                }
            }
            else if (radioChatKeyHit || (activeChatKeyHit && GameMain.ActiveChatMode == ChatMode.Radio))
            {
                ChatModeDropDown.SelectItem(ChatMode.Radio);
                inputBox.AddToGUIUpdateList();
                GUIFrame.Flash(Color.YellowGreen, 0.5f);
                if (!ToggleOpen)
                {
                    CloseAfterMessageSent = !ToggleOpen;
                    ToggleOpen = true;
                }
                if (selectInputBox)
                {
                    inputBox.Select(inputBox.Text.Length);
                }
            }
        }
    }
}
