using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using VOID.Generic.Navigation;
using VOID.Generic.Objects.Unit;
using VOID.Generic.Player;
using VOID.Generic.SaveLoad;
using VOID.UserInterface.LoadingScreen;
using VOID.Generic.Util;
using VOID.Generic.World;

namespace VOID.Generic
{
    public class GameLoopManager : MonoBehaviour
    {
        public static GameLoopManager current { get; private set; }
        
        [SerializeField] private SceneReference _mainMenuScene;
        [SerializeField] private SceneReference _LoadingScreenScene;
        [SerializeField] private SceneReference _lobbyScene;
        [SerializeField] private SceneReference _worldGeneratorScene;

        private void Awake()
        {
            if (current)
            {
                Debug.LogWarning("Instance of " + nameof(GameLoopManager) + " already exists!");
                Destroy(this);
                return;
            }
            
            // Only one instance for whole game, this should manage scene changing nad using all other managers
            // DontDestroyOnLoad works only on root game object so we need to unparent it
            current = this;
            current.transform.parent = null;
            DontDestroyOnLoad(this);
        }

        private void Start()
        {
            // Only for dev/debug purpose - start instantly current scene
            if (SceneManager.GetActiveScene().path != _mainMenuScene)
            {
                StartCoroutine(InitializeDebugScene());
                return;
            }
            
            StartCoroutine(InitializeMainMenu());
        }

        private IEnumerator InitializeDebugScene()
        {
            // wait for other things
            yield return new WaitForFixedUpdate();
            
            // So far only thing needed for dev/debug scene to work is initialize player's units
            if (PlayerController.current.mainUnit)
            {
                PlayerController.current.SetMainUnit(PlayerController.current.mainUnit);
                PlayerController.current.SetMainUnitActive();
                CameraManager.current.stickTo = PlayerController.current.mainUnit.transform;
            }

            if (PlayerController.current.subUnit)
            {
                PlayerController.current.SetSubUnit(PlayerController.current.subUnit);
            }

            // Create (if missing) profile and load it
            PlayerProfileManager.current.SelectProfile(PlayerProfileManager.current.GetDefaultProfile());
        }

        private IEnumerator InitializeMainMenu()
        {
            // wait for other things
            yield return new WaitForFixedUpdate();

            // Load last profile if exists
            var defaultProfile = PlayerProfileManager.current.GetDefaultProfile();
            if (defaultProfile != null) PlayerProfileManager.current.SelectProfile(defaultProfile);
            
            LoadMainMenuAfter().ForEach(action => action.Invoke());
        }

        #region BeforeLoading

        public void LoadMainMenu()
        {
            StartCoroutine(LoadScene(_mainMenuScene, LoadMainMenuAfter()));
        }

        public void LoadLobby()
        {
            StartCoroutine(LoadScene(_lobbyScene, LoadLobbyAfter()));
        }

        public void LoadGenerator(UnitObject playerUnit)
        {
            // Dont destroy selected avatar by player on scene change, to do that we need to remove its PARENT!
            playerUnit.gameObject.transform.parent = null;
            DontDestroyOnLoad(playerUnit.gameObject);
            
            StartCoroutine(LoadScene(_worldGeneratorScene, LoadGeneratorAfter(playerUnit)));
        }

        #endregion

        #region LoadingScreen

        private IEnumerator LoadScene(SceneReference sceneReference, List<Action> onLoadAfter)
        {
            // Load LoadingScene
            // Reset loading progress
            // Previous scene will be automatically unloaded
            var loadAsync = SceneManager.LoadSceneAsync(_LoadingScreenScene, LoadSceneMode.Single);
            yield return new WaitUntil(() => loadAsync.isDone);
            LoadingScreenUI.current.SetProgress(0f);
            yield return new WaitForEndOfFrame();

            // Load wanted scene
            // Important - make that scene active before any Awake() execute!
            var sceneAsync = SceneManager.LoadSceneAsync(sceneReference, LoadSceneMode.Additive);
            sceneAsync.completed += _ => SceneManager.SetActiveScene(SceneManager.GetSceneByPath(sceneReference));
            yield return new WaitUntil(() => sceneAsync.isDone);
            LoadingScreenUI.current.SetProgress(0.2f);
            yield return new WaitForEndOfFrame();
        
            // Additional steps to do before fully changing to next scene
            if (onLoadAfter.Count > 0)
            {
                var progressPerAction = 0.8f / onLoadAfter.Count;
                for (var i = 0; i < onLoadAfter.Count; i++)
                {
                    onLoadAfter[i].Invoke();
                    LoadingScreenUI.current.SetProgress(0.2f + (i+1) * progressPerAction);
                    yield return new WaitForEndOfFrame();
                }
            }
            
            // Set progress to 100% + TODO: wait 1 second so we won't be too "fast" 
            LoadingScreenUI.current.SetProgress(1f);
            yield return new WaitForSecondsRealtime(1f);
            
            // Change to next scene + remove loading screen
            SceneManager.UnloadSceneAsync(_LoadingScreenScene);
        }

        #endregion

        #region AfterLoading
        
        private List<Action> LoadGeneratorAfter(UnitObject playerUnit)
        {
            return new List<Action> { GenerateWorld, GenerateNavMesh, InitializePlayer };
            
            void GenerateWorld()
            {
                // Generate world
                var seed = RandomUtil.RandomInteger(0, int.MaxValue);
                WorldGeneratorManager.current.Generate(150, seed, true);
                Debug.Log("World Generated with seed: " + seed);
            }

            void GenerateNavMesh()
            {
                // Build nav mesh for whole map
                NavigationManager.current.BuildNavMesh();
            }

            void InitializePlayer()
            {
                // initialize managers with selected player avatar
                playerUnit.transform.position = new Vector3(0, 0, 0);
                PlayerController.current.SetMainUnit(playerUnit);
                PlayerController.current.SetMainUnitActive();
                CameraManager.current.stickTo = playerUnit.transform;
                
                // Make avatar destroyable again on scene change
                SceneManager.MoveGameObjectToScene(playerUnit.gameObject, SceneManager.GetActiveScene());
            }
        }

        private List<Action> LoadMainMenuAfter()
        {
            return new List<Action>() { };
        }
        
        private List<Action> LoadLobbyAfter()
        {
            return new List<Action>() { };
        }

        #endregion
    }
}