# ---------------------------------------------------------------------
# This script is supposed to handle character movement logic
# ---------------------------------------------------------------------
class_name PlayerInput extends CharacterBody3D

var health: int = 100

@export_category("PlayerInput")

const SPEED = 5.0
const JUMP_VELOCITY = 4.5

var mouse_captured: bool = false

@onready var player_synchronizer: MultiplayerSynchronizer = $PlayerSynchronizer
@onready var first_view_camera_mount: Node3D = $FirstPersonCameraMount
@onready var first_view_camera: Camera3D = $FirstPersonCameraMount/Camera
@onready var model_mount: Node3D = $ModelMount

# -- This node is supposed to be spawned per player, and since each
# -- player has an id, it is used for giving a node a name. So we can
# -- use it here to tell a controlled node from the rest models
@onready var owner_id: int = str($".".name).to_int()
var multiplayer_id: int = 0

# -- Character state
var alive: bool = true

func _ready() -> void:
	player_synchronizer.set_multiplayer_authority(owner_id)
	multiplayer_id = multiplayer.get_unique_id()
	# -- Separate logic for player and other models that are controlled 
	# -- by other players on the server
	# -- TODO: If player is alive, it must not be able to see other
	# --       cameras, but if player is dead, it must be able to
	# --       switch between other players
	if _is_current_player():
		var err := _add_first_view_model()
		if err != OK:
			print("Error occured: " + str(err))
		err = _add_legs_to_first_view()
		if err != OK:
			print("Error occured: " + str(err))
		
		_enable_camera()
	else:
		var err := _add_world_model()
		if err != OK:
			print("Error occured: " + str(err))
		
	_capture_mouse()

# -- Add a world model to the player, that should be seen by other players 
# -- on the server
func _add_world_model() -> Error :
	# -- TODO: It should not be hardcoded
	var path := "res://scenes/characters/blue/dummy.tscn"
	if not ResourceLoader.exists(path):
		return ERR_DOES_NOT_EXIST
	var scene: PackedScene = ResourceLoader.load(path)
	if not scene.can_instantiate():
		return ERR_CANT_OPEN
	var node: Node3D = scene.instantiate()
	model_mount.add_child(node)
	return OK

var current_gun: String = "ak"
@onready var gun_mount: Node3D = $FirstPersonCameraMount/GunMount
@onready var gun_mount_anim: AnimationPlayer = $FirstPersonCameraMount/GunMount/AnimationPlayer
var gun_with_hands: Node3D = null

# -- Add the first person view to pthe player
func _add_first_view_model() -> Error :
	# -- TODO: It should not be hardcoded
	# Define a format string with placeholder '%s'
	var path_tmpl := "res://scenes/weapon/guns/%s/with_hands.tscn"
	var path := path_tmpl % current_gun
	if not ResourceLoader.exists(path):
		return ERR_DOES_NOT_EXIST
	var scene: PackedScene = ResourceLoader.load(path)
	if not scene.can_instantiate():
		return ERR_CANT_OPEN
	var node: Node3D = scene.instantiate()
	node.scale = Vector3(0.03,0.03,0.03)
	node.position = Vector3(0.02, -0.03, -0.07)
	gun_with_hands = node
	gun_mount.add_child(node)
	return OK
	
	
var first_view_legs_anim: AnimationPlayer = null
# -- Add a world model to the player, that should be seen by other players 
# -- on the server
func _add_legs_to_first_view() -> Error :
	# -- TODO: It should not be hardcoded
	var path := "res://scenes/characters/blue/dummy.tscn"
	if not ResourceLoader.exists(path):
		return ERR_DOES_NOT_EXIST
	var scene: PackedScene = ResourceLoader.load(path)
	if not scene.can_instantiate():
		return ERR_CANT_OPEN
	var node: Node3D = scene.instantiate()
	var skeleton: Skeleton3D = node.find_child("Skeleton3D")
	var bone := skeleton.find_bone("mixamorig_Spine")
	if bone != -1:
		skeleton.set_bone_pose_scale(bone, Vector3(0, 0, 0))
	model_mount.add_child(node)
	var animation_node: AnimationPlayer = node.find_child("AnimationPlayer")
	if animation_node != null:
		first_view_legs_anim = animation_node
	return OK


func _enable_camera():
	first_view_camera.make_current()

func _capture_mouse() -> void:
	Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
	mouse_captured = true

func _release_mouse() -> void:
	Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
	mouse_captured = false

# -- Does the peer own the node
func _is_current_player() -> bool:
	if owner_id == multiplayer_id:
		return true
	return false

# ---------------------------------------------------------------------
# -- Input controller
# ---------------------------------------------------------------------
var move_dir: Vector2 # Input direction for movement
var look_dir: Vector2 # Input direction for look/aim

var walk_vel: Vector3 # Walking velocity 
var grav_vel: Vector3 # Gravity velocity 
var jump_vel: Vector3 # Jumping velocity

@export_range(0.1, 3.0, 0.1) var jump_height: float = 1 # m
@export_range(0.1, 3.0, 0.1, "or_greater") var camera_sens: float = 1


func _unhandled_input(event: InputEvent) -> void:
	if _is_current_player():
		if event is InputEventMouseMotion:
			look_dir = event.relative * 0.001
			if mouse_captured: _rotate_camera()
	#if Input.is_action_just_pressed("jump"): jumping = true
		if Input.is_action_just_pressed("exit"): get_tree().quit()
		if Input.is_action_pressed("shot"): _shoot()
	#if str($"..".name).to_int() == multiplayer.get_unique_id():
		#if Input.is_action_just_pressed("shot"): $UpperTorso/ViewModelCamera.shot()
	#if Input.is_action_just_pressed("reload"): $Body/UpperTorso/CameraMount/Camera.reload()

func _rotate_camera(sens_mod: float = 1.0) -> void:
	#if str($"..".name).to_int() == multiplayer.get_unique_id():
	rotation.y -= look_dir.x * camera_sens * sens_mod
	first_view_camera_mount.rotation.x = clamp(first_view_camera_mount.rotation.x - look_dir.y * camera_sens * sens_mod, -1.5, 1.5)

@onready var health_indicator = $FirstPersonCameraMount/HUD/HealthIndicator
@onready var fps_indicator = $FirstPersonCameraMount/HUD/FPS
func _process(delta: float) -> void:
	health_indicator.text = str(health)
	fps_indicator.text = str(Engine.get_frames_per_second())
	if health == 0:
		queue_free()
	
func _physics_process(delta: float) -> void:
	# Add the gravity.
	if not is_on_floor():
		velocity += get_gravity() * delta

	# Handle jump.
	if Input.is_action_just_pressed("jump") and is_on_floor():
		velocity.y = JUMP_VELOCITY

	# Get the input direction and handle the movement/deceleration.
	# As good practice, you should replace UI actions with custom gameplay actions.
	#var input_dir := Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
	# -- It shouldn't be possible to change direction during the jumps
	if is_on_floor():
		var input_dir := Input.get_vector("move_left", "move_right", "move_forward", "move_backwards")
		var direction := (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
		if direction:
			gun_mount_anim.play("move")
			if first_view_legs_anim != null:
				first_view_legs_anim.play("Run Forward")
			velocity.x = direction.x * SPEED
			velocity.z = direction.z * SPEED
		else:
			if gun_mount_anim.is_playing():
				gun_mount_anim.stop()
			if first_view_legs_anim != null and first_view_legs_anim.is_playing():
				first_view_legs_anim.stop()
			velocity.x = move_toward(velocity.x, 0, SPEED)
			velocity.z = move_toward(velocity.z, 0, SPEED)

	move_and_slide()
# -- TODO: It shouldn't be hardcoded	
var bullet = load("res://scenes/weapon/bullet.tscn")

@onready var shooting_raycast: RayCast3D = $FirstPersonCameraMount/RayCast3D
@onready var bullet_starting_point: Node3D = $FirstPersonCameraMount/BulletStartingPoint
@onready var aim_ray: RayCast3D = $FirstPersonCameraMount/BulletStartingPoint/AimRay
# --find the gun node and exec shoot
var cant_shoot: bool = false
func _shoot():
	if not cant_shoot:
		_send_shot_to_server.rpc_id(1, aim_ray.global_position)
		if aim_ray.is_colliding():
			var collider := aim_ray.get_collider()
			if collider != null and collider.is_in_group("target"):
				aim_ray.get_collider().take_damage()
			if collider != null and collider.is_in_group("body"):
				collider.hit()
		var root := get_tree().get_root()
		gun_with_hands.shoot()
		cant_shoot = true
		await get_tree().create_timer(0.2).timeout
		cant_shoot = false

@rpc("any_peer", "call_local", "unreliable_ordered")
func _send_shot_to_server(start_position):
	# -- TODO: Should not be hardcoded
	var world: Node3D = find_parent("ElTest")
	world.spawn_bullet(bullet_starting_point)
	
func _get_camera_collision():
	var viewport = get_viewport().size
	var ray_origin = first_view_camera.project_ray_origin(viewport / 2)
	var ray_end = ray_origin + first_view_camera.project_ray_normal(viewport / 2 * 100)
	var new_intersection = PhysicsRayQueryParameters3D.create(ray_origin, ray_end)
	var intersection = get_world_3d().direct_space_state.intersect_ray(new_intersection)
	if not intersection.is_empty():
		var collision_point = intersection.position
		print("gotcha")
		return collision_point 
	else:
		return ray_end
		
func _hit_scan_collision(collision_point):
	var viewport = get_viewport().size
	var ray_origin = first_view_camera.project_ray_origin(viewport / 2)
	var ray_end = ray_origin + first_view_camera.project_ray_normal(viewport / 2 * 100)
	var new_intersection = PhysicsRayQueryParameters3D.create(ray_origin, ray_end)
	var bullet_collision = get_world_3d().direct_space_state.intersect_ray(new_intersection)
	if bullet_collision:
		print(bullet_collision)
		_hit_scan_damage(bullet_collision.collider)
		
func _hit_scan_damage(Collider):
	#if Collider.is_in_group("target") and Collider.has_method("take_damage"):
	print("damaged")

func take_damage(dam: int):
	health =- dam