using System;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.SceneManagement;
using static AffectingForcesManager;

public class PlayerController : MonoBehaviour
{
	[SerializeField] public int playerId = 1;
	[SerializeField] public string playerName = "gray";
	// Private variables
	[SerializeField] private CameraOperator cameraOperator;
	[SerializeField] private float thrustAcceleration = 400;
	[SerializeField] private float steerVelocity = 30;
	[SerializeField] private float normalMaxVelocity = 10;
	[SerializeField] private float absolutMaxVelocity = 20;
	[SerializeField] private float antiDriftAmount = 20;
	[SerializeField] private float minAntiDriftFactor = 0.2f;
	[SerializeField] private float normalDrag = 0.1f;
	[SerializeField] private float maximumDrag = 0.3f;
	[SerializeField] private float torqueDrag = 0.2f;
	// The time in Seconds the player is allowed to boost for
	[SerializeField] private float maxBoostCapacity = 2f;
	[SerializeField] private float minBoostCapacity = 0.3f * 2f;
	[SerializeField] private float boostMagnitude = 1.5f;
	[SerializeField] private float outsideBoostRate = 0.5f;
	[SerializeField, Range(0, 1)] private float boostAntiGravityFactor = 0.2f;
	[SerializeField] private float stunLooseControlFactor = 0.1f;
	[SerializeField] private float tackleCriticalStunTime = 0.6f;
	[SerializeField] private float tackleBodyStunTime = 0.3f;
	[SerializeField, Range(0, 300)] private float criticalTacklePowerFactor = 10f;
	[SerializeField, Range(0, 300)] private float normalTacklePowerFactor = 10f;
	[SerializeField] private BoostCapacityUI boostUI;
	private AffectingForcesManager forceManager;
	public PlayerInput playerInput;
	private Rigidbody body;
	private Vector3 currentGravity = new Vector3();
	// Saves the current input value for thrust
	private float currentThrustInput = 0;
	private float currentBoostInput = 0;
	private float boostCapacity;
	// Saves the current input for steering
	private float currentSteerInput = 0;
	private bool canBoost = true;
	private TackleDetection[] tackleDetectors;
	private bool isCriticalTackle = false;
	private bool isTackled = false;
	private float tackledTime = 0f;
	// Current Zone the player occupies
	private Zone zone = Zone.NimbleZone;
	public int instanceID;

	void Awake()
	{
		if (forceManager == null)
		{
			forceManager = GameObject.FindGameObjectWithTag("ForceManager").
				GetComponent<AffectingForcesManager>();
		}
		body = GetComponent<Rigidbody>();
		playerInput = GetComponent<PlayerInput>();
	}

	// Start is called before the first frame update
	void Start()
	{
		instanceID = gameObject.GetInstanceID();
		boostCapacity = maxBoostCapacity;
		boostUI.SetMinBoostRatio(minBoostCapacity / maxBoostCapacity);
		GameManager.GM.RegisterPlayer(this);
		cameraOperator.AddPlayer(gameObject);

		tackleDetectors = GetComponentsInChildren<TackleDetection>();
		foreach (TackleDetection td in tackleDetectors)
		{
			td.TackleResponse.AddListener(StartTackleResponse);
		}
	}

	void OnDestroy()
	{
		foreach (TackleDetection td in tackleDetectors)
		{
			td.TackleResponse.RemoveAllListeners();
		}
	}

	// Update is called once per frame
	void FixedUpdate()
	{
		if (GameManager.GM.currentState == GameState.Starting)
		{
			return;
		}
		zone = forceManager.GetZoneOfInstance(instanceID);
		//BoostStateUpdate(Time.deltaTime);
		// Rotate the vehicle with the current steer velocity
		// Calculate the magnitude of the acceleration with the current thrust
		UpdateMovement();
		// TODO Always full thrust when boosting?
		BoostStateUpdate(Time.deltaTime);
		UpdateTackleResponse(isCriticalTackle);
	}

	void UpdateMovement()
	{

		//Debug.Log("inupdatemove " + currentThrustInput);
		// Player rotation is always possible and same speed
		transform.Rotate(0, 0, -steerVelocity * currentSteerInput * Time.deltaTime);

		// Get and apply the current Gravity
		currentGravity = forceManager.GetGravityForInstance(instanceID)(transform.position);
		body.AddForce(currentGravity, ForceMode.Acceleration);

		float stunFactor = isCriticalTackle ? stunLooseControlFactor : 1f;

		currentThrustInput = IsBoosting() ? 1f : currentThrustInput;
		Vector3 acceleration = thrustAcceleration * currentThrustInput * Time.deltaTime
			* Vector3.up * stunFactor;

		Vector3 currentVelocity = body.velocity;

		Vector3 boostedAcceleration = BoostAcceleration(acceleration, currentGravity);

		if (!isCriticalTackle)
		{
			// Add drag
			if (zone == Zone.NimbleZone)
			{
				Vector3 dragDecceleration = DragDecceleration(currentVelocity, zone);
				body.AddForce(dragDecceleration, ForceMode.Acceleration);

				if (!isTackled)
				{
					// Add anti drift acceleration
					Vector3 driftDampeningAcceleration = DriftDampeningAcceleration(currentVelocity, zone);
					body.AddForce(driftDampeningAcceleration, ForceMode.Acceleration);
				}
			}

			if (currentVelocity.magnitude <= normalMaxVelocity || IsBoosting() || zone != Zone.NimbleZone)
			{
				body.AddRelativeForce(boostedAcceleration, ForceMode.Acceleration);
			}
			if (currentVelocity.magnitude >= absolutMaxVelocity && zone == Zone.NimbleZone)
			{
				body.velocity = body.velocity.normalized * absolutMaxVelocity;
			}
		}

		// Default torque drag
		body.AddTorque(body.angularVelocity * -torqueDrag, ForceMode.Acceleration);

		Debug.DrawRay(transform.position, transform.up * currentVelocity.magnitude * 0.5f, Color.black);
	}

	Vector3 DriftDampeningAcceleration(Vector3 currentVelocity, Zone zone)
	{
		Vector3 antiDriftVelocity;
		float antiDriftFactor;
		// Cancel out inertia/drifting
		Vector3 rotation = transform.rotation.eulerAngles;
		Vector3 driftVelocity = currentVelocity - Vector3.Project(currentVelocity, rotation);

		antiDriftVelocity = Vector3.Reflect(-driftVelocity, transform.up) - driftVelocity;
		antiDriftFactor = Mathf.InverseLerp(absolutMaxVelocity, normalMaxVelocity,
			currentVelocity.magnitude);

		antiDriftFactor = Mathf.Max(antiDriftFactor, minAntiDriftFactor);
		Debug.DrawRay(transform.position, driftVelocity.normalized * 5, Color.red);
		Debug.DrawRay(transform.position, antiDriftVelocity.normalized * 5, Color.green);
		return antiDriftVelocity * antiDriftAmount * antiDriftFactor;
	}

	Vector3 DragDecceleration(Vector3 currentVelocity, Zone zone)
	{
		Vector3 drag = new Vector3();
		bool isBoosting = currentBoostInput > 0 && canBoost == true;
		float minDragFactor = Mathf.InverseLerp(absolutMaxVelocity, normalMaxVelocity,
			currentVelocity.magnitude);

		float normalDragFactor = Mathf.InverseLerp(normalMaxVelocity, 0, currentVelocity.magnitude);
		if (!isBoosting && zone == Zone.NimbleZone)
		{
			drag -= currentVelocity.normalized * normalDrag;
		}
		if (currentVelocity.magnitude >= normalMaxVelocity && zone == Zone.NimbleZone)
		{
			drag -= currentVelocity.normalized * maximumDrag;
		}
		return drag;
	}

	bool IsBoosting()
	{
		return currentBoostInput > 0 && canBoost;
	}

	Vector3 BoostAcceleration(Vector3 acceleration, Vector3 currentGravity)
	{
		if (IsBoosting())
		{
			acceleration *= boostMagnitude;
			acceleration -= currentGravity * boostAntiGravityFactor;
		}
		return acceleration;
	}

	void BoostStateUpdate(float deltaTime)
	{
		boostUI.UpdateFill(Math.Min(boostCapacity / maxBoostCapacity, 1));
		if (IsBoosting() && currentThrustInput != 0)
		{
			// Debug.Log("Boost Kapazität wird verbraucht: " + boostCapacity);
			boostCapacity -= deltaTime;
		}
		if (canBoost && zone == Zone.OutsideZone)
		{
			// Debug.Log("Boost Kapazität wird passiv verbraucht: " + boostCapacity);
			boostCapacity -= deltaTime * outsideBoostRate;
		}
		if (boostCapacity <= 0)
		{
			canBoost = false;
			// Debug.Log("Boost aufgebraucht");
		}
		// TODO this will be spam abused
		if ((currentBoostInput <= 0 || currentThrustInput == 0 || !canBoost)
			&& zone == Zone.NimbleZone
			&& boostCapacity <= maxBoostCapacity)
		{
			// Debug.Log("Boost wird aufgeladen: " + boostCapacity);
			boostCapacity += deltaTime;
		}
		if (canBoost == false && boostCapacity >= minBoostCapacity)
		{
			canBoost = true;
			// Debug.Log("Boost verfügbar");
		}
	}

	void UpdateTackleResponse(bool gotTackled = false)
	{
		if (gotTackled && !isTackled)
		{
			isTackled = true;
			tackledTime = isCriticalTackle ? tackleCriticalStunTime : tackleBodyStunTime;
			return;
		}
		tackledTime -= Time.deltaTime;
		if (tackledTime <= 0)
		{
			isTackled = false;
			isCriticalTackle = false;
			tackledTime = 0;
		}
	}

	public void OnThrust(InputAction.CallbackContext context)
	{
		currentThrustInput = context.ReadValue<float>();
	}

	public void OnSteer(InputAction.CallbackContext context)
	{
		currentSteerInput = context.ReadValue<float>();
	}

	public void OnBoost(InputAction.CallbackContext context)
	{
		currentBoostInput = context.ReadValue<float>();
	}

	public void OnResetScene(InputAction.CallbackContext context)
	{
		if (context.phase == InputActionPhase.Performed)
		{
			Debug.Log("reload triggered");
			Destroy(GameManager.GM);
			string currentSceneName = SceneManager.GetActiveScene().name;
			SceneManager.LoadScene(currentSceneName);
		}
	}
	void StartTackleResponse(TackleKind tackleKind, Collider collider)
	{
		float tacklePowerFactor = criticalTacklePowerFactor;
		if (tackleKind == TackleKind.Critical)
		{
			isCriticalTackle = true;
		}
		else
		{
			isCriticalTackle = false;
			tacklePowerFactor = normalTacklePowerFactor;
		}
		Vector3 colliderVelocity = collider.attachedRigidbody.velocity;
		Vector3 tackleDirection = body.transform.position - collider.transform.position;
		body.AddForce(colliderVelocity.magnitude * tackleDirection * tacklePowerFactor,
			ForceMode.Acceleration);
		UpdateTackleResponse(true);
	}

}
