using System;
using System.Linq;
using System.Reflection;
using UnityEngine;
using UnityEngine.SceneManagement;

namespace ManifoldGardenArchipelago
{
    public class GameState
    {
        public static LevelSystems GetLevelSystems(Component component)
        {
            return GetLevelSystems(component.gameObject.scene);
        }

        public static LevelSystems GetLevelSystems(Scene scene)
        {
            foreach (GameObject gameObject in scene.GetRootGameObjects())
            {
                if (gameObject.name == "Level Systems")
                {
                    return gameObject.GetComponent<LevelSystems>();
                }
            }

            Plugin.Logger.LogWarning($"Could not find Level Systems for {scene.name}");
            return null;
        }

        public static SceneItemReference GetChainListenerSceneReference(Component component)
        {
            LevelSystems levelSystem = GetLevelSystems(component);

            for (int i = 0; i < levelSystem.chainListeners.Count(); i++)
            {
                if (levelSystem.chainListeners[i] == component)
                {
                    return new(component.gameObject.scene.name, i);
                }
            }

            Plugin.Logger.LogWarning($"Could not find SIR for chain listener {component.name}");
            throw new Exception("Shouldn't happen");
        }

        public static SceneItemReference GetGameplayComponentSceneReference(Component component)
        {
            LevelSystems levelSystem = GetLevelSystems(component);

            for (int i = 0; i < levelSystem.gameplayComponentsInLevel.Count(); i++)
            {
                if (levelSystem.gameplayComponentsInLevel[i] == component)
                {
                    return new(component.gameObject.scene.name, i);
                }
            }

            Plugin.Logger.LogWarning($"Could not find SIR for gameplay component {component.name}");
            throw new Exception("Shouldn't happen");
        }

        public static SceneItemReference GetButtonSceneReference(ButtonLineActivator arg)
        {
            LevelSystems levelSystem = GetLevelSystems(arg);

            for (int i = 0; i < levelSystem.buttonLineActivators.Count(); i++)
            {
                if (levelSystem.buttonLineActivators[i] == arg)
                {
                    return new(arg.gameObject.scene.name, i);
                }
            }

            Plugin.Logger.LogWarning($"Could not find SIR for button {arg.name}");
            throw new Exception("Shouldn't happen");
        }

        public static SceneItemReference GetPadSceneReference(CubeLineActivator arg)
        {
            LevelSystems levelSystem = GetLevelSystems(arg);

            for (int i = 0; i < levelSystem.cubeLineActivators.Count(); i++)
            {
                if (levelSystem.cubeLineActivators[i] == arg)
                {
                    return new(arg.gameObject.scene.name, i);
                }
            }

            Plugin.Logger.LogWarning($"Could not find SIR for pad {arg.name}");
            throw new Exception("Shouldn't happen");
        }

        public static SceneItemReference GetSocketSceneReference(CubeReceiverController arg)
        {
            LevelSystems levelSystem = GetLevelSystems(arg);

            for (int i = 0; i < levelSystem.cubeReceiverControllers.Count(); i++)
            {
                if (levelSystem.cubeReceiverControllers[i] == arg)
                {
                    return new(arg.gameObject.scene.name, i);
                }
            }

            Plugin.Logger.LogWarning($"Could not find SIR for socket {arg.name}");
            throw new Exception("Shouldn't happen");
        }

        public static SceneItemReference GetSphereSceneReference(SphereController arg)
        {
            LevelSystems levelSystem = GetLevelSystems(arg);

            for (int i = 0; i < levelSystem.sphereControllers.Count(); i++)
            {
                if (levelSystem.sphereControllers[i] == arg)
                {
                    return new(arg.gameObject.scene.name, i);
                }
            }

            Plugin.Logger.LogWarning($"Could not find SIR for sphere {arg.name}");
            throw new Exception("Shouldn't happen");
        }

        public static SceneItemReference GetWaterwheelSceneReference(WaterDetector arg)
        {
            LevelSystems levelSystem = GetLevelSystems(arg);

            for (int i = 0; i < levelSystem.waterDetectors.Count(); i++)
            {
                if (levelSystem.waterDetectors[i] == arg)
                {
                    return new(arg.gameObject.scene.name, i);
                }
            }

            Plugin.Logger.LogWarning($"Could not find SIR for waterwheel {arg.name}");
            throw new Exception("Shouldn't happen");
        }

        public static void EvaluateGameStateListeners(GameStateListeners listeners)
        {
            foreach (var location in listeners.locations)
            {
                if (location.Value.Check() == Requirement.Decision.Yes)
                {
                    Plugin.archipelagoManager.CheckLocation(location.Key);
                }
            }

            foreach (var door in listeners.doors)
            {
                Requirement.Decision decision = door.Value.Check();
                if (decision == Requirement.Decision.Maybe)
                {
                    continue;
                }

                bool shouldOpen = (decision == Requirement.Decision.Yes);
                //Plugin.Logger.LogInfo($"{door.Key}: {door.Value} -> {shouldOpen}");

                if (SceneManager.GetSceneByName(door.Key.scene) is Scene doorScene && doorScene.IsValid() && doorScene.isLoaded)
                {
                    LevelSystems levelSystems = GetLevelSystems(doorScene);
                    if (levelSystems.isActiveAndEnabled)
                    {
                        LineDoorController ldc = levelSystems.chainListeners[door.Key.index].GetComponent<LineDoorController>();
                        ldc.chains.Clear();

                        FieldInfo fieldInfo = typeof(LineDoorController).GetField("doorShouldOpenImmediatelyOnEnable", BindingFlags.Instance | BindingFlags.NonPublic);
                        fieldInfo.SetValue(ldc, shouldOpen);

                        MethodInfo methodInfo = typeof(LineDoorController).GetMethod("SetDoorOpenCloseStateAnimated", BindingFlags.NonPublic | BindingFlags.Instance);
                        methodInfo.Invoke(ldc, [shouldOpen, false]);

                        ldc.saveData.isOpen = shouldOpen;
                    }
                }
            }

            foreach (var smokeWall in listeners.smokeWalls)
            {
                Requirement.Decision decision = smokeWall.Value.Check();
                if (decision == Requirement.Decision.Maybe)
                {
                    continue;
                }

                bool shouldOpen = (decision == Requirement.Decision.Yes);
                //Plugin.Logger.LogInfo($"{smokeWall.Key}: {smokeWall.Value} -> {shouldOpen}");

                if (SceneManager.GetSceneByName(smokeWall.Key.scene) is Scene smokeScene && smokeScene.IsValid() && smokeScene.isLoaded)
                {
                    LevelSystems levelSystems = GetLevelSystems(smokeScene);
                    if (levelSystems.isActiveAndEnabled)
                    {
                        SolidStateController ldc = levelSystems.chainListeners[smokeWall.Key.index].GetComponent<SolidStateController>();
                        ldc.chains.Clear();

                        FieldInfo fieldInfo = typeof(SolidStateController).GetField("m_isSolid", BindingFlags.Instance | BindingFlags.NonPublic);
                        fieldInfo.SetValue(ldc, !shouldOpen);
                    }
                }
            }

            foreach (var worldGrow in listeners.worldGrows)
            {
                if (worldGrow.Value.Check() == Requirement.Decision.Yes)
                {
                    if (SceneManager.GetSceneByName(worldGrow.Key.scene) is Scene gardenScene && gardenScene.IsValid() && gardenScene.isLoaded)
                    {
                        LevelSystems levelSystems = GetLevelSystems(gardenScene);
                        if (levelSystems.isActiveAndEnabled)
                        {
                            DarkModeCollapsedCubeWorldGrow ldc = levelSystems.chainListeners[worldGrow.Key.index].GetComponent<DarkModeCollapsedCubeWorldGrow>();
                            ldc.chains.Clear();

                            FieldInfo fieldInfo = typeof(DarkModeCollapsedCubeWorldGrow).GetField("m_grown", BindingFlags.Instance | BindingFlags.NonPublic);
                            fieldInfo.SetValue(ldc, true);
                        }
                    }
                }
            }

            foreach (var laser in listeners.lasers)
            {
                Requirement.Decision decision = laser.Value.Check();
                if (decision == Requirement.Decision.Maybe)
                {
                    continue;
                }

                bool shouldOpen = (decision == Requirement.Decision.Yes);

                if (SceneManager.GetSceneByName(laser.Key.scene) is Scene laserScene && laserScene.IsValid() && laserScene.isLoaded)
                {
                    LevelSystems levelSystems = GetLevelSystems(laserScene);
                    if (levelSystems.isActiveAndEnabled)
                    {
                        DarkModeCageController ldc = levelSystems.gameplayComponentsInLevel[laser.Key.index].GetComponent<DarkModeCageController>();
                        
                        if (shouldOpen)
                        {
                            MethodInfo methodInfo = typeof(DarkModeCageController).GetMethod("OpenLaserSource", BindingFlags.Instance | BindingFlags.NonPublic);
                            methodInfo.Invoke(ldc, [false]);
                        }
                        else
                        {
                            MethodInfo methodInfo = typeof(DarkModeCageController).GetMethod("CloseLaserSource", BindingFlags.Instance | BindingFlags.NonPublic);
                            methodInfo.Invoke(ldc, [false]);
                        }
                    }
                }
            }
        }
    }
}