/*
 * author: mark joshwel
 * date: 29/5/2024
 * description: game manager singleton for single source of truth state management
 */

using System;
using UnityEngine;
using UnityEngine.SceneManagement;

/// <summary>
/// singleton class for managing the game state as a single source of truth
/// </summary>
public class GameManager : MonoBehaviour
{
    /// <summary>
    /// singleton pattern: define instance field for accessing the singleton elsewhere
    /// </summary>
    public static GameManager Instance;

    /// <summary>
    /// the current state of the game
    /// </summary>
    private DisplayState _state = DisplayState.UnassociatedState;

    /// <summary>
    /// property to check if the game is paused based on the current <c>DisplayState</c>
    /// </summary>
    public bool Paused => _state != DisplayState.Game;

    /// <summary>
    /// function to set don't destroy on load and check for multiple instances
    /// </summary>
    private void Awake()
    {
        // check if instance hasn't been set yet
        if (Instance == null)
        {
            // set this instance as the singleton instance
            Instance = this;
            // don't destroy this instance on scene load
            DontDestroyOnLoad(gameObject);

            Debug.Log("GameManager: Awake as singleton instance");
        }
        // check if instance is already set and it's not this instance
        else if (Instance != null && Instance != this)
        {
            Debug.Log("GameManager: Awake as non-singleton instance, destroying self");

            // destroy the new instance if it's not the singleton instance
            Destroy(gameObject);
        }
    }

    /// <summary>
    /// called when game starts, sets state to main menu
    /// </summary>
    private void Start()
    {
        SetDisplayState(DisplayState.ScreenMainMenu);
    }

    /// <summary>
    /// enum for available menus in the game, for use with <c>ShowMenu()</c>
    /// </summary>
    public enum DisplayState
    {
        Game,
        ScreenMainMenu,
        ScreenOptionsMenu,
        ScreenCreditsMenu,
        ScreenPauseMenu,
        ScreenCaughtPause,
        ScreenEscapedMenu,
        UnassociatedState
    }

    /// <summary>
    /// helper function to hide any menu that is currently showing
    /// </summary>
    private void HideMenuHelper()
    {
        // get parent object tagged "Menus"
        foreach (var menu in GameObject.FindGameObjectsWithTag("Menus"))
        {
            // hide each menu under the parent object\
            foreach (Transform menuChild in menu.transform)
            {
                Debug.Log($"GameManager: HideMenuHelper - hiding menu '{menuChild}'");
                menuChild.gameObject.SetActive(false);
            }
        }
    }

    /// <summary>
    /// helper function for <c>SetDisplayState()</c> to pause the game,
    /// called before the incoming game state is set
    /// </summary>
    /// <param name="incomingState">the to-be-set state of the game</param>
    private void PauseGameHelper(DisplayState incomingState)
    {
        // if we're transitioning from a state of gameplay to a state of non-gameplay,
        // then we should pause the game
        if (_state == DisplayState.Game && incomingState != DisplayState.Game)
        {
            Debug.Log("GameManager: PauseGameHelper - actually pausing game");
            Time.timeScale = 0f;
            Cursor.lockState = CursorLockMode.None;
            Cursor.visible = true;
        }

        // if the current and incoming states are the same, then we shouldn't do anything,
        // so we return early
        if (_state == incomingState)
        {
            Debug.Log("GameManager: PauseGameHelper - states are the same, returning early");
            return;
        }

        ;

        // hide any menu that is currently showing
        HideMenuHelper();

        // show the menu based on the incoming state
        // 1. get all menus via the parent object tagged "Menus"
        foreach (var menuParent in GameObject.FindGameObjectsWithTag("Menus"))
        {
            // 2. get all menus under the parent object
            foreach (Transform menu in menuParent.transform)
            {
                // 2. check its associated state
                var associatedState = menu.gameObject.GetComponent<CommonMenu>().associatedState;
                Debug.Log(
                    $"GameManager: PauseGameHelper - found menu '{menu}' with associated state {associatedState} against incoming state {incomingState}");

                // 3. if the associated state is the same as the incoming state, then show the menu
                if (associatedState != incomingState)
                    continue;

                Debug.Log($"GameManager: PauseGameHelper - showing menu for {incomingState}");
                menu.gameObject.SetActive(true);
            }
        }
    }

    /// <summary>
    /// helper function for <c>SetDisplayState()</c> to resume the game,
    /// called before the incoming game state is set
    /// </summary>
    /// <param name="incomingState">the to-be-set state of the game</param>
    private void ResumeGameHelper(DisplayState incomingState)
    {
        // if we're NOT transitioning from a state of non-gameplay to a state of gameplay,
        // then we shouldn't do anything, because the game is already running,
        // so we return early
        if (_state != DisplayState.Game && incomingState == DisplayState.Game) return;

        // else, we should resume the game
        Time.timeScale = 1f;
        Cursor.lockState = CursorLockMode.Locked;
        Cursor.visible = false;

        // hide any menu that is currently showing
        HideMenuHelper();
    }

    /// <summary>
    /// function to show a menu based on the enum passed,
    /// and any other necessary actions
    /// </summary>
    /// <param name="displayState">the game menu to show</param>
    public void SetDisplayState(DisplayState displayState)
    {
        // check if the game is paused or not
        if (displayState == DisplayState.Game)
        {
            Debug.Log($"GameManager: SetDisplayState({displayState}) - resuming game");
            ResumeGameHelper(displayState);
        }
        else
        {
            Debug.Log($"GameManager: SetDisplayState({displayState}) - pausing game");
            PauseGameHelper(displayState);
        }

        // set the state of the game to the incoming state
        _state = displayState;

        // TODO: post-state change actions case switch
        switch (displayState)
        {
            // if we're transitioning to the game state,
            // change camera to the player camera
            case DisplayState.Game:
                // TODO
                return;

            // if we're transitioning to the main menu state,
            // change camera to the main menu camera
            case DisplayState.ScreenMainMenu:
                // TODO
                return;
        }
    }

    /// <summary>
    /// wrapper function to quit the game
    /// in case of any cleanup needed
    /// </summary>
    public void Quit()
    {
        // quit the application
        Application.Quit();
    }

    /// <summary>
    /// resets game state and starts a new game, will call <c>SetDisplayState()</c>
    /// </summary>
    public void NewGame()
    {
        // TODO
    }
}