using System.Collections.Generic;
using EscapeRoomEngine.Engine.Runtime.Measurements;
using EscapeRoomEngine.Engine.Runtime.Modules;
using EscapeRoomEngine.Engine.Runtime.Utilities;
using NaughtyAttributes;
using UnityEngine;
using UnityEngine.UI;

namespace EscapeRoomEngine.Engine.Runtime.UI
{
    public enum GameState
    {
        Stopped, Paused, Running
    }
    
    /// <summary>
    /// The component that manages the gamemaster UI and the time.
    /// </summary>
    public class GameControl : MonoBehaviour
    {
        /// <summary>
        /// The active instance of the game control.
        /// </summary>
        public static GameControl Instance { get; private set; }

        [Tooltip("How much time in seconds should be between UI updates.")]
        [SerializeField]
        private float uiUpdateInterval = 1;
        [Tooltip("How much time in seconds should be between puzzle plan updates.")]
        [SerializeField]
        private float planUpdateInterval = 1;
        [BoxGroup("Internal")] [SerializeField]
        private Button startButton, stopButton, pauseButton, addTimeButton, removeTimeButton;
        [BoxGroup("Internal")] [SerializeField]
        private Text timeText, roomTimeText, estimateTimeText, targetTimeText, percentileText;
        [BoxGroup("Internal")] [SerializeField]
        private PuzzlePlan puzzlePlan;

        [HideInInspector] public GameState gameState = GameState.Stopped;
        public PuzzleModuleDescription CurrentPuzzle
        {
            set => puzzlePlan.CurrentPuzzle = value;
        }
        public List<PuzzleModuleDescription> PlannedPuzzles
        {
            set => puzzlePlan.Puzzles = value;
        }

        /// <summary>
        /// The time that elapsed since the game was started.
        /// </summary>
        public float TimeElapsed { get; private set; }
        /// <summary>
        /// The time the player has spent in this room.
        /// </summary>
        public float TimeInRoom { get; set; }
        /// <summary>
        /// The target time set by the game master.
        /// </summary>
        public float TargetTime { get; private set; }
        /// <summary>
        /// The estimated total time the player will have spent when they finish this room.
        /// </summary>
        public float EstimatedTimeRoom { get; private set; }
        /// <summary>
        /// The estimated total time the player will spend in the experience.
        /// </summary>
        public float EstimatedTime { get; private set; }

        private float _previousUIUpdate, _previousPlanUpdate;

        private void Awake()
        {
            Instance = this;
        }

        private void Start()
        {
            TargetTime = Engine.Instance.initialTargetTime;
            
            SetTimeText();
        }

        private void Update()
        {
            // update time
            if (gameState == GameState.Running)
            {
                TimeElapsed += Time.deltaTime;
                TimeInRoom += Time.deltaTime;
            }
            
            // update ui
            if (Time.time > _previousUIUpdate + uiUpdateInterval)
            {
                _previousUIUpdate = Time.time;
                
                SetTimeText();
                UpdateStats();
            }
            
            // update plan
            if (Time.time > _previousPlanUpdate + planUpdateInterval)
            {
                _previousPlanUpdate = Time.time;
                
                Engine.Instance.PlanPuzzles();
            }
            
            // enable or disable buttons
            startButton.interactable = gameState == GameState.Stopped;
            stopButton.interactable = gameState != GameState.Stopped;
            pauseButton.interactable = gameState is GameState.Running or GameState.Paused;
            addTimeButton.interactable = gameState != GameState.Stopped;
            removeTimeButton.interactable = gameState != GameState.Stopped && TargetTime >= TimeElapsed + 60;
        }

        #region Time Controls

        public void StartGame()
        {
            gameState = GameState.Running;

            TimeElapsed = 0;
            
            // generate the first room if it hasn't been generated yet
            Engine.Instance.CurrentRoom.Match(none: () => Engine.Instance.GenerateRoom());
            
            // start a new session
            Measure.StartSession();
        }

        public void StopGame()
        {
            if (gameState != GameState.Stopped)
            {
                // was running
                Measure.EndSession(TimeElapsed);
            }
            
            gameState = GameState.Stopped;
        }

        public void PauseGame()
        {
            // ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault
            switch (gameState)
            {
                case GameState.Running:
                    gameState = GameState.Paused;
                    pauseButton.GetComponent<PauseButton>().Paused = true;
                    break;
                case GameState.Paused:
                    gameState = GameState.Running;
                    pauseButton.GetComponent<PauseButton>().Paused = false;
                    break;
            }
        }

        /// <summary>
        /// Change the allowed time by a specified amount of seconds.
        /// </summary>
        /// <param name="seconds">The amount of seconds that will be added to the time. Can be negative to remove time.</param>
        public void ChangeTime(int seconds)
        {
            if (TargetTime + seconds >= TimeElapsed)
            {
                TargetTime += seconds;
            }
        }

        private void SetTimeText()
        {
            timeText.text = TimeToText(TimeElapsed);
            roomTimeText.text = TimeToText(TimeInRoom);
            targetTimeText.text = TimeToText(TargetTime);

            Engine.Instance.CurrentRoom.Match(some: room =>
            {
                EstimatedTimeRoom =
                    TimeElapsed - TimeInRoom + Mathf.Max(TimeInRoom, Measure.EstimateTime(room));
                EstimatedTime = EstimatedTimeRoom + Engine.Instance.EstimatedTimeRemaining;
                
                estimateTimeText.text = TimeToText(EstimatedTime);
            });
        }

        private void UpdateStats()
        {
            percentileText.text = PercentageToText(Measure.SessionPercentile());
        }

        private static string TimeToText(float time) => $"{time.ToTimeSpan():mm':'ss}";

        private static string PercentageToText(float percentage) => $"{percentage:P1}";

        #endregion

        #region Measurements

        public void StoreMeasurements(Toggle toggle)
        {
            Measure.Store = toggle.isOn;
        }

        #endregion

        public void ExitGame()
        {
            StopGame();
            
#if UNITY_STANDALONE
            Application.Quit();
#endif
#if UNITY_EDITOR
            UnityEditor.EditorApplication.isPlaying = false;
#endif
        }
    }
}
