using ABI_RC.Core.Player;
using ABI_RC.Core.Savior;
using ABI_RC.Core.UI;
using MelonLoader;
using System.Text;
using UnityEngine;

namespace NAK.Blackout;

/*

    Functionality heavily inspired by VRSleeper on Booth: https://booth.pm/ja/items/2151940

    There are three states of "blackout":

    0 - Awake (no effect)
    1 - Drowsy (partial effect)
    2 - Sleep (full effect)

    After staying still for DrowsyModeTimer (minutes), you enter DrowsyMode.
    This mode dims the screen to your selected dimming strength.
    After continuing to stay still for SleepModeTimer (seconds), you enter SleepMode.
    This mode overrenders mostly everything with black.

    Slight movement while in SleepMode will place you in DrowsyMode until SleepModeTimer is reached again.
    Hard movement once entering DrowsyMode will fully wake you and return complete vision.

*/

public class BlackoutController : MonoBehaviour
{
    public static BlackoutController Instance;

    // The current state of the player's consciousness.
    public BlackoutState CurrentState = BlackoutState.Awake;

    // Should the states automatically change based on time?
    public bool AutomaticStateChange = true;
    // Should the sleep state be automatically transitioned to? Some may prefer drowsy state only due to dimming.
    public bool AutoSleepState = true;

    // The minimum amount of movement required to partially restore vision.
    public float drowsyThreshold = 2f;
    // The minimum amount of movement required to fully restore vision.
    public float wakeThreshold = 4f;

    // The amount of time the player must remain still to enter drowsy state (in minutes).
    public float DrowsyModeTimer = 3f;
    // The amount of time the player must remain in drowsy state before entering sleep state (in seconds).
    public float SleepModeTimer = 10f;

    // The amount by which DrowsyMode affects the screen.
    public float DrowsyDimStrength = 0.6f;
    // Should DrowsyDimStrength be affected by velocity?
    public bool DrowsyVelocityMultiplier = true;

    // Whether to display HUD messages.
    public bool HudMessages = true;

    // Whether to lower the frame rate while in sleep mode.
    public bool DropFPSOnSleep = false;

    // The available states of consciousness.
    public enum BlackoutState
    {
        Awake = 0,
        Drowsy,
        Sleeping,
    }

    private Camera activeModeCam;
    private Vector3 headVelocity = Vector3.zero;
    private Vector3 lastHeadPos = Vector3.zero;
    private float curTime = 0f;
    private float lastAwakeTime = 0f;
    private Animator blackoutAnimator;
    private int targetFPS;

    public void ChangeBlackoutStateFromInt(int state) => ChangeBlackoutState((BlackoutState)state);

    // Changes the player's state of consciousness.
    public void ChangeBlackoutState(BlackoutState newState)
    {
        if (!blackoutAnimator) return;
        if (newState == CurrentState) return;

        lastAwakeTime = curTime;

        // Update the blackout animator based on the new state.
        switch (newState)
        {
            case BlackoutState.Awake:
                blackoutAnimator.SetBool("BlackoutState.Drowsy", false);
                blackoutAnimator.SetBool("BlackoutState.Sleeping", false);
                drowsyMagnitude = 0f;
                break;
            case BlackoutState.Drowsy:
                blackoutAnimator.SetBool("BlackoutState.Drowsy", true);
                blackoutAnimator.SetBool("BlackoutState.Sleeping", false);
                drowsyMagnitude = 0f;
                break;
            case BlackoutState.Sleeping:
                blackoutAnimator.SetBool("BlackoutState.Drowsy", false);
                blackoutAnimator.SetBool("BlackoutState.Sleeping", true);
                drowsyMagnitude = 1f;
                break;
            default:
                break;
        }

        // Update the current state and send a HUD message if enabled.
        BlackoutState prevState = CurrentState;
        CurrentState = newState;
        SendHUDMessage($"Exiting {prevState} and entering {newState} state.");
        ChangeTargetFPS();
    }

    public void AdjustDrowsyDimStrength(float multiplier = 1f)
    {
        blackoutAnimator.SetFloat("BlackoutSetting.DrowsyStrength", DrowsyDimStrength * multiplier);
    }

    // Initialize the BlackoutInstance object.
    private void Start()
    {
        Instance = this;

        // Get the blackout asset and instantiate it.
        GameObject blackoutAsset = AssetsHandler.GetAsset("Assets/BundledAssets/Blackout/Blackout.prefab");
        GameObject blackoutGO = Instantiate(blackoutAsset, new Vector3(0, 0, 0), Quaternion.identity);
        blackoutGO.name = "BlackoutInstance";

        // Get the blackout animator component.
        blackoutAnimator = blackoutGO.GetComponent<Animator>();
        if (!blackoutAnimator)
        {
            Blackout.Logger.Error("Blackout: Could not find blackout animator component!");
            return;
        }

        SetupBlackoutInstance();

        //we dont want this to ever disable
        Camera.onPreRender += OnPreRender;
        Camera.onPostRender += OnPostRender;
    }

    //Automatic State Change
    private void Update()
    {
        //get the current position of the player's head
        Vector3 curHeadPos = activeModeCam.transform.position;
        //calculate the player's head velocity by taking the difference in position
        headVelocity = (curHeadPos - lastHeadPos) / Time.deltaTime;
        //store the current head position for use in the next frame
        lastHeadPos = curHeadPos;

        if (AutomaticStateChange)
        {
            curTime = Time.time;
            //handle current state
            switch (CurrentState)
            {
                case BlackoutState.Awake:
                    HandleAwakeState();
                    break;
                case BlackoutState.Drowsy:
                    HandleDrowsyState();
                    break;
                case BlackoutState.Sleeping:
                    HandleSleepingState();
                    break;
                default:
                    break;
            }
        }
        else
        {
            CalculateDimmingMultiplier();
        }
    }

    public void OnEnable()
    {
        curTime = Time.time;
        lastAwakeTime = curTime;
    }

    public void OnDisable()
    {
        ChangeBlackoutState(BlackoutState.Awake);
    }

    private void OnPreRender(Camera cam)
    {
        if (cam == activeModeCam) return;
        blackoutAnimator.transform.localScale = Vector3.zero;
    }

    private void OnPostRender(Camera cam)
    {
        blackoutAnimator.transform.localScale = Vector3.one;
    }

    public void SetupBlackoutInstance()
    {
        activeModeCam = PlayerSetup.Instance.GetActiveCamera().GetComponent<Camera>();
        blackoutAnimator.transform.parent = activeModeCam.transform;
        blackoutAnimator.transform.localPosition = Vector3.zero;
        blackoutAnimator.transform.localRotation = Quaternion.identity;
        blackoutAnimator.transform.localScale = Vector3.one;
    }

    private float GetNextStateTimer()
    {
        switch (CurrentState)
        {
            case BlackoutState.Awake:
                return (lastAwakeTime + DrowsyModeTimer * 60 - curTime);
            case BlackoutState.Drowsy:
                return (lastAwakeTime + SleepModeTimer - curTime);
            case BlackoutState.Sleeping:
                return 0f;
            default:
                return 0f;
        }
    }

    //broken, needs to run next frame
    private void SendHUDMessage(string message)
    {
        Blackout.Logger.Msg(message);
        if (!CohtmlHud.Instance || !HudMessages) return;

        StringBuilder secondmessage = new StringBuilder();
        if (AutomaticStateChange)
        {
            if (CurrentState == BlackoutState.Drowsy && !AutoSleepState)
            {
                secondmessage = new StringBuilder("AutoSleepState is disabled. Staying in Drowsy State.");
            }
            else
            {
                secondmessage = new StringBuilder(GetNextStateTimer().ToString() + " seconds till next state change.");
            }
        }

        CohtmlHud.Instance.ViewDropTextImmediate("Blackout", message, secondmessage.ToString());
    }

    private void ChangeTargetFPS()
    {
        if (!DropFPSOnSleep) return;

        //store target FPS to restore, i check each time just in case it changed
        targetFPS = MetaPort.Instance.settings.GetSettingInt("GraphicsFramerateTarget", 0);

        Application.targetFrameRate = (CurrentState == BlackoutState.Sleeping) ? 5 : targetFPS;
    }

    private void HandleAwakeState()
    {
        //small movement should reset sleep timer
        if (headVelocity.magnitude > drowsyThreshold)
        {
            lastAwakeTime = curTime;
        }
        //enter drowsy mode after few minutes
        if (curTime > lastAwakeTime + DrowsyModeTimer * 60)
        {
            ChangeBlackoutState(BlackoutState.Drowsy);
        }
    }

    public float fadeSpeed = 0.8f; // The speed at which the value fades back to 0 or increases
    public float minimumThreshold = 0.5f; // The minimum value that the drowsy magnitude can have
    public float drowsyMagnitude = 0f;

    private void CalculateDimmingMultiplier()
    {
        if (!DrowsyVelocityMultiplier)
        {
            AdjustDrowsyDimStrength();
            return;
        }

        float normalizedMagnitude = headVelocity.magnitude / wakeThreshold;
        float targetMagnitude = 1f - normalizedMagnitude;
        targetMagnitude = Mathf.Max(targetMagnitude, minimumThreshold);
        drowsyMagnitude = Mathf.Lerp(drowsyMagnitude, targetMagnitude, fadeSpeed * Time.deltaTime);
        AdjustDrowsyDimStrength(drowsyMagnitude);
    }

    private void HandleDrowsyState()
    {
        //hard movement should exit drowsy state
        if (headVelocity.magnitude > wakeThreshold)
        {
            ChangeBlackoutState(BlackoutState.Awake);
            return;
        }
        //small movement should reset sleep timer
        if (headVelocity.magnitude > drowsyThreshold)
        {
            lastAwakeTime = curTime;
        }
        //enter full sleep mode
        if (AutoSleepState && curTime > lastAwakeTime + SleepModeTimer)
        {
            ChangeBlackoutState(BlackoutState.Sleeping);
        }
        CalculateDimmingMultiplier();
    }

    private void HandleSleepingState()
    {
        //small movement should enter drowsy state
        if (headVelocity.magnitude > drowsyThreshold)
        {
            ChangeBlackoutState(BlackoutState.Drowsy);
        }
    }
}