using Unity.Cinemachine;
using UnityEngine;

public struct CameraSettingSingleValue<T>{
    // NOTE: Could add a locked variable here if needed??
    public T originalValue;
    public T targetValue;
    public float smoothing;
    
    public float velocityRef;
    public Vector2 velocityRefV2;
    public Vector3 velocityRefV3;

    public CameraSettingSingleValue(float defaultSmoothing = .2f, T original = default(T)){
        originalValue = original;
        targetValue = original;
        smoothing = defaultSmoothing;

        velocityRef = 0;
        velocityRefV2 = default;
        velocityRefV3 = default;
    }

    public void Reset(){
        targetValue = originalValue;
    }
}

public struct CameraSettingValues{
    public CameraSettingSingleValue<float> mainFieldOfView;

    public CameraSettingSingleValue<Vector3> orbitPositionDamping;
    public CameraSettingSingleValue<Vector3> orbitTargetOffset;
    
    public CameraSettingSingleValue<bool> axisLookEnabledX;
    public CameraSettingSingleValue<bool> axisLookEnabledY;

    public CameraSettingSingleValue<float> axisLookGainX;
    public CameraSettingSingleValue<float> axisLookGainY;
    
    public CameraSettingSingleValue<float> orbitFollowTopHeight;
    public CameraSettingSingleValue<float> orbitFollowTopRadius;
    public CameraSettingSingleValue<float> orbitFollowCenterHeight;
    public CameraSettingSingleValue<float> orbitFollowCenterRadius;
    public CameraSettingSingleValue<float> orbitFollowBottomHeight;
    public CameraSettingSingleValue<float> orbitFollowBottomRadius;

    public CameraSettingSingleValue<Vector2> rotationComposerScreenPos;
    
    public CameraSettingSingleValue<Vector3> cameraOffsetOffset;

    public CameraSettingValues(float defaultSmoothing){
        mainFieldOfView = new CameraSettingSingleValue<float>(defaultSmoothing);
        
        orbitPositionDamping = new CameraSettingSingleValue<Vector3>(defaultSmoothing);
        orbitTargetOffset = new CameraSettingSingleValue<Vector3>(defaultSmoothing);

        axisLookEnabledX = new CameraSettingSingleValue<bool>();
        axisLookEnabledY = new CameraSettingSingleValue<bool>();
        
        axisLookGainX = new CameraSettingSingleValue<float>(defaultSmoothing);
        axisLookGainY = new CameraSettingSingleValue<float>(defaultSmoothing);
        
        orbitFollowTopHeight = new CameraSettingSingleValue<float>(defaultSmoothing);
        orbitFollowTopRadius = new CameraSettingSingleValue<float>(defaultSmoothing);
        orbitFollowCenterHeight = new CameraSettingSingleValue<float>(defaultSmoothing);
        orbitFollowCenterRadius = new CameraSettingSingleValue<float>(defaultSmoothing);
        orbitFollowBottomHeight = new CameraSettingSingleValue<float>(defaultSmoothing);
        orbitFollowBottomRadius = new CameraSettingSingleValue<float>(defaultSmoothing);
        
        rotationComposerScreenPos = new CameraSettingSingleValue<Vector2>(defaultSmoothing);
        cameraOffsetOffset = new CameraSettingSingleValue<Vector3>(defaultSmoothing);
    }
}

public class CameraSettingsProcessor : MonoBehaviour{
    public static CameraSettingsProcessor Instance{ get; private set; }

    public static CameraSettingValues values = new(defaultSmoothing: .2f);
    
    public static GameObject mainCamera;

    private CinemachineCamera main;
    private CinemachineOrbitalFollow orbit;
    private CinemachineRotationComposer rotComp;
    private CinemachineCameraOffset offset;
    private CinemachineInputAxisController axisCont;
    
    public void Awake(){
        // Singleton management
        if (Instance != null && Instance != this) {
            Destroy(this);
        } else {
            Instance = this;
        }

        // Set references for camera object and cinemachine components
        mainCamera = gameObject;
        main = mainCamera.GetComponent<CinemachineCamera>();
        orbit = mainCamera.GetComponent<CinemachineOrbitalFollow>();
        rotComp = mainCamera.GetComponent<CinemachineRotationComposer>();
        offset = mainCamera.GetComponent<CinemachineCameraOffset>();
        axisCont = mainCamera.GetComponent<CinemachineInputAxisController>();

        // Initialize camera settings values
        values = new CameraSettingValues{
            cameraOffsetOffset = new CameraSettingSingleValue<Vector3>(defaultSmoothing: .2f, offset.Offset),
            mainFieldOfView = new CameraSettingSingleValue<float>(defaultSmoothing: .2f, main.Lens.FieldOfView),
            axisLookEnabledX = new CameraSettingSingleValue<bool>(0, axisCont.Controllers[0].Enabled),
            axisLookEnabledY = new CameraSettingSingleValue<bool>(0, axisCont.Controllers[1].Enabled),
            axisLookGainX = new CameraSettingSingleValue<float>(defaultSmoothing: .2f, axisCont.Controllers[0].Input.Gain),
            axisLookGainY = new CameraSettingSingleValue<float>(defaultSmoothing: .2f, axisCont.Controllers[1].Input.Gain),
            orbitPositionDamping = new CameraSettingSingleValue<Vector3>(defaultSmoothing: .2f, orbit.TrackerSettings.PositionDamping),
            orbitTargetOffset = new CameraSettingSingleValue<Vector3>(defaultSmoothing: .1f, orbit.TargetOffset),
            orbitFollowTopHeight = new CameraSettingSingleValue<float>(defaultSmoothing: .2f, orbit.Orbits.Top.Height),
            orbitFollowTopRadius = new CameraSettingSingleValue<float>(defaultSmoothing: .2f, orbit.Orbits.Top.Radius),
            orbitFollowCenterHeight = new CameraSettingSingleValue<float>(defaultSmoothing: .2f, orbit.Orbits.Center.Height),
            orbitFollowCenterRadius = new CameraSettingSingleValue<float>(defaultSmoothing: .2f, orbit.Orbits.Center.Radius),
            orbitFollowBottomHeight = new CameraSettingSingleValue<float>(defaultSmoothing: .2f, orbit.Orbits.Bottom.Height),
            orbitFollowBottomRadius = new CameraSettingSingleValue<float>(defaultSmoothing: .2f, orbit.Orbits.Bottom.Radius),
            rotationComposerScreenPos = new CameraSettingSingleValue<Vector2>(defaultSmoothing: .2f, rotComp.Composition.ScreenPosition),
        };
    }
    
    void ProcessCameraValues(){
        main.Lens.FieldOfView = Mathf.SmoothDamp(main.Lens.FieldOfView,
            values.mainFieldOfView.targetValue, ref values.mainFieldOfView.velocityRef,
            values.mainFieldOfView.smoothing);

        axisCont.Controllers[0].Enabled = values.axisLookEnabledX.targetValue;
        
        axisCont.Controllers[1].Enabled = values.axisLookEnabledY.targetValue;
        
        axisCont.Controllers[0].Input.Gain = Mathf.SmoothDamp(axisCont.Controllers[0].Input.Gain,
            values.axisLookGainX.targetValue, ref values.axisLookGainX.velocityRef,
            values.axisLookGainX.smoothing);
        
        axisCont.Controllers[1].Input.Gain = Mathf.SmoothDamp(axisCont.Controllers[1].Input.Gain,
            values.axisLookGainY.targetValue, ref values.axisLookGainY.velocityRef,
            values.axisLookGainY.smoothing);
        
        orbit.TargetOffset = Vector3.SmoothDamp(orbit.TargetOffset,
            values.orbitTargetOffset.targetValue, ref values.orbitTargetOffset.velocityRefV3,
            values.orbitTargetOffset.smoothing);
        
        orbit.TrackerSettings.PositionDamping = Vector3.SmoothDamp(orbit.TrackerSettings.PositionDamping,
            values.orbitPositionDamping.targetValue, ref values.orbitPositionDamping.velocityRefV3,
            values.orbitPositionDamping.smoothing);
        
        orbit.Orbits.Top.Height = Mathf.SmoothDamp(orbit.Orbits.Top.Height,
            values.orbitFollowTopHeight.targetValue, ref values.orbitFollowTopHeight.velocityRef,
            values.orbitFollowTopHeight.smoothing);
        
        orbit.Orbits.Top.Radius = Mathf.SmoothDamp(orbit.Orbits.Top.Radius,
            values.orbitFollowTopRadius.targetValue, ref values.orbitFollowTopRadius.velocityRef,
            values.orbitFollowTopRadius.smoothing);
        
        orbit.Orbits.Center.Height = Mathf.SmoothDamp(orbit.Orbits.Center.Height,
            values.orbitFollowCenterHeight.targetValue, ref values.orbitFollowCenterHeight.velocityRef,
            values.orbitFollowCenterHeight.smoothing);
        
        orbit.Orbits.Center.Radius = Mathf.SmoothDamp(orbit.Orbits.Center.Radius,
            values.orbitFollowCenterRadius.targetValue, ref values.orbitFollowCenterRadius.velocityRef,
            values.orbitFollowCenterRadius.smoothing);
        
        orbit.Orbits.Bottom.Height = Mathf.SmoothDamp(orbit.Orbits.Bottom.Height,
            values.orbitFollowBottomHeight.targetValue, ref values.orbitFollowBottomHeight.velocityRef,
            values.orbitFollowBottomHeight.smoothing);
        
        orbit.Orbits.Bottom.Radius = Mathf.SmoothDamp(orbit.Orbits.Bottom.Radius,
            values.orbitFollowBottomRadius.targetValue, ref values.orbitFollowBottomRadius.velocityRef,
            values.orbitFollowBottomRadius.smoothing);

        rotComp.Composition.ScreenPosition = Vector2.SmoothDamp(rotComp.Composition.ScreenPosition,
            values.rotationComposerScreenPos.targetValue, ref values.rotationComposerScreenPos.velocityRefV2,
            values.rotationComposerScreenPos.smoothing);
        
        offset.Offset = Vector3.SmoothDamp(offset.Offset,
            values.cameraOffsetOffset.targetValue, ref values.cameraOffsetOffset.velocityRefV3,
            values.cameraOffsetOffset.smoothing);
        
        
    }
    
    void Update(){
        ProcessCameraValues();
    }
}
