using System.Collections.Generic;
using System.Runtime.InteropServices.JavaScript;
using Godot;
using RustyOptions;

namespace PolarBears.PlayerControllerAddon;

public partial class PlayerController : CharacterBody3D
{
	// User API to important child nodes.
	public HeadSystem       HeadSystem;
	public Bobbing          Bobbing;
	public FieldOfView      FieldOfView;
	public Stamina          Stamina;
	public StairsSystem     StairsSystem;
	public MantleSystem     MantleSystem;
	public DashSystem       DashSystem;
	public CapsuleCollider  CapsuleCollider;
	public Gravity          Gravity;
	public HealthSystem     HealthSystem;
	public MoveSystem       MoveSystem;
	public TweenQueueSystem TweenQueueSystem;

	private bool _movementEnabled = true;

	private bool _shouldMantle = false;
	private Vector3 _dashLocation = Vector3.Zero;
	private Vector3 _mantleLocation = Vector3.Zero;

	private float _lastFrameWasOnFloor = -Mathf.Inf;

	private const int NumOfHeadCollisionDetectors = 4;
	private RayCast3D[] _headCollisionDetectors;
	
	private Vector3 _inputMove = Vector3.Zero;
	private float _inputRotateY = 0.0f;
	private float _inputRotateFloorplane = 0.0f;

	private bool _isAiming = false;
	private bool _dashCanceled = false;

	public void OnInputMove(Vector3 value)
	{
		_inputMove = value;
	}
	
	public void OnInputRotateY(float value)
	{
		_inputRotateY = value;
	}
	
	public void OnInputRotateFloorplane(float value)
	{
		_inputRotateFloorplane = value;
	}

	public void OnInputAimPressed()
	{
		if (_dashCanceled)
			return;
		
		DashSystem.PrepareDash();
	}
	public void OnInputAimReleased()
	{
		if (!_dashCanceled)
			DashSystem.Dash();
		_dashCanceled = false;
	}
	public void OnInputAimCanceled()
	{
		_dashCanceled = true;
		DashSystem.CancelDash();
	}

	public void OnInputJumpPressed()
	{
		bool doesCapsuleHaveCrouchingHeight = CapsuleCollider.IsCrouchingHeight();
		bool isPlayerDead = HealthSystem.IsDead();
		
		if (!doesCapsuleHaveCrouchingHeight && !isPlayerDead)
			MoveSystem.Jump(IsOnFloor());
	}
	

	public override void _Ready()
	{
		HeadSystem = GetNode<HeadSystem>("HeadSystem");
		HeadSystem.Init();
		
		_headCollisionDetectors = new RayCast3D[NumOfHeadCollisionDetectors];

		for (int i = 0; i < NumOfHeadCollisionDetectors; i++)
		{
			_headCollisionDetectors[i] = GetNode<RayCast3D>(
				"HeadCollisionDetectors/HeadCollisionDetector" + i);
		}

		// Getting dependencies of the components(In godot we manage this from upwards to downwards not vice versa)
		Camera3D camera = GetNode<Camera3D>("HeadSystem/CameraSmooth/Camera3D");

		RayCast3D stairsBelowRayCast3D = GetNode<RayCast3D>("StairsBelowRayCast3D");
		RayCast3D stairsAheadRayCast3D = GetNode<RayCast3D>("StairsAheadRayCast3D");

		Node3D cameraSmooth = GetNode<Node3D>("HeadSystem/CameraSmooth");
		
		// Getting universal setting from GODOT editor to be in sync
		float gravitySetting = (float)ProjectSettings.GetSetting("physics/3d/default_gravity");

		ColorRect vignetteRect = GetNode<ColorRect>(
			"HeadSystem/CameraSmooth/Camera3D/CLVignette(Layer_1)/HealthVignetteRect");
		
		ColorRect distortionRect = GetNode<ColorRect>(
			"HeadSystem/CameraSmooth/Camera3D/CLDistortion(Layer_2)/HealthDistortionRect");

		ColorRect blurRect = GetNode<ColorRect>("HeadSystem/CameraSmooth/Camera3D/CLBlur(Layer_2)/BlurRect");
		
		Node3D mapNode = GetTree().Root.FindChild("Map", true, false) as Node3D;
		
		// Getting components

		Bobbing = GetNode<Bobbing>("Bobbing");
		Bobbing.Init(camera);

		FieldOfView = GetNode<FieldOfView>("FieldOfView");
		FieldOfView.Init(camera);

		CapsuleCollider = GetNode<CapsuleCollider>("CapsuleCollider");

		Gravity = GetNode<Gravity>("Gravity");
		Gravity.Init(gravitySetting);

		MantleSystem = GetNode<MantleSystem>("MantleSystem");
		MantleSystem.Init(HeadSystem);
		
		TweenQueueSystem = GetNode<TweenQueueSystem>("TweenQueueSystem");
		TweenQueueSystem.Init(this);
		
		MoveSystem = GetNode<MoveSystem>("MoveSystem");
		var moveSystemParams = new MoveSystem.MoveSystemParameters(this, Gravity, MantleSystem, TweenQueueSystem,
			HeadSystem, CapsuleCollider);
		MoveSystem.Init(moveSystemParams);

		Stamina = GetNode<Stamina>("Stamina");
		Stamina.SetSpeeds(MoveSystem.WalkSpeed, MoveSystem.SprintSpeed);

		StairsSystem = GetNode<StairsSystem>("StairsSystem");
		StairsSystem.Init(stairsBelowRayCast3D, stairsAheadRayCast3D, cameraSmooth);
		
		DashSystem = GetNode<DashSystem>("DashSystem");
		DashSystem.Init(HeadSystem, camera, TweenQueueSystem);

		HealthSystem = GetNode<HealthSystem>("HealthSystem");
		
		HealthSystem.HealthSystemInitParams healthSystemParams = new HealthSystem.HealthSystemInitParams()
		{
			Gravity = Gravity,
			Parent = this,
			Camera = camera,
			Head =  HeadSystem,
			VignetteRect = vignetteRect,
			DistortionRect = distortionRect,
			BlurRect = blurRect,
		};
		
		HealthSystem.Init(healthSystemParams);
	}

	public override void _PhysicsProcess(double delta)
	{
		TweenQueueSystem.ProcessTweens();
		
		var isPlayerDead = HealthSystem.IsDead();
		var isHeadTouchingCeiling = IsHeadTouchingCeiling();
		
		var moveAroundParams = new MoveSystem.MoveAroundParameters(
			delta, 
			_inputMove, 
			isOnFloorCustom(), 
			isPlayerDead,
			isHeadTouchingCeiling);
		MoveSystem.MoveAround(moveAroundParams);
		
		Vector2 inputLookDir = new Vector2(_inputRotateY, _inputRotateFloorplane);
		HeadSystem.LookAround(inputLookDir);
		
		Bobbing.CameraBobbingParams cameraBobbingParams = new Bobbing.CameraBobbingParams
		{
			Delta = (float)delta,
			IsOnFloorCustom = isOnFloorCustom(),
			Velocity = Velocity
		};
		
		Bobbing.PerformCameraBobbing(cameraBobbingParams);

		FieldOfView.FovParameters fovParams = new FieldOfView.FovParameters
		{
			IsCrouchingHeight = CapsuleCollider.IsCrouchingHeight(),
			Delta = (float)delta,
			SprintSpeed = MoveSystem.SprintSpeed,
			Velocity = Velocity
		};
		
		FieldOfView.PerformFovAdjustment(fovParams);

		StairsSystem.UpStairsCheckParams upStairsCheckParams = new StairsSystem.UpStairsCheckParams
		{
			IsOnFloorCustom = isOnFloorCustom(),
			IsCapsuleHeightLessThanNormal = CapsuleCollider.IsCapsuleHeightLessThanNormal(),
			CurrentSpeedGreaterThanWalkSpeed = MoveSystem._currentSpeed > MoveSystem.WalkSpeed,
			IsCrouchingHeight = CapsuleCollider.IsCrouchingHeight(),
			Delta = (float)delta,
			FloorMaxAngle = FloorMaxAngle,
			GlobalPositionFromDriver = GlobalPosition,
			Velocity = Velocity,
			GlobalTransformFromDriver = GlobalTransform,
			Rid = GetRid()
		};
		
		// TODO: SnapUpStairsCheck influences the ability of player to crouch because of `stepHeightY <= 0.01` part
		// Ideally, it should not. SnapUpStairsCheck and SnapDownStairsCheck should be called, when player is actually
		// on the stairs

		StairsSystem.UpStairsCheckResult upStairsCheckResult = StairsSystem.SnapUpStairsCheck(upStairsCheckParams);

		if (upStairsCheckResult.UpdateRequired)
		{
			upStairsCheckResult.Update(this);
		}
		else
		{
			MoveAndSlide();
		
			StairsSystem.DownStairsCheckParams downStairsCheckParams = new StairsSystem.DownStairsCheckParams
			{
				IsOnFloor = IsOnFloor(),  // TODO: replace on IsOnFloor Custom
				IsCrouchingHeight = CapsuleCollider.IsCrouchingHeight(),
				LastFrameWasOnFloor = _lastFrameWasOnFloor,
				CapsuleDefaultHeight = CapsuleCollider.GetDefaultHeight(),
				CurrentCapsuleHeight = CapsuleCollider.GetCurrentHeight(),
				FloorMaxAngle = FloorMaxAngle,
				VelocityY = Velocity.Y,
				GlobalTransformFromDriver = GlobalTransform,
				Rid = GetRid()
			};
		
			StairsSystem.DownStairsCheckResult downStairsCheckResult = StairsSystem.SnapDownStairsCheck(
				downStairsCheckParams);

			if (downStairsCheckResult.UpdateIsRequired)
			{
				downStairsCheckResult.Update(this);
			}
		}
		
		StairsSystem.SlideCameraParams slideCameraParams = new StairsSystem.SlideCameraParams
		{
			IsCapsuleHeightLessThanNormal = CapsuleCollider.IsCapsuleHeightLessThanNormal(),
			CurrentSpeedGreaterThanWalkSpeed = MoveSystem._currentSpeed > MoveSystem.WalkSpeed,
			BetweenCrouchingAndNormalHeight  = CapsuleCollider.IsBetweenCrouchingAndNormalHeight(),
			Delta = (float)delta
		};
		
		StairsSystem.SlideCameraSmoothBackToOrigin(slideCameraParams);
	}

	private bool IsHeadTouchingCeiling()
	{
		for (int i = 0; i < NumOfHeadCollisionDetectors; i++)
		{
			if (_headCollisionDetectors[i].IsColliding())
			{
				return true;
			}
		}

		return false;
	}
	 
	private bool isOnFloorCustom()
	{
		return IsOnFloor() || StairsSystem.WasSnappedToStairsLastFrame();
	}
}
