class_name AccelCalibration
extends RefCounted

enum Axis { X, Y, Z }

const MIN_SAMPLES := 25
const TAIL_FRACTION := 0.55

var axis: Axis = Axis.X
var sign: float = 1.0
var center: float = 0.0
var suggested_threshold: float = 3000.0
var direction: Vector3 = Vector3.RIGHT
var neutral: Vector3 = Vector3.ZERO
var uses_vector: bool = false

var _samples_neutral: Array[Vector3] = []
var _samples_right: Array[Vector3] = []
var _samples_left: Array[Vector3] = []


func reset() -> void:
	_samples_neutral.clear()
	_samples_right.clear()
	_samples_left.clear()
	uses_vector = false
	direction = Vector3.RIGHT
	neutral = Vector3.ZERO


func record_neutral(accel: Vector3i) -> void:
	_samples_neutral.append(Vector3(accel))


func record_right(accel: Vector3i) -> void:
	_samples_right.append(Vector3(accel))


func record_left(accel: Vector3i) -> void:
	_samples_left.append(Vector3(accel))


func is_ready() -> bool:
	return (
		_samples_neutral.size() >= MIN_SAMPLES
		and _samples_right.size() >= MIN_SAMPLES
		and _samples_left.size() >= MIN_SAMPLES
	)


func analyze() -> bool:
	if not is_ready():
		return false

	var mean_n := _tail_mean(_samples_neutral)
	var mean_r := _tail_mean(_samples_right)
	var mean_l := _tail_mean(_samples_left)
	var delta := mean_r - mean_l

	if delta.length_squared() < 150.0 * 150.0:
		return false

	direction = delta.normalized()
	neutral = mean_n
	uses_vector = true
	sign = 1.0
	_set_axis_from_direction()

	var neutral_proj: Array[float] = []
	for sample in _samples_neutral:
		neutral_proj.append(_project_vec(sample))
	neutral_proj.sort()
	center = neutral_proj[neutral_proj.size() / 2]

	var deviations: Array[float] = []
	for value in neutral_proj:
		deviations.append(absf(value - center))
	deviations.sort()
	var spread := deviations[deviations.size() / 2] if not deviations.is_empty() else 500.0
	suggested_threshold = clampf(spread * 2.2 + 180.0, 250.0, 8000.0)

	var right_proj := _project_vec(mean_r) - center
	var left_proj := _project_vec(mean_l) - center
	var separation := absf(right_proj - left_proj)
	if separation < suggested_threshold * 2.0:
		suggested_threshold = clampf(separation * 0.18, 250.0, 8000.0)

	return true


func project(accel: Vector3i) -> float:
	if uses_vector:
		return _project_vec(Vector3(accel))
	return _component(Vector3(accel), axis) * sign


func duplicate() -> AccelCalibration:
	var copy := AccelCalibration.new()
	copy.axis = axis
	copy.sign = sign
	copy.center = center
	copy.suggested_threshold = suggested_threshold
	copy.direction = direction
	copy.neutral = neutral
	copy.uses_vector = uses_vector
	return copy


func axis_name() -> String:
	if uses_vector:
		return "3D-Schwank (%+.1f,%+.1f,%+.1f)" % [direction.x, direction.y, direction.z]
	match axis:
		Axis.X:
			return "x"
		Axis.Y:
			return "y"
		Axis.Z:
			return "z"
	return "?"


func _project_vec(v: Vector3) -> float:
	return (v - neutral).dot(direction)


func _tail_mean(samples: Array[Vector3]) -> Vector3:
	var start_idx := int(samples.size() * TAIL_FRACTION)
	return _mean(samples.slice(start_idx))


func _set_axis_from_direction() -> void:
	var abs_dir := direction.abs()
	axis = Axis.X
	var best := abs_dir.x
	if abs_dir.y > best:
		best = abs_dir.y
		axis = Axis.Y
	if abs_dir.z > best:
		axis = Axis.Z


func _mean(samples: Array[Vector3]) -> Vector3:
	if samples.is_empty():
		return Vector3.ZERO
	var sum := Vector3.ZERO
	for s in samples:
		sum += s
	return sum / float(samples.size())


func _component(v: Vector3, ax: Axis) -> float:
	match ax:
		Axis.X:
			return v.x
		Axis.Y:
			return v.y
		Axis.Z:
			return v.z
	return 0.0
