using System.Collections.Generic;
using System;
using System.Text;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using NeonTea.Quakeball.Players;
using TMPro;

namespace NeonTea.Quakeball.Interface {
    public class Terminal : MonoBehaviour {

        public RectTransform TerminalPanel;
        public TMP_InputField InputField;
        public TMP_Text TextField;
        public LocalPlayer Player;
        [Tooltip("Used to optimize the amount of text rendered. 0 for no optimization.")]
        public float LinesVisibleAtOnce;
        public Scrollbar TerminalScrollbar;

        private InputAction ToggleTerminalAction;
        private InputAction SubmitTerminal;
        private float DesiredTerminalPos;
        private bool IsOpen = false;

        private Dictionary<string, Func<string[], bool>> Commands = new Dictionary<string, Func<string[], bool>>();
        private Dictionary<string, string> Helps = new Dictionary<string, string>();

        private List<string> Messages = new List<string>();
        private List<string> PreviousRuns = new List<string>();
        private int CurrentScroll = -1;
        private bool JustScrolled = false;

        public static string INFO_COLOR = "#FE8";
        public static string ERROR_COLOR = "#F33";

        public static Terminal Singleton => GameObject.FindGameObjectWithTag("Terminal").GetComponent<Terminal>();

        private void Start() {
            ToggleTerminalAction = new InputAction("Toggle Terminal", binding: "<Keyboard>/backquote");
            ToggleTerminalAction.Enable();
            ToggleTerminalAction.performed += ToggleTerminal;
            SubmitTerminal = new InputAction("Terminal Submit", binding: "<Keyboard>/enter");
            SubmitTerminal.Enable();
            SubmitTerminal.performed += Submit;
            SubmitTerminal = new InputAction("Terminal ScrollUp", binding: "<Keyboard>/uparrow");
            SubmitTerminal.Enable();
            SubmitTerminal.performed += _ => { Scroll(1); };
            SubmitTerminal = new InputAction("Terminal ScrollDown", binding: "<Keyboard>/downarrow");
            SubmitTerminal.Enable();
            SubmitTerminal.performed += _ => { Scroll(-1); };

            DesiredTerminalPos = (TerminalPanel.rect.height / 2) * (IsOpen ? -1 : 1);

            InputField.restoreOriginalTextOnEscape = false;
            InputField.onValueChanged.AddListener(_ => {
                if (JustScrolled) {
                    JustScrolled = false;
                    return;
                }
                CurrentScroll = -1;
            });

            RegisterCommand("help", args => {
                if (args.Length == 0) {
                    foreach (string command in Commands.Keys) {
                        string help = Helps.ContainsKey(command) ? Helps[command] : "No help info";
                        Println($"{command}: {help}");
                    }
                }
                foreach (string command in args) {
                    if (Helps.ContainsKey(command)) {
                        Println($"help {command}: {Helps[command]}");
                    } else {
                        Println($"<color={ERROR_COLOR}>Help for command {command} not found.</color>");
                    }
                }
                return true;
            }, "help [command [...]] - Displays help information for the given commands");

            Println($"<color={INFO_COLOR}>Welcome to Quakeball!</color>");
        }

        public void ToggleTerminal(InputAction.CallbackContext context) {
            IsOpen = !IsOpen;
            DesiredTerminalPos = (TerminalPanel.rect.height / 2) * (IsOpen ? -1 : 1);
            if (IsOpen) {
                InputField.text = "";
            }
            InputField.readOnly = true;
            if (Player != null) {
                Player.DisableInput = IsOpen;
            }
        }

        private void Update() {
            Vector3 pos = TerminalPanel.anchoredPosition;
            pos.y = Mathf.Lerp(pos.y, DesiredTerminalPos, Time.deltaTime * 10);
            TerminalPanel.anchoredPosition = pos;
            if (IsOpened() && EventSystem.current.currentSelectedGameObject != InputField) {
                InputField.Select();
                InputField.ActivateInputField();
                InputField.readOnly = false;
            }
            if (IsOpen) {
                if (LinesVisibleAtOnce == 0) {
                    TextField.text = String.Join("\n", Messages);
                } else {
                    float VisibleLinesCenterIndex = (1 - TerminalScrollbar.value) * Messages.Count;
                    int StartIndex = Mathf.Max(0, (int)(VisibleLinesCenterIndex - LinesVisibleAtOnce / 2));
                    int EndIndex = Mathf.Min(Messages.Count - 1, (int)(VisibleLinesCenterIndex + LinesVisibleAtOnce / 2));
                    StringBuilder OptimizedText = new StringBuilder();
                    for (int i = 0; i < Messages.Count; i++) {
                        if (i < StartIndex || i > EndIndex) {
                            OptimizedText.AppendLine();
                        } else {
                            OptimizedText.AppendLine(Messages[i]);
                        }
                    }
                    TextField.text = OptimizedText.ToString();
                }
            }
        }

        public bool RegisterCommand(string name, Func<string[], bool> command) {
            if (Commands.ContainsKey(name)) {
                return false;
            }
            Commands.Add(name, command);
            return true;
        }

        public bool RegisterCommand(string name, Func<string[], bool> command, string help) {
            if (RegisterCommand(name, command)) {
                Helps.Add(name, help);
                return true;
            }
            return false;
        }

        public void Run(string command) {
            string[] parts = command.Split(new char[] { ' ' });
            string name = parts[0];
            if (Commands.ContainsKey(name)) {
                Func<string[], bool> func = Commands[name];
                string[] args = new string[parts.Length - 1];
                Array.Copy(parts, 1, args, 0, parts.Length - 1);
                int message = Println($"> {command}");
                if (!func(args)) {
                    EditPrintln(message, $"<color={ERROR_COLOR}>> {command}</color>");
                }
            } else {
                Println($"<color={ERROR_COLOR}>> {command}</color>");
                Println("No such command exists!");
            }
            PreviousRuns.Insert(0, command);
        }

        public int Println(string message) {
            Messages.Add(message);
            return Messages.Count - 1;
        }

        public void EditPrintln(int idx, string message) {
            if (0 > idx || idx >= Messages.Count) {
                return;
            }
            Messages[idx] = message;
        }

        private void Scroll(int amount) {
            if (IsOpened() && PreviousRuns.Count > 0) {
                JustScrolled = true;
                CurrentScroll = Math.Min(Math.Max(0, CurrentScroll + amount), PreviousRuns.Count - 1);
                InputField.text = PreviousRuns[CurrentScroll];
            }
        }

        private void Submit(InputAction.CallbackContext context) {
            if (IsOpened()) {
                Run(InputField.text);
                InputField.text = "";
            }
        }

        private bool IsOpened() {
            return IsOpen && (TerminalPanel.anchoredPosition.y + TerminalPanel.rect.height / 2) < 10;
        }
    }
}