class_name FollowCamera
extends Camera

export (NodePath) var _target_path: NodePath
export (Vector3) var _target_offset: = Vector3(0.0, 1.25, 0.0)
export (float) var _target_distance: = 1.0
export (float) var _min_distance: = 1.0
export (float) var _max_distance: = 5.0
export (float) var _zoom_step: = 0.25
export (float) var _zoom_speed: = 1.0

onready var _distance: = _target_distance
var _rot_x: = 0.0
var _rot_y: = 0.0
var _mouse_sensitivity: = 0.003


func _ready() -> void:
	__test()
	Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)


func _input(event: InputEvent):
	if event is InputEventMouseMotion:
		var mm_event: = event as InputEventMouseMotion
		var new_basis: = transform.basis
		new_basis = new_basis.rotated(transform.basis.x, -mm_event.relative.y * _mouse_sensitivity)
		new_basis = new_basis.rotated(Vector3(0.0, 1.0, 0.0), -mm_event.relative.x * _mouse_sensitivity)
		transform.basis = new_basis
	elif event is InputEventMouseButton:
		var mb_event: = event as InputEventMouseButton
		if mb_event.button_index == BUTTON_WHEEL_UP and mb_event.pressed:
			_target_distance = max(_min_distance, _target_distance - _zoom_step)
		elif mb_event.button_index == BUTTON_WHEEL_DOWN and mb_event.pressed:
			_target_distance = min(_max_distance, _target_distance + _zoom_step)


func _process(delta: float) -> void:
	_update_distance(delta)
	var cam_dir: = transform.basis.z.normalized()
	var distance: = _distance
	var target_gt = _target().global_transform.origin
	var collision = get_world().direct_space_state.intersect_ray(
		target_gt + _target_offset,
		target_gt + _target_offset + cam_dir * _distance,
		[_target()]
	)
	if collision.has("position"):
		distance = (collision.position - (target_gt + _target_offset)).length() - 0.1
	global_transform.origin = target_gt + _target_offset + cam_dir * distance


func _update_distance(delta: float) -> void:
	if abs(_target_distance - _distance) < _zoom_speed * delta:
		_distance = _target_distance
	else:
		var scale: = max(1.0, abs(_target_distance - _distance) / _zoom_step)
		_distance += sign(_target_distance - _distance) * _zoom_speed * delta * scale


func _target() -> Spatial:
	return get_node(_target_path) as Spatial


func __test() -> void:
	assert(_target_path != null)
	assert(_target() is Spatial)
