#if NotEditor
using HarmonyLib;
using System;
using System.Reflection;
#endif
using UnityEngine;
using UnityEngine.Rendering.PostProcessing;

namespace KFCommonUtilityLib.KFAttached.Render
{
    [AddComponentMenu("KFAttachments/Render Utils/Magnify Scope")]
    [RequireComponent(typeof(Renderer))]
    public class MagnifyScope : MonoBehaviour
    {
#if NotEditor
        private static Shader newShader;
        private static FieldInfo fieldResources = AccessTools.Field(typeof(PostProcessLayer), "m_Resources");
#endif
        private RenderTexture targetTexture;
        private Renderer renderTarget;

        private Camera pipCamera;
        [Header("Core")]
        [SerializeField]
        private bool manualControl = false;
        [SerializeField]
        private Transform cameraJoint;
        [SerializeField]
        private float aspectRatio = 1.0f;
        [SerializeField]
        private bool hideFpvModelInScope = false;
        [SerializeField]
        private bool variableZoom = false;
        [Header("Reticle Scaling")]
        [SerializeField]
        private bool scaleReticle = false;
        [SerializeField]
        private Vector2 reticleSizeRange = new Vector2(1, 1);
        //[SerializeField]
        //private bool scaleDownReticle = false;
        //[SerializeField]
        //private float reticleScaleRatio = 1.0f;
        [Header("Camera Texture Size And Procedural Aiming")]
        [SerializeField]
        private Transform aimRef;
        [SerializeField]
        private float lensSizeFull;
        [SerializeField]
        private float lensSizeValid;

        private float initialReticleScale = 1f;
        private float initialFov = 55f;
        private float textureHeight = Screen.height;

#if NotEditor
        private EntityPlayerLocal player;
        private int itemSlot = -1;
        private ItemActionZoom.ItemActionDataZoom zoomActionData;
        private bool IsVariableZoom => variableZoom && variableZoomData != null;
        private ActionModuleVariableZoom.VariableZoomData variableZoomData;
        private float targetStep = 0;
        private float currentStep = 0;
        private float stepVelocity = 0;
#else
        [Header("Editor Debug")]
        public Camera debugCamera;
        public float debugScale = 2f;
#endif
        private void Awake()
        {
            renderTarget = GetComponent<Renderer>();
            if (!renderTarget)
            {
                Destroy(this);
                return;
            }
#if NotEditor

            if (newShader == null)
            {
                newShader = LoadManager.LoadAsset<Shader>("#@modfolder(CommonUtilityLib):Resources/PIPScope.unity3d?PIPScope.shadergraph", null, null, false, true).Asset;
            }
            if (renderTarget.material.shader.name == "Shader Graphs/MagnifyScope" || renderTarget.material.shader.name == "Shader Graphs/PIPScopeNew")
            {
                renderTarget.material.shader = newShader;
            }
            initialReticleScale = renderTarget.material.GetFloat("_ReticleScale");
#else
            if(debugCamera == null)
            {
                Destroy(this);
                return;
            }
#endif
            //if (!player.playerCamera.TryGetComponent<BokehBlurTargetRef>(out var bokeh))
            //{
            //    bokeh = player.playerCamera.gameObject.AddComponent<BokehBlurTargetRef>();
            //}
            //bokeh.target = this;
            // Precompute rotations
        }

#if NotEditor
        private void Start()
        {
            var entity = GetComponentInParent<EntityPlayerLocal>();
            if (!entity)
            {
                Destroy(gameObject);
                return;
            }
            player = entity;
            itemSlot = player.inventory.holdingItemIdx;
            OnEnable();
        }
#endif

        private void OnEnable()
        {
            float targetFov;
#if NotEditor
            if (!player)
            {
                return;
            }
            CalcInitialFov();
            //inventory holding item is not set when creating model, this might be an issue for items with base scope that has this script attached
            //workaround taken from alternative action module, which keeps a reference to the ItemValue being set until its custom data is created
            //afterwards it's set to null so we still need to access holding item when this method is triggered by mods
            if (itemSlot != player.inventory.holdingItemIdx)
            {
                Log.Out($"Scope shader script: Expecting holding item idx {itemSlot} but getting {player.inventory.holdingItemIdx}!");
                return;
            }
            var zoomAction = (ItemActionZoom)((ActionModuleAlternative.InventorySetItemTemp?.ItemClass ?? player.inventory.holdingItem).Actions[1]);
            zoomActionData = (ItemActionZoom.ItemActionDataZoom)player.inventory.holdingItemData.actionData[1];
            variableZoomData = (zoomActionData as IModuleContainerFor<ActionModuleVariableZoom.VariableZoomData>)?.Instance;
            if (variableZoomData != null && (variableZoom || variableZoomData.forceFov))
            {
                if (variableZoom)
                {
                    variableZoomData.shouldUpdate = false;
                    targetStep = currentStep = variableZoomData.curStep;
                    stepVelocity = 0f;
                    targetFov = CalcCurrentFov();
                }
                else
                {
                    targetFov = variableZoomData.fovRange.min;
                }
            }
            else
            {
                string originalRatio = zoomAction.Properties.GetString("ZoomRatio");
                if (string.IsNullOrEmpty(originalRatio))
                {
                    originalRatio = "0";
                }
                targetFov = StringParsers.ParseFloat(player.inventory.holdingItemItemValue.GetPropertyOverride("ZoomRatio", originalRatio));
                targetFov = ScaleToFov(targetFov);
            }

#else
            if(debugCamera == null)
            {
                Destroy(this);
            }
            targetFov = Mathf.Rad2Deg * 2 * Mathf.Atan(Mathf.Tan(Mathf.Deg2Rad * 27.5f) / Mathf.Sqrt(debugScale));
#endif
            CreateCamera();
            UpdateFOV(targetFov);
        }

#if NotEditor
        private void Update()
        {
            if (IsVariableZoom)
            {
                if (variableZoomData.shouldUpdate)
                {
                    variableZoomData.shouldUpdate = false;
                    targetStep = variableZoomData.curStep;
                }
                if (currentStep != targetStep)
                {
                    if (variableZoomData.isToggleOnly)
                    {
                        currentStep = targetStep;
                    }
                    else
                    {
                        currentStep = Mathf.SmoothDamp(currentStep, targetStep, ref stepVelocity, 0.05f);
                    }
                    UpdateFOV(CalcCurrentFov());
                }
            }

            if (!manualControl && zoomActionData != null)
            {
                if (player.bFirstPersonView)
                {
                    bool aimingGun = player.AimingGun;
                    if (aimingGun && !pipCamera.gameObject.activeSelf)
                    {
                        pipCamera.gameObject.SetActive(true);
                    }
                    else if (!aimingGun && !zoomActionData.bZoomInProgress && pipCamera.gameObject.activeSelf)
                    {
                        pipCamera.gameObject.SetActive(false);
                    }
                }
                else if (pipCamera.gameObject.activeSelf)
                {
                    pipCamera.gameObject.SetActive(false);
                }
            }
        }
#endif

        private void OnDisable()
        {
            DestroyCamera();
#if NotEditor
            currentStep = targetStep = stepVelocity = 0f;
#else
            if(debugCamera == null)
            {
                Destroy(this);
            }
#endif
        }

        private float ScaleToFov(float scale)
        {
            return Mathf.Rad2Deg * 2 * Mathf.Atan(Mathf.Tan(Mathf.Deg2Rad * initialFov * 0.5f) / scale);
        }

        private float FovToScale(float fov)
        {
            return Mathf.Tan(Mathf.Deg2Rad * initialFov * 0.5f) / Mathf.Tan(Mathf.Deg2Rad * fov * 0.5f);
        }

#if NotEditor
        private void CalcInitialFov()
        {
            if (aimRef)
            {
                var distance = Mathf.Abs(Vector3.Dot(renderTarget.bounds.center - aimRef.position, aimRef.forward));
                var scaleFov = lensSizeValid / (2 * distance * Mathf.Tan(Mathf.Deg2Rad * 27.5f));
                var scaleTexture = lensSizeFull / (2 * distance * Mathf.Tan(Mathf.Deg2Rad * 27.5f));
                textureHeight = scaleTexture * Screen.height;
                //textureHeight = Mathf.Abs(player.playerCamera.WorldToScreenPoint(player.playerCamera.transform.forward * distance + player.playerCamera.transform.up * height).y - 
                //                          player.playerCamera.WorldToScreenPoint(player.playerCamera.transform.forward * distance - player.playerCamera.transform.up * height).y);
                initialFov = Mathf.Rad2Deg * 2 * Mathf.Atan(Mathf.Tan(Mathf.Deg2Rad * 27.5f) * scaleFov);
                Log.Out($"distance {distance}, scale fov {scaleFov}, scale texture {scaleTexture} texture height {textureHeight} initial fov {initialFov}");
                return;
            }
            textureHeight = Screen.height * 0.5f;
            initialFov = 15;
        }

        private static float CalcFovStep(float t, float fovMin, float fovMax)
        {
            return 2f * Mathf.Rad2Deg * Mathf.Atan(Mathf.Lerp(Mathf.Tan(fovMax * 0.5f * Mathf.Deg2Rad), Mathf.Tan(fovMin * 0.5f * Mathf.Deg2Rad), t));
        }

        private float CalcCurrentFov()
        {
            if (!IsVariableZoom)
            {
                throw new Exception("Variable Zoom is not set!");
            }
            float targetFov;
            if (variableZoomData.forceFov)
            {
                targetFov = CalcFovStep(currentStep, variableZoomData.fovRange.min, variableZoomData.fovRange.max);
            }
            else
            {
                targetFov = ScaleToFov(Mathf.Lerp(variableZoomData.minScale, variableZoomData.maxScale, currentStep));
            }
            return targetFov;
        }
#endif

        private void DestroyCamera()
        {
            if (targetTexture && targetTexture.IsCreated())
            {
                targetTexture.Release();
                Destroy(targetTexture);
            }
            if (pipCamera)
            {
                Destroy(pipCamera.gameObject);
            }
        }

        private void UpdateFOV(float targetFov)
        {
            if (targetFov > 0)
            {
                pipCamera.fieldOfView = targetFov;
#if NotEditor
                if (scaleReticle && IsVariableZoom)
                {
                    renderTarget.material.SetFloat("_ReticleScale", Mathf.Lerp(reticleSizeRange.x, reticleSizeRange.y, currentStep));
                    //if (variableZoomData.maxScale > variableZoomData.minScale)
                    //{
                    //    float minScale;
                    //    if (reticleScaleRatio >= 1)
                    //    {
                    //        minScale = scaleDownReticle ? 1 - (variableZoomData.maxScale * reticleScaleRatio - variableZoomData.minScale) / (variableZoomData.maxScale * reticleScaleRatio) : 1;
                    //    }
                    //    else
                    //    {
                    //        minScale = scaleDownReticle ? 1 - reticleScaleRatio * (variableZoomData.maxScale - variableZoomData.minScale) / variableZoomData.maxScale : 1;
                    //    }
                    //    float maxScale;
                    //    if (reticleScaleRatio >= 1)
                    //    {
                    //        maxScale = scaleDownReticle ? 1 : variableZoomData.maxScale * reticleScaleRatio / variableZoomData.minScale;
                    //    }
                    //    else
                    //    {
                    //        maxScale = scaleDownReticle ? 1 : 1 + reticleScaleRatio * (variableZoomData.maxScale - variableZoomData.minScale) / variableZoomData.minScale;
                    //    }
                    //    float reticleScale = Mathf.Lerp(minScale, maxScale, variableZoomData.curStep);
                    //    renderTarget.material.SetFloat("_ReticleScale", initialReticleScale / reticleScale);
                    //}
                    //else
                    //{
                    //    renderTarget.material.SetFloat("_ReticleScale", initialReticleScale);
                    //}
                }
                //Log.Out($"target fov {targetFov} target scale {targetScale}");
#endif
            }
        }

        private void CreateCamera()
        {
            const float texScale = 1f;
            targetTexture = new RenderTexture((int)(textureHeight * aspectRatio), (int)(textureHeight), 24, RenderTextureFormat.ARGBHalf, RenderTextureReadWrite.Linear)
            {
                filterMode = FilterMode.Bilinear,
                wrapMode = TextureWrapMode.Clamp
            };
            renderTarget.material.mainTexture = targetTexture;
            GameObject cameraGO = new GameObject("KFPiPCam");
            if (cameraJoint != null)
            {
                cameraGO.transform.parent = cameraJoint.transform;
            }
            else
            {
                cameraGO.transform.parent = transform;
            }

            pipCamera = cameraGO.AddComponent<Camera>();
            pipCamera.targetTexture = targetTexture;
            pipCamera.depth = -2;
            pipCamera.fieldOfView = 55;
            pipCamera.nearClipPlane = 0.05f;
            pipCamera.farClipPlane = 5000;
            pipCamera.aspect = aspectRatio;
            pipCamera.rect = new Rect(0, 0, texScale, texScale);
#if NotEditor
            //pipCamera.CopyFrom(player.playerCamera);
            pipCamera.cullingMask = player.playerCamera.cullingMask;
            //renderTarget.material.SetFloat("_AspectMain", player.playerCamera.aspect);
            //renderTarget.material.SetFloat("_AspectScope", pipCamera.aspect);
#else
            pipCamera.CopyFrom(debugCamera);
#endif
            if (cameraJoint == null || hideFpvModelInScope)
            {
                pipCamera.cullingMask &= ~(1024);
            }
            else
            {
                pipCamera.cullingMask |= 1024;
            }
            cameraGO.transform.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity);
            cameraGO.transform.localScale = Vector3.one;

#if NotEditor
            WeaponCameraFollow weaponCameraFollow = cameraGO.AddComponent<WeaponCameraFollow>();
            weaponCameraFollow.targetTexture = targetTexture;
            weaponCameraFollow.dynamicSensitivityData = (zoomActionData as IModuleContainerFor<ActionModuleDynamicSensitivity.DynamicSensitivityData>)?.Instance;
            weaponCameraFollow.player = player;
            var old = player.playerCamera.GetComponent<PostProcessLayer>();
            var layer = pipCamera.gameObject.GetOrAddComponent<PostProcessLayer>();
            //layer.antialiasingMode = old.antialiasingMode;
            //layer.superResolution = (SuperResolution)old.superResolution.GetType().CreateInstance();
            layer.Init(fieldResources.GetValue(old) as PostProcessResources);
            //weaponCameraFollow.UpdateAntialiasing();
#endif
        }

        internal void RenderImageCallback(RenderTexture source, RenderTexture destination)
        {
        }
    }
}
