using System;
using EntityStates;
using EntityStates.Loader;
using UnityEngine;
using UnityEngine.Networking;

namespace RoR2.Projectile;

[RequireComponent(typeof(ProjectileController))]
[RequireComponent(typeof(ProjectileSimple))]
[RequireComponent(typeof(EntityStateMachine))]
[RequireComponent(typeof(ProjectileStickOnImpact))]
public class ProjectileGrappleController : MonoBehaviour
{
	private struct OwnerInfo
	{
		public readonly GameObject gameObject;

		public readonly CharacterBody characterBody;

		public readonly CharacterMotor characterMotor;

		public readonly Rigidbody rigidbody;

		public readonly EntityStateMachine stateMachine;

		public readonly bool hasEffectiveAuthority;

		public OwnerInfo(GameObject ownerGameObject)
		{
			this = default(OwnerInfo);
			gameObject = ownerGameObject;
			if (!gameObject)
			{
				return;
			}
			characterBody = gameObject.GetComponent<CharacterBody>();
			characterMotor = gameObject.GetComponent<CharacterMotor>();
			rigidbody = gameObject.GetComponent<Rigidbody>();
			hasEffectiveAuthority = Util.HasEffectiveAuthority(gameObject);
			EntityStateMachine[] components = gameObject.GetComponents<EntityStateMachine>();
			for (int i = 0; i < components.Length; i++)
			{
				if (components[i].customName == "Hook")
				{
					stateMachine = components[i];
					break;
				}
			}
		}
	}

	private class BaseState : EntityStates.BaseState
	{
		protected ProjectileGrappleController grappleController;

		protected Vector3 aimOrigin;

		protected Vector3 position;

		protected bool ownerValid { get; private set; }

		protected ref OwnerInfo owner => ref grappleController.owner;

		private void UpdatePositions()
		{
			aimOrigin = grappleController.owner.characterBody.aimOrigin;
			position = base.transform.position + base.transform.up * grappleController.normalOffset;
		}

		public override void OnEnter()
		{
			base.OnEnter();
			grappleController = GetComponent<ProjectileGrappleController>();
			ownerValid = (bool)grappleController && (bool)grappleController.owner.gameObject;
			if (ownerValid)
			{
				UpdatePositions();
			}
		}

		public override void FixedUpdate()
		{
			base.FixedUpdate();
			if (ownerValid)
			{
				ownerValid &= grappleController.owner.gameObject;
				if (ownerValid)
				{
					UpdatePositions();
					FixedUpdateBehavior();
				}
			}
			if (NetworkServer.active && !ownerValid)
			{
				ownerValid = false;
				EntityState.Destroy(base.gameObject);
			}
		}

		protected virtual void FixedUpdateBehavior()
		{
			if (base.isAuthority && !grappleController.OwnerIsInFiringState())
			{
				outer.SetNextState(new ReturnState());
			}
		}

		protected Ray GetOwnerAimRay()
		{
			if (!owner.characterBody)
			{
				return default(Ray);
			}
			return owner.characterBody.inputBank.GetAimRay();
		}
	}

	private class FlyState : BaseState
	{
		private float duration;

		public override void OnEnter()
		{
			base.OnEnter();
			duration = grappleController.maxTravelDistance / grappleController.GetComponent<ProjectileSimple>().velocity;
		}

		protected override void FixedUpdateBehavior()
		{
			base.FixedUpdateBehavior();
			if (!base.isAuthority)
			{
				return;
			}
			if (grappleController.projectileStickOnImpactController.stuck)
			{
				EntityState entityState = null;
				if ((bool)grappleController.projectileStickOnImpactController.stuckBody)
				{
					Rigidbody component = grappleController.projectileStickOnImpactController.stuckBody.GetComponent<Rigidbody>();
					if ((bool)component && component.mass < grappleController.yankMassLimit)
					{
						CharacterBody component2 = component.GetComponent<CharacterBody>();
						if (!component2 || !component2.isPlayerControlled || component2.teamComponent.teamIndex != base.projectileController.teamFilter.teamIndex || FriendlyFireManager.ShouldDirectHitProceed(component2.healthComponent, base.projectileController.teamFilter.teamIndex))
						{
							entityState = new YankState();
						}
					}
				}
				if (entityState == null)
				{
					entityState = new GripState();
				}
				DeductOwnerStock();
				outer.SetNextState(entityState);
			}
			else if (duration <= base.fixedAge)
			{
				outer.SetNextState(new ReturnState());
			}
		}

		private void DeductOwnerStock()
		{
			if (!base.ownerValid || !base.owner.hasEffectiveAuthority)
			{
				return;
			}
			SkillLocator component = base.owner.gameObject.GetComponent<SkillLocator>();
			if ((bool)component)
			{
				GenericSkill secondary = component.secondary;
				if ((bool)secondary)
				{
					secondary.DeductStock(1);
				}
			}
		}
	}

	private class BaseGripState : BaseState
	{
		protected float currentDistance;

		public override void OnEnter()
		{
			base.OnEnter();
			currentDistance = Vector3.Distance(aimOrigin, position);
		}

		protected override void FixedUpdateBehavior()
		{
			base.FixedUpdateBehavior();
			currentDistance = Vector3.Distance(aimOrigin, position);
			if (base.isAuthority)
			{
				bool flag = !grappleController.projectileStickOnImpactController.stuck;
				bool flag2 = currentDistance < grappleController.nearBreakDistance;
				bool flag3 = !grappleController.OwnerIsInFiringState();
				if (!base.owner.stateMachine || !((base.owner.stateMachine.state as BaseSkillState)?.IsKeyDownAuthority() ?? false) || flag3 || flag2 || flag)
				{
					outer.SetNextState(new ReturnState());
				}
			}
		}
	}

	private class GripState : BaseGripState
	{
		private float lastDistance;

		private void DeductStockIfStruckNonPylon()
		{
			GameObject victim = grappleController.projectileStickOnImpactController.victim;
			if ((bool)victim)
			{
				GameObject gameObject = victim;
				EntityLocator component = gameObject.GetComponent<EntityLocator>();
				if ((bool)component)
				{
					gameObject = component.entity;
				}
				_ = (bool)gameObject.GetComponent<ProjectileController>();
			}
		}

		public override void OnEnter()
		{
			base.OnEnter();
			lastDistance = Vector3.Distance(aimOrigin, position);
			if (base.ownerValid)
			{
				grappleController.didStick = true;
				if ((bool)base.owner.characterMotor)
				{
					Vector3 direction = GetOwnerAimRay().direction;
					Vector3 velocity = base.owner.characterMotor.velocity;
					velocity = ((Vector3.Dot(velocity, direction) < 0f) ? Vector3.zero : Vector3.Project(velocity, direction));
					velocity += direction * grappleController.initialLookImpulse;
					velocity += base.owner.characterMotor.moveDirection * grappleController.initiallMoveImpulse;
					base.owner.characterMotor.velocity = velocity;
				}
			}
		}

		protected override void FixedUpdateBehavior()
		{
			base.FixedUpdateBehavior();
			float num = grappleController.acceleration;
			if (currentDistance > lastDistance)
			{
				num *= grappleController.escapeForceMultiplier;
			}
			lastDistance = currentDistance;
			if (base.owner.hasEffectiveAuthority && (bool)base.owner.characterMotor && (bool)base.owner.characterBody)
			{
				Ray ownerAimRay = GetOwnerAimRay();
				Vector3 normalized = (base.transform.position - base.owner.characterBody.aimOrigin).normalized;
				Vector3 vector = normalized * num;
				float time = Mathf.Clamp01(base.fixedAge / grappleController.lookAccelerationRampUpDuration);
				float num2 = grappleController.lookAccelerationRampUpCurve.Evaluate(time);
				float num3 = Util.Remap(Vector3.Dot(ownerAimRay.direction, normalized), -1f, 1f, 1f, 0f);
				vector += ownerAimRay.direction * (grappleController.lookAcceleration * num2 * num3);
				vector += base.owner.characterMotor.moveDirection * grappleController.moveAcceleration;
				base.owner.characterMotor.ApplyForce(vector * (base.owner.characterMotor.mass * Time.fixedDeltaTime), alwaysApply: true, disableAirControlUntilCollision: true);
			}
		}
	}

	private class YankState : BaseGripState
	{
		public static float yankSpeed;

		public static float delayBeforeYanking;

		public static float hoverTimeLimit = 0.5f;

		private CharacterBody stuckBody;

		public override void OnEnter()
		{
			base.OnEnter();
			stuckBody = grappleController.projectileStickOnImpactController.stuckBody;
		}

		protected override void FixedUpdateBehavior()
		{
			base.FixedUpdateBehavior();
			if (!stuckBody)
			{
				return;
			}
			if (Util.HasEffectiveAuthority(stuckBody.gameObject))
			{
				Vector3 vector = aimOrigin - position;
				IDisplacementReceiver component = stuckBody.GetComponent<IDisplacementReceiver>();
				if ((bool)(Component)component && base.fixedAge >= delayBeforeYanking)
				{
					component.AddDisplacement(vector * (yankSpeed * Time.fixedDeltaTime));
				}
			}
			if (base.owner.hasEffectiveAuthority && (bool)base.owner.characterMotor && base.fixedAge < hoverTimeLimit)
			{
				Vector3 velocity = base.owner.characterMotor.velocity;
				if (velocity.y < 0f)
				{
					velocity.y = 0f;
					base.owner.characterMotor.velocity = velocity;
				}
			}
		}
	}

	private class ReturnState : BaseState
	{
		private float returnSpeedAcceleration = 240f;

		private float returnSpeed;

		public override void OnEnter()
		{
			base.OnEnter();
			if (base.ownerValid)
			{
				returnSpeed = grappleController.projectileSimple.velocity;
				returnSpeedAcceleration = returnSpeed * 2f;
			}
			if (NetworkServer.active && (bool)grappleController)
			{
				grappleController.projectileStickOnImpactController.Detach();
				grappleController.projectileStickOnImpactController.ignoreCharacters = true;
				grappleController.projectileStickOnImpactController.ignoreWorld = true;
			}
			Collider component = GetComponent<Collider>();
			if ((bool)component)
			{
				component.enabled = false;
			}
		}

		protected override void FixedUpdateBehavior()
		{
			base.FixedUpdateBehavior();
			if (!base.rigidbody)
			{
				return;
			}
			returnSpeed += returnSpeedAcceleration * Time.fixedDeltaTime;
			base.rigidbody.velocity = (aimOrigin - position).normalized * returnSpeed;
			if (NetworkServer.active)
			{
				Vector3 endPosition = position + base.rigidbody.velocity * Time.fixedDeltaTime;
				if (HGMath.Overshoots(position, endPosition, aimOrigin))
				{
					EntityState.Destroy(base.gameObject);
				}
			}
		}
	}

	private ProjectileController projectileController;

	private ProjectileStickOnImpact projectileStickOnImpactController;

	private ProjectileSimple projectileSimple;

	public SerializableEntityStateType ownerHookStateType;

	public float acceleration;

	public float lookAcceleration = 4f;

	public float lookAccelerationRampUpDuration = 0.25f;

	public float initialLookImpulse = 5f;

	public float initiallMoveImpulse = 5f;

	public float moveAcceleration = 4f;

	public string enterSoundString;

	public string exitSoundString;

	public string hookDistanceRTPCstring;

	public float minHookDistancePitchModifier;

	public float maxHookDistancePitchModifier;

	public AnimationCurve lookAccelerationRampUpCurve;

	public Transform ropeEndTransform;

	public string muzzleStringOnBody = "MuzzleLeft";

	[Tooltip("The minimum distance the hook can be from the target before it detaches.")]
	public float nearBreakDistance;

	[Tooltip("The maximum distance this hook can travel.")]
	public float maxTravelDistance;

	public float escapeForceMultiplier = 2f;

	public float normalOffset = 1f;

	public float yankMassLimit;

	private Type resolvedOwnerHookStateType;

	private OwnerInfo owner;

	private bool didStick;

	private uint soundID;

	private void Awake()
	{
		projectileStickOnImpactController = GetComponent<ProjectileStickOnImpact>();
		projectileController = GetComponent<ProjectileController>();
		projectileSimple = GetComponent<ProjectileSimple>();
		resolvedOwnerHookStateType = ownerHookStateType.stateType;
		if ((bool)ropeEndTransform)
		{
			soundID = Util.PlaySound(enterSoundString, ropeEndTransform.gameObject);
		}
	}

	private void FixedUpdate()
	{
		if ((bool)ropeEndTransform)
		{
			float in_value = Util.Remap((ropeEndTransform.transform.position - base.transform.position).magnitude, minHookDistancePitchModifier, maxHookDistancePitchModifier, 0f, 100f);
			AkSoundEngine.SetRTPCValueByPlayingID(hookDistanceRTPCstring, in_value, soundID);
		}
	}

	private void AssignHookReferenceToBodyStateMachine()
	{
		if ((bool)owner.stateMachine && owner.stateMachine.state is FireHook fireHook)
		{
			fireHook.SetHookReference(base.gameObject);
		}
		Transform modelTransform = owner.gameObject.GetComponent<ModelLocator>().modelTransform;
		if (!modelTransform)
		{
			return;
		}
		ChildLocator component = modelTransform.GetComponent<ChildLocator>();
		if ((bool)component)
		{
			Transform transform = component.FindChild(muzzleStringOnBody);
			if ((bool)transform)
			{
				ropeEndTransform.SetParent(transform, worldPositionStays: false);
			}
		}
	}

	private void Start()
	{
		owner = new OwnerInfo(projectileController.owner);
		AssignHookReferenceToBodyStateMachine();
	}

	private void OnDestroy()
	{
		if ((bool)ropeEndTransform)
		{
			Util.PlaySound(exitSoundString, ropeEndTransform.gameObject);
			UnityEngine.Object.Destroy(ropeEndTransform.gameObject);
		}
		else
		{
			AkSoundEngine.StopPlayingID(soundID);
		}
	}

	private bool OwnerIsInFiringState()
	{
		if ((bool)owner.stateMachine)
		{
			return owner.stateMachine.state.GetType() == resolvedOwnerHookStateType;
		}
		return false;
	}
}
