using NodeCanvas.Framework;
using ParadoxNotion.Design;
using Unity.Cinemachine;
using UnityEditor;
using UnityEngine;

// Individual bool setting for each ring. Three of these will be used. 
public struct OrbitalFollowValueGroup : ICameraValueGroup{
	public string label;
	
	public CameraSettingsToggle changeHeight;
	public float height;
	
	public CameraSettingsToggle changeRadius;
	public float radius;

	public OrbitalFollowValueGroup(string newLabel){
		label = newLabel;
		changeHeight = CameraSettingsToggle.NoChange;
		height = 0f;
		changeRadius = CameraSettingsToggle.NoChange;
		radius = 0f;
	}
}

// Setting for any X and Y based values
public struct CinemachineVectorValueGroup{
	public CameraSettingsToggle changeX;
	public float xValue;
	public CameraSettingsToggle changeY;
	public float yValue;
}

// Enum options for individual camera settings
public enum CameraSettingsToggle{
	NoChange,
	NewValue,
	ResetValue,
	RelativeValue, // Placeholder for using as altering existing value
}

public interface ICameraValueGroup{
	
}

public struct Vector3CameraValueGroup : ICameraValueGroup{
	public string label;
	public Vector3 newValue;
	
	public CameraSettingsToggle changeX;
	public CameraSettingsToggle changeY;
	public CameraSettingsToggle changeZ;
	
	public Vector3CameraValueGroup(string newLabel){
		changeX = CameraSettingsToggle.NoChange;
		changeY = CameraSettingsToggle.NoChange;
		changeZ = CameraSettingsToggle.NoChange;

		newValue = Vector3.zero;
		label = newLabel;
	}
}

public struct Vector2CameraValueGroup : ICameraValueGroup{
	public string label;
	public Vector2 newValue;
	
	public CameraSettingsToggle changeX;
	public CameraSettingsToggle changeY;
	
	public Vector2CameraValueGroup(string newLabel){
		changeX = CameraSettingsToggle.NoChange;
		changeY = CameraSettingsToggle.NoChange;

		newValue = Vector2.zero;
		label = newLabel;
	}
}

public struct FloatCameraValueGroup : ICameraValueGroup{
	public string label;
	public float value;

	public CameraSettingsToggle changeValue;

	public FloatCameraValueGroup(string newLabel){
		label = newLabel;
		value = 0f;
		changeValue = CameraSettingsToggle.NoChange;
	}
}

public struct BoolCameraValueGroup : ICameraValueGroup{
	public string label;
	public bool value;

	public CameraSettingsToggle changeValue;

	public BoolCameraValueGroup(string newLabel){
		label = newLabel;
		value = true;
		changeValue = CameraSettingsToggle.NoChange;
	}
}

#if UNITY_EDITOR
public class BoolCameraValueGroupDrawer : ObjectDrawer<BoolCameraValueGroup>  {
	public override BoolCameraValueGroup OnGUI(GUIContent content, BoolCameraValueGroup _instance){
		// Remove label for floats
		EditorGUIUtility.labelWidth = 50;
		
		// Set layout options for the label and the float fields
		GUILayoutOption[] floatOptions = new GUILayoutOption[] {
			GUILayout.Width(80.0f), 
			GUILayout.MinWidth(20.0f), 
			GUILayout.ExpandWidth(true), 
		};

		GUILayoutOption[] labelOptions = new GUILayoutOption[]{
			GUILayout.Width(200.0f),
		};
		
		// Start the Vertical layout then add the label before adding a horizontal so the label will be on top of side-by-side options
		GUILayout.BeginVertical();
		GUILayout.Label(_instance.label, labelOptions);
		GUILayout.BeginHorizontal();
		
		// Create the x settings enum
		_instance.changeValue = (CameraSettingsToggle)EditorGUILayout.EnumPopup("", instance.changeValue);
		
		// Create the value/disabled information field
		if (_instance.changeValue == CameraSettingsToggle.NewValue){
			_instance.value = EditorGUILayout.Toggle(_instance.value, floatOptions);
		} else {
			EditorGUI.BeginDisabledGroup(true);
			EditorGUILayout.TextField(_instance.changeValue == CameraSettingsToggle.NoChange ? "Unchanged" : "Reset", floatOptions);
			EditorGUI.EndDisabledGroup();
		}
		
		// Close this line up
		GUILayout.EndHorizontal();
		GUILayout.EndVertical();
		
		// Reset to default so the rest of things don't get messed up
		EditorGUIUtility.labelWidth = 0;
		return _instance;
	}
}

public class FloatCameraValueGroupDrawer : ObjectDrawer<FloatCameraValueGroup>  {
	public override FloatCameraValueGroup OnGUI(GUIContent _content, FloatCameraValueGroup _instance){
		// Remove label for floats
		EditorGUIUtility.labelWidth = 50;
		
		// Set layout options for the label and the float fields
		GUILayoutOption[] floatOptions = new GUILayoutOption[] {
			GUILayout.Width(80.0f), 
			GUILayout.MinWidth(20.0f), 
			GUILayout.ExpandWidth(true), 
		};

		GUILayoutOption[] labelOptions = new GUILayoutOption[]{
			GUILayout.Width(200.0f),
		};
		
		// Start the Vertical layout then add the label before adding a horizontal so the label will be on top of side-by-side options
		GUILayout.BeginVertical();
		GUILayout.Label(_instance.label, labelOptions);
		GUILayout.BeginHorizontal();
		
		// Create the x settings enum
		_instance.changeValue = (CameraSettingsToggle)EditorGUILayout.EnumPopup("", instance.changeValue);
		
		// Create the value/disabled information field
		if (_instance.changeValue == CameraSettingsToggle.NewValue){
			_instance.value = EditorGUILayout.FloatField(_instance.value, floatOptions);
		} else {
			EditorGUI.BeginDisabledGroup(true);
			EditorGUILayout.TextField(_instance.changeValue == CameraSettingsToggle.NoChange ? "Unchanged" : "Reset", floatOptions);
			EditorGUI.EndDisabledGroup();
		}
		
		// Close this line up
		GUILayout.EndHorizontal();
		GUILayout.EndVertical();
		
		// Reset to default so the rest of things don't get messed up
		EditorGUIUtility.labelWidth = 0;
		return _instance;
	}
}

public class Vector3CameraValueGroupDrawer : ObjectDrawer<Vector3CameraValueGroup>  {
	public override Vector3CameraValueGroup OnGUI(GUIContent _content, Vector3CameraValueGroup _instance){
		// Remove label for floats
		EditorGUIUtility.labelWidth = 20;
		
		// Set layout options for the label and the float fields
		GUILayoutOption[] floatOptions = new GUILayoutOption[] {
			GUILayout.Width(300.0f), 
			GUILayout.MinWidth(20.0f), 
			GUILayout.ExpandWidth(true), 
		};

		GUILayoutOption[] labelOptions = new GUILayoutOption[]{
			GUILayout.Width(200.0f),
		};
		
		// Start the Vertical layout then add the label before adding a horizontal so the label will be on top of side-by-side options
		GUILayout.BeginVertical();
		GUILayout.Label(_instance.label, labelOptions);
		GUILayout.BeginHorizontal();
		
		// Create the x settings enum
		_instance.changeX = (CameraSettingsToggle)EditorGUILayout.EnumPopup("", instance.changeX);
		
		// Create the value/disabled information field
		if (_instance.changeX == CameraSettingsToggle.NewValue){
			_instance.newValue.x = EditorGUILayout.FloatField(_instance.newValue.x, floatOptions);
		} else {
			EditorGUI.BeginDisabledGroup(true);
			EditorGUILayout.TextField(_instance.changeX == CameraSettingsToggle.NoChange ? "Unchanged" : "Reset", floatOptions);
			EditorGUI.EndDisabledGroup();
		}
		  
		// It do what it do.
		GUILayout.Space(5);
		
		// Create the y settings enum
		_instance.changeY = (CameraSettingsToggle)EditorGUILayout.EnumPopup("", _instance.changeY);
		
		// Create the value/disabled information field
		if (_instance.changeY == CameraSettingsToggle.NewValue){
			_instance.newValue.y = EditorGUILayout.FloatField(_instance.newValue.y, floatOptions);
		} else {
			EditorGUI.BeginDisabledGroup(true);
			EditorGUILayout.TextField(_instance.changeY == CameraSettingsToggle.NoChange ? "Unchanged" : "Reset", floatOptions);
			EditorGUI.EndDisabledGroup();
		}
		
		// It do what it do.
		GUILayout.Space(5);
		
		// Create the y settings enum
		_instance.changeZ = (CameraSettingsToggle)EditorGUILayout.EnumPopup("", _instance.changeZ);
		
		// Create the value/disabled information field
		if (_instance.changeZ == CameraSettingsToggle.NewValue){
			_instance.newValue.z = EditorGUILayout.FloatField(_instance.newValue.z, floatOptions);
		} else {
			EditorGUI.BeginDisabledGroup(true);
			EditorGUILayout.TextField(_instance.changeZ == CameraSettingsToggle.NoChange ? "Unchanged" : "Reset", floatOptions);
			EditorGUI.EndDisabledGroup();
		}
		
		// Close this line up
		GUILayout.EndHorizontal();
		GUILayout.EndVertical();
		
		// Reset to default so the rest of things don't get messed up
		EditorGUIUtility.labelWidth = 0;
		return _instance;
	}
}

public class Vector2CameraValueGroupDrawer : ObjectDrawer<Vector2CameraValueGroup>  {
	public override Vector2CameraValueGroup OnGUI(GUIContent _content, Vector2CameraValueGroup _instance){
		// Remove label for floats
		EditorGUIUtility.labelWidth = 50;
		
		// Set layout options for the label and the float fields
		GUILayoutOption[] floatOptions = new GUILayoutOption[] {
			GUILayout.Width(300.0f), 
			GUILayout.MinWidth(20.0f), 
			GUILayout.ExpandWidth(true), 
		};

		GUILayoutOption[] labelOptions = new GUILayoutOption[]{
			GUILayout.Width(200.0f),
		};
		
		// Start the Vertical layout then add the label before adding a horizontal so the label will be on top of side-by-side options
		GUILayout.BeginVertical();
		GUILayout.Label(_instance.label, labelOptions);
		GUILayout.BeginHorizontal();
		
		// Create the x settings enum
		_instance.changeX = (CameraSettingsToggle)EditorGUILayout.EnumPopup("", instance.changeX);
		
		// Create the value/disabled information field
		if (_instance.changeX == CameraSettingsToggle.NewValue){
			_instance.newValue.x = EditorGUILayout.FloatField(_instance.newValue.x, floatOptions);
		} else {
			EditorGUI.BeginDisabledGroup(true);
			EditorGUILayout.TextField(_instance.changeX == CameraSettingsToggle.NoChange ? "Unchanged" : "Reset", floatOptions);
			EditorGUI.EndDisabledGroup();
		}
		  
		// It do what it do.
		GUILayout.Space(5);
		
		// Create the y settings enum
		_instance.changeY = (CameraSettingsToggle)EditorGUILayout.EnumPopup("", _instance.changeY);
		
		// Create the value/disabled information field
		if (_instance.changeY == CameraSettingsToggle.NewValue){
			_instance.newValue.y = EditorGUILayout.FloatField(_instance.newValue.y, floatOptions);
		} else {
			EditorGUI.BeginDisabledGroup(true);
			EditorGUILayout.TextField(_instance.changeY == CameraSettingsToggle.NoChange ? "Unchanged" : "Reset", floatOptions);
			EditorGUI.EndDisabledGroup();
		}
		
		// Close this line up
		GUILayout.EndHorizontal();
		GUILayout.EndVertical();
		
		// Reset to default so the rest of things don't get messed up
		EditorGUIUtility.labelWidth = 0;
		return _instance;
	}
}

// Custom editor for each orbital follow ring setting
public class OrbitalFollowValueGroupDrawer : ObjectDrawer<OrbitalFollowValueGroup>{
	public override OrbitalFollowValueGroup OnGUI(GUIContent _content, OrbitalFollowValueGroup _instance){
		// Remove label for floats
		EditorGUIUtility.labelWidth = 1;
		
		// Set layout options for the label and the float fields
		GUILayoutOption[] floatOptions = new GUILayoutOption[] {
			GUILayout.Width(300.0f), 
			GUILayout.MinWidth(20.0f), 
			GUILayout.ExpandWidth(true), 
		};

		GUILayoutOption[] labelOptions = new GUILayoutOption[]{
			GUILayout.Width(60.0f),
		};
		
		// Start a Horiztonal Section
		GUILayout.BeginHorizontal();
		
		// Add the left side label
		GUILayout.Label(_instance.label, labelOptions);
		
		// Create the height settings enum
		_instance.changeHeight = (CameraSettingsToggle)EditorGUILayout.EnumPopup("", _instance.changeHeight);
		
		// Create the value/disabled information field
		if (_instance.changeHeight == CameraSettingsToggle.NewValue){
			_instance.height = EditorGUILayout.FloatField(_instance.height, floatOptions);
		} else {
			EditorGUI.BeginDisabledGroup(true);
			EditorGUILayout.TextField(_instance.changeHeight == CameraSettingsToggle.NoChange ? "Unchanged" : "Reset", floatOptions);
			EditorGUI.EndDisabledGroup();
		}
		  
		// It do what it do.
		GUILayout.Space(5);
		
		// Create the radius settings enum
		_instance.changeRadius = (CameraSettingsToggle)EditorGUILayout.EnumPopup("", _instance.changeRadius);
		
		// Create the value/disabled information field
		if (_instance.changeRadius == CameraSettingsToggle.NewValue){
			_instance.radius = EditorGUILayout.FloatField(_instance.radius, floatOptions);
		} else {
			EditorGUI.BeginDisabledGroup(true);
			EditorGUILayout.TextField(_instance.changeRadius == CameraSettingsToggle.NoChange ? "Unchanged" : "Reset", floatOptions);
			EditorGUI.EndDisabledGroup();
		}
		
		// Close this line up
		GUILayout.EndHorizontal();
		
		// Reset to default so the rest of things don't get messed up
		EditorGUIUtility.labelWidth = 0;
		return _instance;
	}
}
#endif

namespace NodeCanvas.Tasks.Actions {
	[Category("Reset")]
	[Description("Change Cinemachine camera settings for the player")]
	public class ChangeCameraSettings : ActionTask{
		[ParadoxNotion.Design.Header("Main Settings")]
		public FloatCameraValueGroup fieldOfView = new (newLabel: "FOV");

		[ParadoxNotion.Design.Header("Orbit Follow Ring Settings"), Space (5)]
		public Vector3CameraValueGroup orbitTargetOffset = new(newLabel: "Target Offset");
		[Space(5)]public Vector3CameraValueGroup orbitPositionDamping = new(newLabel: "Position Damping");
		
		public OrbitalFollowValueGroup orbitFollowTop = new (newLabel: "Top");
		public OrbitalFollowValueGroup orbitFollowCenter = new (newLabel: "Center");
		public OrbitalFollowValueGroup orbitFollowBottom = new (newLabel: "Bottom");

		public BoolCameraValueGroup enableXAxis = new (newLabel: "Input Axis X Enabled");
		public BoolCameraValueGroup enableYAxis = new (newLabel: "Input Axis Y Enabled");
		
		public FloatCameraValueGroup axisLookXGain = new (newLabel: "Look Orbit X Gain");
		public FloatCameraValueGroup axisLookYGain = new (newLabel: "Look Orbit Y Gain");
		
		[ParadoxNotion.Design.Header("Rotation Composer Settings")]
		public Vector2CameraValueGroup screenPosition = new (newLabel: "Screen Position");
		
		[ParadoxNotion.Design.Header("Camera Offset Settings")]
		public Vector3CameraValueGroup cameraOffset = new (newLabel: "Offset");
		
		//Use for initialization. This is called only once in the lifetime of the task.
		//Return null if init was successfull. Return an error string otherwise
		protected override string OnInit(){
			return null;
		}

		//This is called once each time the task is enabled.
		//Call EndAction() to mark the action as finished, either in success or failure.
		//EndAction can be called from anywhere.
		protected override void OnExecute(){
			UpdateOrbitFollowValue(orbitFollowTop, ref CameraSettingsProcessor.values.orbitFollowTopHeight, ref CameraSettingsProcessor.values.orbitFollowTopRadius);
			UpdateOrbitFollowValue(orbitFollowCenter, ref CameraSettingsProcessor.values.orbitFollowCenterHeight, ref CameraSettingsProcessor.values.orbitFollowCenterRadius);
			UpdateOrbitFollowValue(orbitFollowBottom, ref CameraSettingsProcessor.values.orbitFollowBottomHeight, ref CameraSettingsProcessor.values.orbitFollowBottomRadius);
			
			UpdateVector3Value(cameraOffset, ref CameraSettingsProcessor.values.cameraOffsetOffset);
			
			UpdateVector3Value(orbitPositionDamping, ref CameraSettingsProcessor.values.orbitPositionDamping);
			UpdateVector3Value(orbitTargetOffset, ref CameraSettingsProcessor.values.orbitTargetOffset);
			
			UpdateVector2Value(screenPosition, ref CameraSettingsProcessor.values.rotationComposerScreenPos);
			UpdateFloatValue(fieldOfView, ref CameraSettingsProcessor.values.mainFieldOfView);
			
			UpdateFloatValue(axisLookXGain, ref CameraSettingsProcessor.values.axisLookGainX);
			UpdateFloatValue(axisLookYGain, ref CameraSettingsProcessor.values.axisLookGainY);
			
			UpdateBoolValue(enableXAxis, ref CameraSettingsProcessor.values.axisLookEnabledX);
			UpdateBoolValue(enableYAxis, ref CameraSettingsProcessor.values.axisLookEnabledY);
			
			
			EndAction(true);
		}

		public void UpdateVector3Value(Vector3CameraValueGroup valueGroup, ref CameraSettingSingleValue<Vector3> targetProperty){
			switch (valueGroup.changeX) {
				case CameraSettingsToggle.NewValue:
					targetProperty.targetValue.x = valueGroup.newValue.x;
					break;
				case CameraSettingsToggle.ResetValue:
					targetProperty.targetValue.x = targetProperty.originalValue.x;
					break;
			}
			
			switch (valueGroup.changeY) {
				case CameraSettingsToggle.NewValue:
					targetProperty.targetValue.y = valueGroup.newValue.y;
					break;
				case CameraSettingsToggle.ResetValue:
					targetProperty.targetValue.y = targetProperty.originalValue.y;
					break;
			}
			
			switch (valueGroup.changeZ) {
				case CameraSettingsToggle.NewValue:
					targetProperty.targetValue.z = valueGroup.newValue.z;
					break;
				case CameraSettingsToggle.ResetValue:
					targetProperty.targetValue.z = targetProperty.originalValue.z;
					break;
			}
		}
		
		public void UpdateVector2Value(Vector2CameraValueGroup valueGroup, ref CameraSettingSingleValue<Vector2> targetProperty){
			switch (valueGroup.changeX) {
				case CameraSettingsToggle.NewValue:
					targetProperty.targetValue.x = valueGroup.newValue.x;
					break;
				case CameraSettingsToggle.ResetValue:
					targetProperty.targetValue.x = targetProperty.originalValue.x;
					break;
			}
			
			switch (valueGroup.changeY) {
				case CameraSettingsToggle.NewValue:
					targetProperty.targetValue.y = valueGroup.newValue.y;
					break;
				case CameraSettingsToggle.ResetValue:
					targetProperty.targetValue.y = targetProperty.originalValue.y;
					break;
			}
		}
		
		public void UpdateOrbitFollowValue(OrbitalFollowValueGroup valueGroup, ref CameraSettingSingleValue<float> targetHeight, ref CameraSettingSingleValue<float> targetRadius){
			switch (valueGroup.changeHeight) {
				case CameraSettingsToggle.NewValue:
					targetHeight.targetValue = valueGroup.height;
					break;
				case CameraSettingsToggle.ResetValue:
					targetHeight.targetValue = targetHeight.originalValue;
					break;
			}
			
			switch (valueGroup.changeRadius) {
				case CameraSettingsToggle.NewValue:
					targetRadius.targetValue = valueGroup.radius;
					break;
				case CameraSettingsToggle.ResetValue:
					targetRadius.targetValue = targetRadius.originalValue;
					break;
			}
		}
		
		public void UpdateBoolValue(BoolCameraValueGroup valueGroup, ref CameraSettingSingleValue<bool> targetProperty){
			switch (valueGroup.changeValue) {
				case CameraSettingsToggle.NewValue:
					targetProperty.targetValue = valueGroup.value;
					break;
				case CameraSettingsToggle.ResetValue:
					targetProperty.targetValue = targetProperty.originalValue;
					break;
			}
		}
		
		public void UpdateFloatValue(FloatCameraValueGroup valueGroup, ref CameraSettingSingleValue<float> targetProperty){
			switch (valueGroup.changeValue) {
				case CameraSettingsToggle.NewValue:
					targetProperty.targetValue = valueGroup.value;
					break;
				case CameraSettingsToggle.ResetValue:
					targetProperty.targetValue = targetProperty.originalValue;
					break;
			}
		}

		//Called once per frame while the action is active.
		protected override void OnUpdate() {
			
		}

		//Called when the task is disabled.
		protected override void OnStop() {
			
		}

		//Called when the task is paused.
		protected override void OnPause() {
			
		}
	}
}