using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GrapplingHook : MonoBehaviour
{
	[Header("Physics Settings")]

	[SerializeField]
	private Rigidbody2D climberRigidbody;

	[SerializeField]
	private Vector3Asset climberPositionAsset;

	[SerializeField]
	private Vector3Asset climberVelocityAsset;

	[SerializeField]
	private LayerMask hookable;

	[Header("Rope Settings")]

	[SerializeField]
	private float climbSpeed = 1f;

	[SerializeField]
	private float initialForce = 1f;

	[SerializeField]
	private bool enforceRopeLength = true;

	private const float RAYCAST_DISTANCE = 500f;

	private bool hookIsActive;

	Vector2 hookPosition;

	private void OnEnable()
	{
		EventManager.StartListening(EventNames.TOUCHINPUT_DOWN, NewHook);
		EventManager.StartListening(EventNames.TOUCHINPUT_UP, ReleaseHook);
	}

	private void OnDisable()
	{
		EventManager.StopListening(EventNames.TOUCHINPUT_DOWN, NewHook);
		EventManager.StopListening(EventNames.TOUCHINPUT_UP, ReleaseHook);
	}

	private void FixedUpdate()
	{
		if(hookIsActive)
		{
			SwingPhysics();
		}

		climberPositionAsset.SetVector(GetClimberPosition());
		climberVelocityAsset.SetVector(GetClimberVelocity());
	}

	private void SwingPhysics()
	{
		climberRigidbody.AddForce(GetClimbForce(hookPosition), ForceMode2D.Force);
		if(enforceRopeLength)
		{
			climberRigidbody.velocity = SwingVelocity(hookPosition);
		}
	}

	private Vector2 GetClimbForce(Vector2 hookPosition)
	{
		return GetRopeVector(hookPosition).normalized * climbSpeed;
	}

	private Vector2 GetInitialForce(Vector2 hookPosition)
	{
		return GetRopeVector(hookPosition).normalized * initialForce;
	}

	private void NewHook(Vector3 inputPosition)
	{
		RaycastHit2D hit = GetHookRaycastHit(inputPosition);

		if(hit)
		{
			hookPosition = hit.point;
			hookIsActive = true;
			AddInitialForce();
			EventManager.TriggerEvent(EventNames.UPDATE_HOOK_POSITION, hookPosition);
		}
	}

	private void AddInitialForce()
	{
		climberRigidbody.AddForce(GetInitialForce(hookPosition), ForceMode2D.Impulse);
	}

	private void ReleaseHook()
	{
		hookIsActive = false;
		EventManager.TriggerEvent(EventNames.RELEASE_HOOK);
	}

	private RaycastHit2D GetHookRaycastHit(Vector2 inputPosition)
	{
		Vector2 aim = GetAim(inputPosition);
		return Physics2D.Raycast(GetClimberPosition(), aim, RAYCAST_DISTANCE, hookable);
	}

	private Vector2 GetAim(Vector3 newPosition)
	{
		return ((Vector2)newPosition - GetClimberPosition()).normalized;
	}

	private Vector2 GetHookPosition(Vector3 aim)
	{
		RaycastHit2D hitCollider = Physics2D.Raycast(GetClimberPosition(), aim, RAYCAST_DISTANCE, hookable);
		return hitCollider.point;
	}

	private Vector2 GetClimberPosition()
	{
		return climberRigidbody.position;
	}

	private Vector2 SwingVelocity(Vector2 hookPosition)
	{
		Vector2 normalizedRopeVector = GetRopeVector(hookPosition).normalized;
		Vector2 climberVelocity = GetClimberVelocity();
		Vector2 normalizedClimberVelocity = climberVelocity.normalized;

		float dotRope = Vector2.Dot(normalizedRopeVector, normalizedClimberVelocity);

		if(dotRope < 0f)
		{
			Vector2 crossRope = Vector3.Cross(Vector3.forward, normalizedRopeVector).normalized;
			float dotCross = Vector3.Dot(normalizedClimberVelocity, crossRope);
			return crossRope * climberVelocity.magnitude * dotCross;
		}

		return climberVelocity;
	}

	private Vector2 GetRopeVector(Vector2 hookPosition)
	{
		return hookPosition - GetClimberPosition();
	}

	private Vector2 GetClimberVelocity()
	{
		return climberRigidbody.velocity;
	}
}
