using Sandbox;
using System;
using System.Collections.Generic;

namespace VeloX;

public partial class VeloXCar
{
	[Property, Feature( "Engine" )] public EngineStream Stream { get; set; }
	public EngineStreamPlayer StreamPlayer { get; set; }

	[Property, Feature( "Engine" )] public float MinRPM { get; set; } = 800;
	[Property, Feature( "Engine" )] public float MaxRPM { get; set; } = 7000;
	[Property, Feature( "Engine" ), Range( -1, 1 )]
	public float PowerDistribution
	{
		get => powerDistribution; set
		{
			powerDistribution = value;
			UpdatePowerDistribution();
		}
	}
	[Property, Feature( "Engine" )] public float FlyWheelMass { get; set; } = 80f;
	[Property, Feature( "Engine" )] public float FlyWheelRadius { get; set; } = 0.5f;
	[Property, Feature( "Engine" )] public float FlywheelFriction { get; set; } = -6000;
	[Property, Feature( "Engine" )] public float FlywheelTorque { get; set; } = 20000;
	[Property, Feature( "Engine" )] public float EngineBrakeTorque { get; set; } = 2000;
	[Property, Feature( "Engine" )]
	public Dictionary<int, float> Gears { get; set; } = new()
	{
		[-1] = 2.5f,
		[0] = 0f,
		[1] = 2.8f,
		[2] = 1.7f,
		[3] = 1.2f,
		[4] = 0.9f,
		[5] = 0.75f,
		[6] = 0.7f
	};
	[Property, Feature( "Engine" )] public float DifferentialRatio { get; set; } = 1f;
	[Property, Feature( "Engine" ), Range( 0, 1 )] public float TransmissionEfficiency { get; set; } = 0.8f;
	[Property, Feature( "Engine" )] private float MinRPMTorque { get; set; } = 5000f;
	[Property, Feature( "Engine" )] private float MaxRPMTorque { get; set; } = 8000f;
	[Sync] public int Gear { get; set; } = 0;
	[Sync] public float Clutch { get; set; } = 1;
	[Sync( SyncFlags.Interpolate )] public float EngineRPM { get; set; }
	public float RPMPercent => (EngineRPM - MinRPM) / MaxRPM;

	private const float TAU = MathF.Tau;

	private int MinGear { get; set; }
	private int MaxGear { get; set; }

	[Sync] public bool IsRedlining { get; private set; }

	private float flywheelVelocity;
	private TimeUntil switchCD = 0;
	private float groundedCount;
	private float burnout;

	private float frontBrake;
	private float rearBrake;

	private float availableFrontTorque;
	private float availableRearTorque;

	private float avgSideSlip;
	private float avgPoweredRPM;
	private float avgForwardSlip;

	private float inputThrottle, inputBrake;
	private bool inputHandbrake;
	private float transmissionRPM;
	private float powerDistribution;

	public float FlywheelRPM
	{
		get => flywheelVelocity * 60 / TAU;
		set
		{
			flywheelVelocity = value * TAU / 60; EngineRPM = value;
		}
	}
	private void UpdateGearList()
	{
		int minGear = 0;
		int maxGear = 0;

		foreach ( var (gear, ratio) in Gears )
		{

			if ( gear < minGear )
				minGear = gear;

			if ( gear > maxGear )
				maxGear = gear;

			if ( minGear != 0 || maxGear != 0 )
			{
				SwitchGear( 0, false );
			}
		}

		MinGear = minGear;
		MaxGear = maxGear;

	}


	public void SwitchGear( int index, bool cooldown = true )
	{
		if ( Gear == index ) return;
		index = Math.Clamp( index, MinGear, MaxGear );

		if ( index == 0 || !cooldown )
			switchCD = 0;
		else
			switchCD = 0.3f;
		Clutch = 1;
		Gear = index;
	}

	public float TransmissionToEngineRPM( int gear ) => avgPoweredRPM * Gears[gear] * DifferentialRatio * 60 / TAU;
	public float GetTransmissionMaxRPM( int gear ) => FlywheelRPM / Gears[gear] / DifferentialRatio;

	private void UpdatePowerDistribution()
	{
		if ( Wheels is null ) return;
		int frontCount = 0, rearCount = 0;

		foreach ( var wheel in Wheels )
		{
			if ( wheel.IsFront )
				frontCount++;
			else
				rearCount++;

		}

		float frontDistribution = 0.5f + PowerDistribution * 0.5f;

		float rearDistribution = 1 - frontDistribution;

		frontDistribution /= frontCount;
		rearDistribution /= rearCount;

		foreach ( var wheel in Wheels )
			if ( wheel.IsFront )
				wheel.DistributionFactor = frontDistribution;
			else
				wheel.DistributionFactor = rearDistribution;
	}

	private void EngineAccelerate( float torque, float dt )
	{
		var inertia = 0.5f * FlyWheelMass * FlyWheelRadius * FlyWheelRadius;
		var angularAcceleration = torque / inertia;

		flywheelVelocity += angularAcceleration * dt;
	}

	private float GetTransmissionTorque( int gear, float minTorque, float maxTorque )
	{
		var torque = FlywheelRPM.Remap( MinRPM, MaxRPM, minTorque, maxTorque, true );
		torque *= (1 - Clutch);
		torque = torque * Gears[gear] * DifferentialRatio * TransmissionEfficiency;

		return gear == -1 ? -torque : torque;
	}

	private void AutoGearSwitch()
	{
		if ( ForwardSpeed < 100 && Input.Down( "Backward" ) )
		{
			SwitchGear( -1, false );
			return;
		}

		var currentGear = Gear;

		if ( currentGear < 0 && ForwardSpeed < -100 )
			return;


		if ( Math.Abs( avgForwardSlip ) > 10 )
			return;

		var gear = Math.Clamp( currentGear, 1, MaxGear );

		float minRPM = MinRPM, maxRPM = MaxRPM;


		maxRPM *= 0.98f;

		float gearRPM;


		for ( int i = 1; i <= MaxGear; i++ )
		{
			gearRPM = TransmissionToEngineRPM( i );

			if ( (i == 1 && gearRPM < minRPM) || (gearRPM > minRPM && gearRPM < maxRPM) )
			{
				gear = i;
				break;
			}
		}

		var threshold = minRPM + (maxRPM - minRPM) * (0.5 - Throttle * 0.3);
		if ( gear < currentGear && gear > currentGear - 2 && EngineRPM > threshold )
			return;

		SwitchGear( gear );
	}

	private float EngineClutch( float dt )
	{
		if ( !switchCD )
		{
			inputThrottle = 0;

			return 0;
		}

		if ( inputHandbrake )
			return 1;

		var absForwardSpeed = Math.Abs( ForwardSpeed );

		if ( groundedCount < 1 && absForwardSpeed > 30 )
			return 1;

		if ( ForwardSpeed < -50 && inputBrake > 0 && Gear < 0 )
			return 1;

		if ( absForwardSpeed > 200 )
			return 0;

		return inputThrottle > 0.1f ? 0 : 1;
	}

	private void EngineThink( float dt )
	{
		inputThrottle = Input.Down( "Forward" ) ? 1 : 0;
		inputBrake = Input.Down( "Backward" ) ? 1 : 0;
		inputHandbrake = Input.Down( "Jump" );
		if ( burnout > 0 )
		{
			SwitchGear( 1, false );
			if ( inputThrottle < 0.1f || inputBrake < 0.1f )
				burnout = 0;
		}
		else
			AutoGearSwitch();

		if ( Gear < 0 )
			(inputBrake, inputThrottle) = (inputThrottle, inputBrake);

		var rpm = FlywheelRPM;

		var clutch = EngineClutch( dt );

		if ( inputThrottle > 0.1 && inputBrake > 0.1 && Math.Abs( ForwardSpeed ) < 50 )
		{
			burnout = MathX.Approach( burnout, 1, dt * 2 );
			Clutch = 0;

		}
		else if ( inputHandbrake )
		{
			frontBrake = 0f;
			rearBrake = 0.5f;
			Clutch = 1;
			clutch = 1;
		}
		else
		{
			if ( (Gear == -1 || Gear == 1) && inputThrottle < 0.05f && inputBrake < 0.1f && groundedCount > 1 && rpm < MinRPM * 1.2f )
				inputBrake = 0.2f;

			frontBrake = inputBrake * 0.5f;
			rearBrake = inputBrake * 0.5f;
		}

		clutch = MathX.Approach( Clutch, clutch, dt * ((Gear < 2 && inputThrottle > 0.1f) ? 6 : 2) );

		Clutch = clutch;

		var isRedlining = false;
		transmissionRPM = 0;

		if ( Gear != 0 )
		{
			transmissionRPM = TransmissionToEngineRPM( Gear );
			transmissionRPM = Gear < 0 ? -transmissionRPM : transmissionRPM;
			rpm = (rpm * clutch) + (MathF.Max( 0, transmissionRPM ) * (1 - clutch));
		}
		var throttle = Throttle;

		var gearTorque = GetTransmissionTorque( Gear, MinRPMTorque, MaxRPMTorque );

		var availableTorque = gearTorque * throttle;

		if ( transmissionRPM < 0 )
		{
			availableTorque += gearTorque * 2;
		}
		else
		{
			var engineBrakeTorque = GetTransmissionTorque( Gear, EngineBrakeTorque, EngineBrakeTorque );

			availableTorque -= engineBrakeTorque * (1 - throttle) * 0.5f;
		}

		var maxRPM = MaxRPM;

		if ( rpm < MinRPM )
		{
			rpm = MinRPM;
		}
		else if ( rpm > maxRPM )
		{

			if ( rpm > maxRPM * 1.2f )
				availableTorque = 0;

			rpm = maxRPM;

			if ( Gear != MaxGear || groundedCount < Wheels.Count )
				isRedlining = true;
		}

		FlywheelRPM = Math.Clamp( rpm, 0, maxRPM );

		if ( burnout > 0 )
			availableTorque += availableTorque * burnout * 0.1f;

		var front = 0.5f + PowerDistribution * 0.5f;
		var rear = 1 - front;

		availableFrontTorque = availableTorque * front;
		availableRearTorque = availableTorque * rear;

		throttle = MathX.Approach( throttle, inputThrottle, dt * 4 );


		EngineAccelerate( FlywheelFriction + FlywheelTorque * throttle, dt );

		Throttle = throttle;

		IsRedlining = (isRedlining && inputThrottle > 0);
	}

	public void StreamEngineUpdate()
	{

	}
}
