using System.Collections;
using System.Collections.Generic;
using Nox.Game;
using Nox.Game.UI;
using Nox.Input;
using Nox.Platform;
using Jovian.SaveSystem;
using Nox.EditorCode;
using Unity.Profiling;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;

namespace Nox.Core {
    /// <summary>
    /// The main boot class which is initializing the project, loads the platforms settings, initializes the platforms and creates the game states
    /// </summary>
    public class EntryPoint : MonoBehaviour {
        public AssetReferenceT<InitializerSettingsFile> initializerSettingsFile;

        private IPlatform platform;
        private GameDataState gameDataState;
        private Dictionary<GameState, IGameState> applicationStates;
        private ProfilerMarker createApplicationStateMarker = new ProfilerMarker("createApplicationState");
        private ISceneTransition sceneTransition;
        private ISaveSystem saveSystem;

        private InitializerSettingsFile initializerSettings;
        private BootstrapReferences bootstrapReferences;
        private MenuGameStateData menuGameStateData;

        public IEnumerator Start() {
            DontDestroyOnLoad(this);

            // scene authoring is helping with making each scene stand-alone playable
            var activeSceneReference = FindFirstObjectByType<SceneReference>();
            if(activeSceneReference == null) {
                Debug.LogWarning("The scene has no SceneReference script. Will start by default in Splash Application State");
                var sceneRef = new GameObject {
                    name = "Scene Reference"
                };
                activeSceneReference = sceneRef.AddComponent<SceneReference>();
                activeSceneReference.gameState = GameState.BootState;
                yield return null;
            }

            // Initialize platform
            gameDataState = new GameDataState {
                platformSelector = new PlatformSelector(PlatformSelector.GetDevicePlatform(), PlatformSelector.GetPlatformDefaultInputMode()),
            };
            gameDataState.ChangeGameState(activeSceneReference.gameState);

            // Load Initialization Settings
            var initSettingsHandle = Addressables.LoadAssetAsync<InitializerSettingsFile>(initializerSettingsFile);
            yield return new WaitUntil(() => initSettingsHandle.IsDone);
            initializerSettings = initSettingsHandle.Result;
            yield return CreatePlatformFactory();

            var bootStrapsSettingsHandle = Addressables.LoadAssetAsync<BootstrapReferences>(initializerSettings.bootstrapSettings);
            yield return new WaitUntil(() => bootStrapsSettingsHandle.IsDone);
            bootstrapReferences = bootStrapsSettingsHandle.Result;
            menuGameStateData = new MenuGameStateData();

            var fadeObject = new GameObject("ScreenFadeTransition");
            sceneTransition = fadeObject.AddComponent<ScreenFadeTransition>();

            CreateApplicationStates();

            var gameStateRunner = gameObject.AddComponent<GameStateRunner>();
            gameStateRunner.Initialize(applicationStates, gameDataState, platform, sceneTransition);
        }

        private IEnumerator CreatePlatformFactory() {
            platform = null;
            var devicePlatform = gameDataState.platformSelector.devicePlatform;
            platform = devicePlatform switch {
                DevicePlatform.Desktop => new DesktopPlatform(initializerSettings.desktopPlatformSettings),
                DevicePlatform.UnityEditor => new UnityEditorPlatform(initializerSettings.unityEditorPlatformSettings),
                _ => platform
            };

            if(!Equals(gameDataState.platformSelector.devicePlatform, DevicePlatform.Desktop) &&
                PlatformSelector.GetPlatformDefaultInputMode() == InputMode.Desktop) {
                platform = new DesktopPlatform(initializerSettings.desktopPlatformSettings);
            }
            yield return platform?.Initialize(gameDataState);
        }

        private void CreateApplicationStates() {
            createApplicationStateMarker.Begin();

            // Save System
            var saveSettings = SaveSystemSettings.Load();
            ISaveSerializer saveSerializer = saveSettings.saveFormat == SaveFormat.Json
                ? new JsonSaveSerializer()
                : new BinarySaveSerializer(saveSettings.obfuscationKey);
            ISaveStorage saveStorage = new FileSystemSaveStorage(Application.persistentDataPath, saveSettings.saveDirectoryName);
            ISaveSlotManager saveSlotManager = new SaveSlotManager(saveStorage, saveSettings);
            saveSystem = new SaveSystem(saveSerializer, saveStorage, saveSlotManager, saveSettings);

            var adventureData = new AdventureData();

            var characterBaseSettings = Addressables.LoadAssetAsync<StarterCharacterSettings>("CharacterBaseSettings").WaitForCompletion();
            var perKRegistry = Addressables.LoadAssetAsync<PerksRegistry>("PerksRegistry").WaitForCompletion();
            var characterRegistry = Addressables.LoadAssetAsync<CharacterRegistry>("CharacterRegistry").WaitForCompletion();
            var defaultPartySettings = Addressables.LoadAssetAsync<DefaultPartySettings>("DefaultPartySettings").WaitForCompletion();
            var modifiersRegistry = Addressables.LoadAssetAsync<ModifiersRegistry>("ModifiersRegistry").WaitForCompletion();

            var characterSystems = DefaultCharacterSystemsFactory.Create(characterBaseSettings, perKRegistry, characterRegistry, modifiersRegistry);
            var partyCreatorModel = new PartyCreatorModel(characterSystems.CharacterFactory, characterSystems.PartyFactory, defaultPartySettings);

            applicationStates = new Dictionary<GameState, IGameState> {
                [GameState.BootState] = new SplashGameState(bootstrapReferences, gameDataState),
                [GameState.MainMenu] = new MainMenuGameState(gameDataState, menuGameStateData, bootstrapReferences, saveSystem, partyCreatorModel, adventureData),
                [GameState.GameMode] = new GameModeGameState(gameDataState, bootstrapReferences, platform.PlatformSettings, saveSystem, sceneTransition, adventureData),
            };
            createApplicationStateMarker.End();
        }
    }
}
