class_name ShakingCamera
extends Camera3D

enum ShakeType { POSITIONAL, ROTATIONAL, BOTH }
#enum ShakeType { OFFSET, POSITIONAL, ROTATIONAL, BOTH }

## A list containing all [ShakingCamera]'s.[br]
## Used by the [code]static[/code] [method shake_all_cameras]
## and [method constant_shake_all_cameras] functions.
static var cameras: Array[ShakingCamera]

@export var shake_intensity_multiplier: float = 1.0
@export var origin: Node3D = self

## The overall constant [b]positional[/b] intensity that is being
## added up by [method apply_constant_shake].
var constant_positional_intensity: float = 0.0
# The overall constant [b]offset[/b] intensity that is being
# added up by [method apply_constant_shake].
#var constant_offset_intensity: float = 0.0
## The overall constant [b]rotational[/b] intensity that is being
## added up by [method apply_constant_shake].
var constant_rotational_intensity: float = 0.0
## The local origin at which we offset our positional shake from.
var shake_origin: Vector3 = position
## The local rotation at which we offset our rotational shake from.
var shake_rotation_origin: Vector3 = rotation
#var _offset_intensity: float = 0.0
var _positional_intensity: float = 0.0
var _rotational_intensity: float = 0.0
var _duration: float = 0.001
var _time_left: float = 0.0


## Calls [method shake] on all cameras contained in [member cameras].
static func shake_all_cameras(intensity: float, duration: float, shake_type := ShakeType.POSITIONAL) -> void:
	for camera: ShakingCamera in cameras:
		camera.shake(intensity, duration, shake_type)


## Calls [method apply_constant_shake] on all cameras contained in [member cameras].
static func constant_shake_all_cameras(intensity: float, shake_type := ShakeType.POSITIONAL) -> void:
	for camera: ShakingCamera in cameras:
		camera.apply_constant_shake(intensity, shake_type)


func _enter_tree() -> void:
	if not cameras.has(self):
		cameras.append(self)


func _exit_tree() -> void:
	cameras.erase(self)


func _process(delta: float) -> void:
	var time_ratio: float = clampf(_time_left / _duration, 0.0, 1.0)

	#var offset_intensity: float = maxf(_offset_intensity * time_ratio, constant_offset_intensity)
	#var _offset := Vector3(
		#randf_range(-1.0, 1.0) * offset_intensity,
		#randf_range(-1.0, 1.0) * offset_intensity,
		#randf_range(-1.0, 1.0) * offset_intensity
	#)

	var positional_intensity: float = maxf(_positional_intensity * time_ratio, constant_positional_intensity)
	var position_offset := Vector3(
		randf_range(-1.0, 1.0) * positional_intensity,
		randf_range(-1.0, 1.0) * positional_intensity,
		randf_range(-1.0, 1.0) * positional_intensity
	)

	var rotational_intensity: float = maxf(_rotational_intensity * time_ratio, constant_rotational_intensity)
	var rotation_offset := Vector3(
		# TAU is keeping the shake intensity similar with the position counterpart.
		deg_to_rad(randf_range(-1.0, 1.0) * rotational_intensity) * TAU,
		deg_to_rad(randf_range(-1.0, 1.0) * rotational_intensity) * TAU,
		deg_to_rad(randf_range(-1.0, 1.0) * rotational_intensity) * TAU
	)

	#h_offset = _offset.x
	#v_offset = _offset.y
	position = shake_origin + (position_offset * shake_intensity_multiplier)
	rotation = shake_rotation_origin + (rotation_offset * shake_intensity_multiplier)
	_time_left = maxf(_time_left - delta, 0.0)

	if is_zero_approx(_time_left):
		#_offset_intensity = 0.0
		_positional_intensity = 0.0
		_rotational_intensity = 0.0
		_duration = 0.001

	#constant_offset_intensity = 0.0
	constant_positional_intensity = 0.0
	constant_rotational_intensity = 0.0


# TODO: Document this better.
## Shakes the camera using [param intensity] and [param duration].
func shake(intensity: float, duration: float, shake_type := ShakeType.POSITIONAL) -> void:
	if shake_type == ShakeType.POSITIONAL or shake_type == ShakeType.BOTH:
		_add_positional_shake(intensity, duration)

	if shake_type == ShakeType.ROTATIONAL or shake_type == ShakeType.BOTH:
		_add_rotational_shake(intensity, duration)


## Adds a constant shake to the camera which [b]needs to be called every [method Node._process] frame[/b].
func apply_constant_shake(intensity: float, shake_type: ShakeType) -> void:
	#if shake_type == ShakeType.OFFSET:
		#constant_offset_intensity += intensity

	if shake_type == ShakeType.POSITIONAL or shake_type == ShakeType.BOTH:
		constant_positional_intensity += intensity

	if shake_type == ShakeType.ROTATIONAL or shake_type == ShakeType.BOTH:
		constant_rotational_intensity += intensity


#func _add_offset_shake(intensity: float, duration: float) -> void:
	#if intensity < _offset_intensity and duration < _time_left:
		#return
#
	#_duration = maxf(maxf(_duration, duration), 0.001) # Should always be above 0 due to division.
	#_time_left = duration
	#_offset_intensity = maxf(intensity, _offset_intensity)


func _add_positional_shake(intensity: float, duration: float) -> void:
	if intensity < _positional_intensity and duration < _time_left:
		return

	_duration = maxf(maxf(_duration, duration), 0.001) # Should always be above 0 due to division.
	_time_left = duration
	_positional_intensity = maxf(intensity, _positional_intensity)


func _add_rotational_shake(intensity: float, duration: float) -> void:
	if intensity < _rotational_intensity and duration < _time_left:
		return

	_duration = maxf(maxf(_duration, duration), 0.001) # Should always be above 0 due to division.
	_time_left = duration
	_rotational_intensity = maxf(intensity, _rotational_intensity)
