class_name Player
extends Car

const MIN_SPEED: float = 60.0
const DECELERATION: float = 10.0
const SPEED_FOR_MAX_PITCH: float = 200.0
const FINISH_SPEED: float = 90.0

@export var specs: PlayerSpecs = null
@export var control_factor: Curve
@export var roar_sound: AudioStream = null
@export var mud_sound: AudioStream = null

@onready var mud_fuctor: float = (
			0.45 if specs.control_level == Spec.Level.LOW
			else 0.6 if specs.control_level == Spec.Level.MEDIUM
			else 0.75
	)

var _in_mud_timer: float = 0.0
var _is_in_mud: bool = false
var _current_mud_factor: float = 1.0
var _player_manager: PlayerManager = null


func _ready() -> void:
	super()
	PulseSound0.load_and_play(roar_sound)
	if Globals.current_player_specs:
		specs = Globals.current_player_specs
	set_sprite(specs.sprite_number)
	_player_manager = PlayerManager.get_instance()
	_player_manager.current_speed = MIN_SPEED
	var ride_manager := RideManager.get_instance()
	ride_manager.distance_changed.connect(_on_distance_changed)
	ride_manager.times_up.connect(destroy.bind(true))
	ride_manager.level_finished.connect(finish)
	if not _player_manager.is_alive:
		_player_manager.is_alive = true
		blink()


func _physics_process(delta: float) -> void:
	speed = _player_manager.current_speed
	if not _player_manager.is_alive:
		return

	if not _is_in_mud:
		_in_mud_timer = max(_in_mud_timer - delta, 0)

	if _player_manager.control_enabled:
		handle_controls(delta)

	PulseSound0.pitch_scale = remap(_player_manager.current_speed, 0, SPEED_FOR_MAX_PITCH, 0.5, 4.0)
	if NoiseSound.is_sound_playing(mud_sound):
		NoiseSound.pitch_scale = PulseSound0.pitch_scale * 0.75
	elif _is_in_mud:
		NoiseSound.load_and_play(mud_sound, 0.1)

	if _player_manager.is_finished:
		global_position.y -= FINISH_SPEED * delta
		if global_position.y < -32.0:
			queue_free()


func brake(to_speed: float) -> void:
	_player_manager.current_speed = to_speed


func destroy(times_up: bool = false) -> void:
	PulseSound0.stop_sound(roar_sound)
	_player_manager.is_alive = false
	if not times_up:
		_player_manager.crash()
	super()


func mud_effect(time) -> void:
	if _player_manager.is_finished:
		return
	_is_in_mud = true
	NoiseSound.load_and_play(mud_sound, 0.2)
	_in_mud_timer = max(time, _in_mud_timer)


func mud_exited() -> void:
	_is_in_mud = false
	if not _player_manager.is_alive:
		NoiseSound.stop_sound(mud_sound)
		return
	NoiseSound.stop_sound(mud_sound, 0.5)


func blink() -> void:
	collision_layer = 256
	collision_mask = 0
	for i in range(5):
		await get_tree().create_timer(0.1).timeout
		hide()
		await get_tree().create_timer(0.1).timeout
		show()
	collision_layer = 258
	collision_mask = 12


func finish() -> void:
	set_car_rotation(0)
	NoiseSound.stop()
	PulseSound0.stop()


func handle_controls(delta: float) -> void:
	var target_mud_factor: float = mud_fuctor if _in_mud_timer > 0.0 else 1.0
	_current_mud_factor = lerpf(_current_mud_factor, target_mud_factor, 0.1)
	if absf(_current_mud_factor - target_mud_factor) < 0.01:
		_current_mud_factor = target_mud_factor

	var drive_input := int(signf(Input.get_axis("b", "a") + Input.get_axis("down", "up")))
	match drive_input:
		1:
			_player_manager.current_speed += specs.get_acceleration() * delta * _current_mud_factor
		0:
			_player_manager.current_speed -= DECELERATION / _current_mud_factor * delta
		-1:
			_player_manager.current_speed -= (
				specs.get_brake_deceleration() / _current_mud_factor * delta
			)

	_player_manager.current_speed = clampf(_player_manager.current_speed,
			MIN_SPEED, specs.get_max_speed() * _current_mud_factor)

	var control := int(signf(Input.get_axis("left", "right")))
	if control:
		var speed_factor: float = (
				control_factor.sample(
					(_player_manager.current_speed - MIN_SPEED) /
					(specs.get_max_speed() - MIN_SPEED)
				)
		)
		var control_speed: float = control * specs.get_control() * speed_factor * _current_mud_factor
		global_position.x = clampf(global_position.x + control_speed * delta, 8, 152)
	if not is_skidding:
		set_car_rotation(control if global_position.x != 8 and global_position.x != 152 else 0)


func _on_distance_changed(pixels: int) -> void:
	if _player_manager.is_stared and _player_manager.is_alive:
		var score: float = pixels / 10.0
		Globals.raw_score += score if global_position.x > 80 else score * 2
