using CameraShake;
using GearsAPI.Settings.Global;
using UnityEngine;

namespace KFCommonUtilityLib.Scripts.StaticManagers
{
    public static class RecoilManager
    {
        private enum RecoilState
        {
            None,
            Recoil,
            Return
        }

        private static RecoilState state;
        private static float lastKickTime = 0f;
        //private static float recoilScaledDelta = 0f;
        //private static float returnScaledDelta = 0f;
        private static Vector2 targetRotationXY = Vector2.zero;
        private static Vector2 targetReturnXY = Vector2.zero;
        private static Vector3 returnSpeedCur = Vector3.zero;
        private static Vector2 totalRotationXY = Vector2.zero;
        private static Vector3 totalReturnCur = Vector3.zero;
        private static EntityPlayerLocal player;
        //Gears options
        private static bool enableCap = false;
        private static bool enableDynamicCap = false;
        private static bool enableSoftCap = false;
        private static bool enablePreRecoilCompensation = false;
        private static float maxRecoilAngle = 15;
        private static int maxDynamicRecoilCapShots = 6;
        private static float recoilCapRemain = 1f;
        private static float recoilCompensationSensitivityMultiplier = 0f;
        private const float DEFAULT_SNAPPINESS_PISTOL = 6f;
        private const float DEFAULT_SNAPPINESS_RIFLE = 3.6f;
        private const float DEFAULT_SNAPPINESS_SHOTGUN = 6f;
        private const float DEFAULT_RETURN_SPEED_PISTOL = 8f;
        private const float DEFAULT_RETURN_SPEED_RIFLE = 4f;
        private const float DEFAULT_RETURN_SPEED_SHOTGUN = 4f;
        private static readonly FastTags<TagGroup.Global> PistolTag = FastTags<TagGroup.Global>.Parse("pistol");
        private static readonly FastTags<TagGroup.Global> ShotgunTag = FastTags<TagGroup.Global>.Parse("shotgun");

        private static void ClearData()
        {
            state = RecoilState.None;
            //recoilScaledDelta = 0;
            returnSpeedCur = Vector3.zero;
            targetRotationXY = Vector2.zero;
            targetReturnXY = Vector2.zero;
            totalRotationXY = Vector2.zero;
            totalReturnCur = Vector3.zero;
            lastKickTime = 0f;
        }

        public static void InitRecoilSettings(IModGlobalSettings settings)
        {
            var capSetting = settings.GetTab("RecoilSettings").GetCategory("Capping");

            var recoilCompensationSetting = capSetting.GetSetting("RecoilCompensationSensitivityMultiplier") as ISliderGlobalSetting;
            recoilCompensationSensitivityMultiplier = float.Parse(recoilCompensationSetting.CurrentValue);
            recoilCompensationSetting.OnSettingChanged += (setting, newValue) => recoilCompensationSensitivityMultiplier = float.Parse(newValue);

            var preRecoilCompensationSetting = capSetting.GetSetting("EnablePreRecoilCompensation") as ISwitchGlobalSetting;
            enablePreRecoilCompensation = preRecoilCompensationSetting.CurrentValue == "Enable";
            preRecoilCompensationSetting.OnSettingChanged += (setting, newValue) => enablePreRecoilCompensation = newValue == "Enable";

            var enableCapSetting = capSetting.GetSetting("EnableCap") as ISwitchGlobalSetting;
            enableCap = enableCapSetting.CurrentValue == "Enable";
            enableCapSetting.OnSettingChanged += (setting, newValue) =>
            {
                enableCap = newValue == "Enable";
                UpdateSettingState(setting.Category);
            };

            var recoilRemainSetting = capSetting.GetSetting("RecoilRemain") as ISliderGlobalSetting;
            recoilCapRemain = float.Parse(recoilRemainSetting.CurrentValue);
            recoilRemainSetting.OnSettingChanged += (setting, newValue) => recoilCapRemain = float.Parse(newValue);

            var enableSoftCapSetting = capSetting.GetSetting("EnableSoftCap") as ISwitchGlobalSetting;
            enableSoftCap = enableSoftCapSetting.CurrentValue == "Enable";
            enableSoftCapSetting.OnSettingChanged += (setting, newValue) => enableSoftCap = newValue == "Enable";

            var maxRecoilAngleSetting = capSetting.GetSetting("MaxRecoilAngle") as ISliderGlobalSetting;
            maxRecoilAngle = float.Parse(maxRecoilAngleSetting.CurrentValue);
            maxRecoilAngleSetting.OnSettingChanged += (setting, newValue) => maxRecoilAngle = float.Parse(newValue);

            var enableDynamicCapSetting = capSetting.GetSetting("EnableDynamicCap") as ISwitchGlobalSetting;
            enableDynamicCap = enableDynamicCapSetting.CurrentValue == "Enable";
            enableDynamicCapSetting.OnSettingChanged += (setting, newValue) =>
            {
                enableDynamicCap = newValue == "Enable";
                UpdateSettingState(setting.Category);
            };

            var maxDynamicRecoilCapShotsSetting = capSetting.GetSetting("MaxDynamicRecoilCapShots") as ISliderGlobalSetting;
            maxDynamicRecoilCapShots = int.Parse(maxDynamicRecoilCapShotsSetting.CurrentValue);
            maxDynamicRecoilCapShotsSetting.OnSettingChanged += (setting, newValue) => maxDynamicRecoilCapShots = int.Parse(newValue);
            UpdateSettingState(capSetting);
        }

        private static void UpdateSettingState(IGlobalModSettingsCategory category)
        {
            category.GetSetting("EnableCap").Enabled = true;
            category.GetSetting("RecoilRemain").Enabled = enableCap;
            category.GetSetting("EnableSoftCap").Enabled = enableCap;
            category.GetSetting("MaxRecoilAngle").Enabled = enableCap && !enableDynamicCap;
            category.GetSetting("EnableDynamicCap").Enabled = enableCap;
            category.GetSetting("MaxDynamicRecoilCapShots").Enabled = enableCap && enableDynamicCap;
        }

        public static void InitPlayer(EntityPlayerLocal _player)
        {
            ClearData();
            player = _player;
            player.cameraTransform.AddMissingComponent<CameraShaker>();
        }

        public static void Cleanup()
        {
            ClearData();
            player = null;
        }

        private static float shakeFreq = 20;
        private static int shakeBounce = 5;
        public static void AddRecoil(Vector2 recoilRangeHor, Vector2 recoilRangeVer)
        {
            if (player == null) { return; }
            state = RecoilState.Recoil;
            //recoilScaledDelta = 0;
            returnSpeedCur = Vector3.zero;
            //returnScaledDelta = 0;
            float cap = 0f;
            if (enableCap)
            {
                if (enableDynamicCap)
                {
                    cap = Mathf.Abs(recoilRangeVer.y) * maxDynamicRecoilCapShots;
                }
                else
                {
                    cap = maxRecoilAngle;
                }
            }
            float cameraShakeStrength = EffectManager.GetValue(CustomEnums.RecoilCameraShakeStrength, player.inventory.holdingItemItemValue, 0.12f, player);
            float targetRotationX = player.rand.RandomRange(recoilRangeVer.x, recoilRangeVer.y);
            float targetRotationY = player.rand.RandomRange(recoilRangeHor.x, recoilRangeHor.y);
            if (enableCap)
            {
                if (Mathf.Abs(totalRotationXY.x) >= Mathf.Abs(cap))
                {
                    targetRotationX *= recoilCapRemain;
                }
                else if (enableSoftCap)
                {
                    targetRotationX *= Mathf.Lerp(recoilCapRemain, 1f, 1 - Mathf.InverseLerp(0, Mathf.Abs(cap), Mathf.Abs(totalRotationXY.x)));
                    //targetRotationX *= Mathf.Lerp(recoilCapRemain, 1f, Mathf.Cos(Mathf.PI * .5f * Mathf.InverseLerp(0, Mathf.Abs(cap), Mathf.Abs(totalRotationXY.x))));
                }
            }
            if (!player.AimingGun)
            {
                targetRotationXY += new Vector2(targetRotationX, targetRotationY) * 2f;
                totalRotationXY += new Vector2(targetRotationX, targetRotationY) * 2f;
                CameraShaker.Presets.ShortShake3D(cameraShakeStrength * 1.2f, shakeFreq, shakeBounce);
            }
            else
            {
                targetRotationXY += new Vector2(targetRotationX, targetRotationY);
                totalRotationXY += new Vector2(targetRotationX, targetRotationY);
                CameraShaker.Presets.ShortShake3D(cameraShakeStrength, shakeFreq, shakeBounce);
            }
            lastKickTime = Time.time;
            //if (enableCap)
            //{
            //    float totalRotationXCapped = Mathf.Clamp(totalRotationXY.x, -cap, cap);
            //    targetRotationXY.x = Mathf.Clamp(targetRotationXY.x + totalRotationXCapped - totalRotationXY.x, -cap, cap);
            //    totalRotationXY.x = totalRotationXCapped;
            //}
        }

        public static float CompensateX(float movedX)
        {
            if (!enablePreRecoilCompensation)
            {
                if (targetReturnXY.x * movedX < 0)
                    targetReturnXY.x = Mathf.Max(0, targetReturnXY.x + movedX);
                return movedX;
            }
            float targetX = targetRotationXY.x;
            float returnX = targetReturnXY.x;
            float res = Compensate(movedX, player.movementInput.rotation.x, ref targetX, ref returnX);
            targetRotationXY.x = targetX;
            targetReturnXY.x = returnX;
            return res;
        }

        public static float CompensateY(float movedY)
        {
            if (!enablePreRecoilCompensation)
            {
                if (targetReturnXY.y * movedY < 0)
                    targetReturnXY.y = Mathf.Max(0, targetReturnXY.y + movedY);
                return movedY;
            }
            float targetY = targetRotationXY.y;
            float returnY = targetReturnXY.y;
            float res = Compensate(movedY, player.movementInput.rotation.y, ref targetY, ref returnY);
            targetRotationXY.y = targetY;
            targetReturnXY.y = returnY;
            return res;
        }

        private static float Compensate(float moved, float original, ref float targetRotation, ref float targetReturn)
        {
            float dsScale = 1;
            if (player.AimingGun)
            {
                dsScale = Mathf.Lerp(1, 1 / PlayerMoveController.Instance.mouseZoomSensitivity, recoilCompensationSensitivityMultiplier);
                //dsScale = 1 / GamePrefs.GetFloat(EnumGamePrefs.OptionsZoomSensitivity);
                //if (player.inventory.holdingItemData.actionData[1] is IModuleContainerFor<ActionModuleDynamicSensitivity.DynamicSensitivityData> dsDataContainer && dsDataContainer.Instance.activated)
                //{
                //    dsScale *= Mathf.Sqrt(dsDataContainer.Instance.ZoomRatio);
                //}
            }
            float modified = moved * dsScale - original;
            float target = ApplyOppositeCompensation(targetRotation, modified, out modified);
            modified /= dsScale;
            float compensated = target - targetRotation;
            //if (compensated < 0)
            //{
            //    Log.Out($"compensated {compensated} prev {targetRotation} cur {target}");
            //}
            targetRotation = target;
            float @return = targetReturn + (modified * targetReturn < 0 ? modified : 0);
            //Log.Out($"return {@return} targetReturn {targetReturn} compensated {compensated} modified {modified}");
            if (@return * targetReturn > 0)
            {
                targetReturn = @return;
            }
            else
            {
                targetReturn = 0;
            }
            return original + modified;
        }

        public static void ApplyRecoil()
        {
            if (player == null)
                return;
            if (state == RecoilState.Recoil)
            {
                //Log.Out($"target rotation {targetRotationXY}");
                //if (targetRotationXY.sqrMagnitude <= 1e-6)
                //{
                //    targetRotationXY = Vector3.zero;
                //    recoilScaledDelta = 1;
                //    returnSpeedCur = Vector3.zero;
                //    state = RecoilState.Return;
                //    return;
                //}
                //returnScaledDelta = 0;

                FastTags<TagGroup.Global> actionTags = player.inventory.holdingItemItemValue.ItemClass.ItemTags;
                MultiActionManager.ModifyItemTags(player.inventory.holdingItemItemValue, player.inventory.holdingItemData.actionData[MultiActionManager.GetActionIndexForEntity(player)], ref actionTags);
                float snappinessDefault;
                if (actionTags.Test_AnySet(PistolTag))
                {
                    snappinessDefault = DEFAULT_SNAPPINESS_PISTOL;
                }
                else if (actionTags.Test_AnySet(ShotgunTag))
                {
                    snappinessDefault = DEFAULT_SNAPPINESS_SHOTGUN;
                }
                else
                {
                    snappinessDefault = DEFAULT_SNAPPINESS_RIFLE;
                }
                float snappiness = EffectManager.GetValue(CustomEnums.RecoilSnappiness, player.inventory.holdingItemItemValue, snappinessDefault, player);
                //targetRotationXY = Vector2.Lerp(targetRotationXY, Vector2.zero, returnSpeed * Time.deltaTime);
                float scaledDeltaTime = (Time.time - lastKickTime) * snappiness * 3;
                Vector3 result = Vector3.Lerp(Vector3.zero, new Vector3(targetRotationXY.x, targetRotationXY.y), Mathf.Sin(Mathf.PI * .5f * Mathf.Lerp(0, 1, scaledDeltaTime)));
                targetRotationXY -= new Vector2(result.x, result.y);
                targetReturnXY += new Vector2(result.x, result.y);
                player.movementInput.rotation += result;
                if (scaledDeltaTime >= 1)
                {
                    targetRotationXY = Vector3.zero;
                    returnSpeedCur = Vector3.zero;
                    state = RecoilState.Return;
                }
            }
            else if (state == RecoilState.Return)
            {
                //Log.Out($"target return {targetReturnXY}");
                //if (targetReturnXY.sqrMagnitude <= 1e-6 && totalRotationXY.sqrMagnitude <= 1e-6)
                //{
                //    targetReturnXY = Vector3.zero;
                //    totalRotationXY = Vector2.zero;
                //    returnSpeedCur = Vector3.zero;
                //    totalReturnCur = Vector3.zero;
                //    //returnScaledDelta = 1;
                //    state = RecoilState.None;
                //    return;
                //}
                FastTags<TagGroup.Global> actionTags = player.inventory.holdingItemItemValue.ItemClass.ItemTags;
                MultiActionManager.ModifyItemTags(player.inventory.holdingItemItemValue, player.inventory.holdingItemData.actionData[MultiActionManager.GetActionIndexForEntity(player)], ref actionTags);
                float returnSpeedDefault;
                if (actionTags.Test_AnySet(PistolTag))
                {
                    returnSpeedDefault = DEFAULT_RETURN_SPEED_PISTOL;
                }
                else if (actionTags.Test_AnySet(ShotgunTag))
                {
                    returnSpeedDefault = DEFAULT_RETURN_SPEED_SHOTGUN;
                }
                else
                {
                    returnSpeedDefault = DEFAULT_RETURN_SPEED_RIFLE;
                }

                float returnSpeed = EffectManager.GetValue(CustomEnums.RecoilReturnSpeed, player.inventory.holdingItemItemValue, returnSpeedDefault, player);
                //returnScaledDelta += returnSpeed * Time.deltaTime;
                Vector3 result = Vector3.SmoothDamp(Vector3.zero, new Vector3(targetReturnXY.x, targetReturnXY.y), ref returnSpeedCur, 1 / returnSpeed);
                targetReturnXY -= new Vector2(result.x, result.y);
                player.movementInput.rotation -= result;
                if (enableCap)
                {
                    result = Vector3.SmoothDamp(Vector3.zero, new Vector3(totalRotationXY.x, totalRotationXY.y), ref totalReturnCur, 4 / returnSpeed);
                    totalRotationXY -= new Vector2(result.x, result.y);
                }
                if (targetReturnXY.sqrMagnitude <= 1e-6 && totalRotationXY.sqrMagnitude <= 1e-6 && Time.time - lastKickTime >= 1 / returnSpeed)
                {
                    targetReturnXY = Vector3.zero;
                    totalRotationXY = Vector2.zero;
                    returnSpeedCur = Vector3.zero;
                    totalReturnCur = Vector3.zero;
                    state = RecoilState.None;
                }
            }
            else
            {
                ClearData();
            }
        }

        private static float ApplyOppositeCompensation(float target, float mod, out float modRes)
        {
            //mouse movement come in with the same direction as recoil
            if (mod * target >= 0)
            {
                modRes = mod;
                return target;
            }
            float res = target + mod;
            //is mouse movement enough to compensate the recoil?
            if (res * target >= 0)
            {
                modRes = 0;
                return res;
            }
            else
            {
                modRes = res;
                return 0;
            }
        }
    }
}
