extends CharacterBody3D

const SENSETIVITY = 0.009;

enum Class_type {
	Spectator = 0,
	Astra = 1,
	BlackArch = 2,
	Kali = 3,
	Debian = 4,
	Arch = 5,
	Windows11 = -1,
	WindowsServer = -2,
	MacOS = -3,
	WindowsXP = -4,
	Windows10 = -5
}

var Weapons = GameData.Weapons

var properties

var speed = 12
var walk = 12
var run = 24
var jump = 16
var gravity = 9.8
var acceleration = 2.5
var inertia = 20
var climbing = false

var game_settings
var client_settings
var can_shoot = true
var time_since_last_shot = 0

var round_status = {
	"cs_round_score" = 0,
	"os_round_score" = 0,
	"os_game_score" = 0,
	"cs_game_score" = 0,
	"round_number" = 0,
}

@onready var head = $Head
@onready var camera: Camera3D = $Head/Camera
@onready var playerCharacterBody = $"."
@onready var collision_shapes = find_children("collision*","",false)

var HUD
var healthLabel
var armorLabel
var ammoLabel
var magazineLabel
var statusLabel
var csScoreRoundLabel
var csScoreGameLabel
var osScoreRoundLabel
var osScoreGameLabel
var roundNumberLabel
var timeLabel
var winnerLabel
var killLogList

func set_properties(props):
	print("Got props from server: " + str(props))
	properties = props
	$"Head/Nickname".text = properties["nickname"]
	name = "player" + str(properties["internal_id"])
	position = properties["position"]
	if (game_settings != null):
		change_weapon(properties["current_weapon"]["number"])

func set_property(key, value):
	properties[key] = value
	update_state()

func init_hud():
	HUD = $HUD
	
	healthLabel = $"HUD/health_and_ammo_display/healthdisplay/text"
	armorLabel = $"HUD/health_and_ammo_display/armordisplay/text"
	ammoLabel = $"HUD/health_and_ammo_display/ammodisplay/text"
	magazineLabel = $"HUD/health_and_ammo_display/magazinedisplay/text"
	statusLabel = $"HUD/Status"
	
	csScoreRoundLabel = $"HUD/round_status/cs_score_round"
	csScoreGameLabel = $"HUD/round_status/cs_score_game"
	osScoreRoundLabel = $"HUD/round_status/os_score_round"
	osScoreGameLabel = $"HUD/round_status/os_score_game"
	
	roundNumberLabel = $"HUD/round_status/round_number"
	timeLabel = $"HUD/round_status/time"
	winnerLabel = $"HUD/round_status/winner"
	
	killLogList = $"HUD/kill_log"

func update_hud():
	healthLabel.text = str(properties["HP"])
	armorLabel.text = str(properties["AP"])
	ammoLabel.text = str(properties["current_weapon"]["ammo"])
	magazineLabel.text = str(properties["current_weapon"]["magazine"])
	if (properties["reloading"]):
		statusLabel.text = "Reloading..."
	else:
		statusLabel.text = "Reloaded"
	
	csScoreRoundLabel.text = str(round_status["cs_round_score"])
	csScoreGameLabel.text = str(round_status["cs_game_score"])
	
	osScoreRoundLabel.text = str(round_status["os_round_score"])
	osScoreGameLabel.text = str(round_status["os_game_score"])
	
	roundNumberLabel.text = "Round " + str(round_status["round_number"])
	
func find_weapon_name_by_number(number):
	var name = ""
	for weapon in game_settings["weapons"].keys():
		if (game_settings["weapons"][weapon].number == number):
			name = weapon
	return name

@rpc("authority", "call_local", "reliable")
func change_weapon(new_weapon_number):
	var weapons = $"Head/Camera/Hand".get_children()
	for weapon in weapons:
		weapon.visible = false
	weapons[new_weapon_number].visible = true
	print("Changing weapon: " + str(new_weapon_number))
	#if (properties["is_playable"]):
		#properties["current_weapon"] = properties["weapons"][find_weapon_name_by_number(new_weapon_number)]
		#properties["reloading"] = false

func find_weapon_in_inventory(number):
	var found_weapon
	for weapon in properties["weapons"].values():
		if weapon["number"] == number:
			found_weapon = weapon
			break
	return found_weapon

func find_weapon_by_number(number):
	var found_weapon
	for weapon in game_settings["weapons"].keys():
		if (game_settings["weapons"][weapon]["number"] == number):
			found_weapon = game_settings["weapons"][weapon].duplicate()
			break
	return found_weapon

func find_current_weapon_by_number(number):
	var found_weapon
	for weapon in Weapons.keys():
		if (Weapons[weapon]["number"] == number):
			found_weapon = Weapons[weapon]
			break
	return found_weapon

func update_state():
	camera.set_current(false)
	$"Head/Nickname".text = properties["nickname"]
	
	if (!properties["is_playable"]): return
	camera.set_current(true)
	if (HUD == null):
		var hud = load("res://scenes/HUD/hud.tscn").instantiate()
		add_child(hud)
		init_hud()
		update_hud()
	
	playerCharacterBody.up_direction = Vector3.UP;
	Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
	
func _ready():
	update_state()
	
func update_weapon_raycast():
	var current_weapon_name = Weapons.find_key(properties["current_weapon"])
	if(current_weapon_name == null): current_weapon_name = "knife"
	var raycast:RayCast3D = get_node("Head/Camera/viewRaycast")
	var weapon_raycast:RayCast3D = get_node("Head/Camera/Hand/" + str(current_weapon_name) + "/raycast")
	
	weapon_raycast.target_position = weapon_raycast.to_local(raycast.to_global(raycast.target_position) - weapon_raycast.position)

func try_shoot():
	print(str(properties["current_weapon"]))
	#var current_weapon = game_settings["weapons"].find_key(find_weapon_by_number(properties["current_weapon"]["number"]))
	var current_weapon = find_weapon_name_by_number(properties["current_weapon"]["number"])#game_settings["weapons"].find_key(find_weapon_by_number(properties["current_weapon"]["number"]))
	print("currect weapon: " + current_weapon)
	var current_weapon_settings = game_settings["weapons"][current_weapon]
	properties["reloading"] = false

	if (properties["current_weapon"]["magazine"] == 0):
		return
	if Input.is_action_pressed("shoot") and current_weapon_settings["fireRate"] >= 0.5 and not Input.is_action_just_pressed("shoot"):
		return
	time_since_last_shot = (Time.get_ticks_msec() - properties["last_shot"]) / 1000.
	
	if (time_since_last_shot > current_weapon_settings["fireRate"]):
		Networking.shot.rpc_id(1, multiplayer.get_unique_id())
		properties["current_weapon"]["magazine"] -= 1
		properties["last_shot"] = Time.get_ticks_msec()
		
		var raycast:RayCast3D = get_node("Head/Camera/viewRaycast")
		var weapon_raycast:RayCast3D = get_node("Head/Camera/Hand/" + str(current_weapon) + "/raycast")
		raycast.target_position.z = -current_weapon_settings["range"]
		raycast.force_raycast_update()
		raycast.force_update_transform()
		var target_point = raycast.get_collision_point()
		
		weapon_raycast.target_position = weapon_raycast.to_local(target_point) * 1.1
		weapon_raycast.rotation.y = atan((weapon_raycast.position.x - position.x) / ((-current_weapon_settings["range"]) - (abs(weapon_raycast.position.z - position.z))))
		
		weapon_raycast.target_position.y += current_weapon_settings["spreading"] * randf() * sin(randf() * 2 * PI)
		weapon_raycast.target_position.z += current_weapon_settings["spreading"] * randf() * cos(randf() * 2 * PI)
		
		weapon_raycast.force_raycast_update()
		weapon_raycast.force_update_transform()
		
		var trace = preload("res://scenes/models/bullet_trace.tscn").instantiate()
		get_node("..").add_child(trace)
		trace.init((weapon_raycast.global_position), (weapon_raycast.get_collision_point()))

func reload():
	var current_weapon_instance = properties["current_weapon"]
	var current_weapon = game_settings["weapons"].find_key(find_weapon_by_number(current_weapon_instance["number"]))
	var current_weapon_settings = game_settings["weapons"][current_weapon]
	var to_reload = current_weapon_settings["magazine"] - current_weapon_instance["magazine"]
	if (properties["reloading"]):
		if (to_reload <= current_weapon_instance["ammo"]):
			current_weapon_instance["magazine"] += to_reload
			current_weapon_instance["ammo"] -= to_reload
		else:
			current_weapon_instance["magazine"] += current_weapon_instance["ammo"]
			current_weapon_instance["ammo"] = 0
		properties["reloading"] = false
		if (HUD != null):
			update_hud()

func _unhandled_input(event):
	if (!properties["is_playable"]): return
	
	var weapon_change = 0
	if Input.is_action_pressed("MWU"):
		weapon_change = 1
	if Input.is_action_pressed("MWD"):
		weapon_change = -1
	if (weapon_change != 0):
		var new_weapon_number = wrap(properties["current_weapon"]["number"] + weapon_change, 0, game_settings["weapons"].size())
		properties["current_weapon"] = find_weapon_in_inventory(new_weapon_number)
		
		print(str(properties["current_weapon"]))
		Networking.change_weapon.rpc_id(1, multiplayer.get_unique_id(), properties["current_weapon"]["number"])
		
	if event is InputEventMouseMotion:
		rotate_y(-event.relative.x * SENSETIVITY)
		camera.rotate_x(-event.relative.y * SENSETIVITY)
		camera.rotation.x = clamp(camera.rotation.x, deg_to_rad(-50), deg_to_rad(60))
		
func _physics_process(delta):
	if (!properties["is_playable"]): return
	if (game_settings == null): return
	if (multiplayer.multiplayer_peer == null):
		properties["is_playable"] = false
		
	if Input.is_action_pressed("shoot"):
		try_shoot()
	
	if (HUD != null):
		update_hud()
	
	update_weapon_raycast()
	
	if Input.is_action_just_pressed("reload"):
		var current_weapon = game_settings["weapons"].find_key(find_weapon_by_number(properties["current_weapon"]["number"]))
		Networking.client_reloading.rpc_id(1, multiplayer.get_unique_id())
		properties["reloading"] = true
		get_tree().create_timer(game_settings["weapons"][current_weapon]["reload"]).connect("timeout", reload)
		
	var input_dir = Input.get_vector("left", "right", "forward", "backward")
	var direction = (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
	
	if direction:
		velocity.x = lerp(velocity.x, direction.x * speed, delta * acceleration)
		velocity.z = lerp(velocity.z, direction.z * speed, delta * acceleration)
		if (climbing):
			print(str(input_dir))
			velocity.y = -input_dir.y * jump / 2 if input_dir.x == 0 else abs(input_dir.x) * jump / 2
	else:
		if is_on_floor():
			velocity.x = lerp(velocity.x, direction.x * speed * 0.5, delta * inertia)
			velocity.z = lerp(velocity.z, direction.z * speed * 0.5, delta * inertia)
		elif not is_on_floor() and not climbing:
			velocity.x = lerp(velocity.x, direction.x * speed * 0.5, delta)
			velocity.z = lerp(velocity.z, direction.z * speed * 0.5, delta)
	if is_on_floor():
		if Input.is_action_just_pressed("jump"):
			velocity.y = jump
		if Input.is_action_pressed("run"):
			speed = run
		else:
			speed = walk
	else:
		velocity.y -= 4 * gravity * delta
		velocity.x = lerp(velocity.x, direction.x * speed, delta)
		velocity.z = lerp(velocity.z, direction.z * speed, delta)
	move_and_slide()
	for index in get_slide_collision_count():
		var collision := get_slide_collision(index)
		var body := collision.get_collider()
		if ("ladder" in body.name):
			climbing = true
		else:
			climbing = false
		#print("Collided with: " + body.name)
	if (!multiplayer.is_server()):
		Networking.sync_client.rpc_id(1, multiplayer.get_unique_id(), $".".position, {"y": rotation.y, "x": camera.rotation.x})
		#print("I am " + $"..".name +", belonging of " + str(multiplayer.get_unique_id()) + ", with internal ID: " + str(properties["internal_id"]))

@rpc ("authority", "call_remote", "unreliable")
func sync_puppet(i_id, p, rot):
	if(!properties["ready"]): return
	if(properties["is_playable"]): return
	if (int(i_id) == properties["internal_id"]):
		playerCharacterBody.position = p
		rotation.y = rot.y
		camera.rotation.x = rot.x

@rpc ("authority", "call_remote", "reliable")
func set_hp(hp):
	if (!properties["is_playable"]): return
	properties["HP"] = hp
	
@rpc ("authority", "call_remote", "reliable")
func set_game_settings(s):
	game_settings = s
	gravity = game_settings["moving"]["gravity"]
	walk = game_settings["moving"]["walk"]
	run = game_settings["moving"]["run"]
	jump = game_settings["moving"]["jump"]
	acceleration = game_settings["moving"]["acceleration"]
	inertia = game_settings["moving"]["inertia"]
	change_weapon(properties["current_weapon"]["number"])
	GameData.apply_weapon_settings(game_settings["weapons"])
	Weapons = GameData.Weapons
	
@rpc ("authority", "call_remote", "reliable")
func teleport(pos):
	position = pos

@rpc ("authority", "call_remote", "reliable")
func change_weapon_puppet(i_id, new_weapon_number):
	if (int(i_id) == properties["internal_id"]):
		change_weapon(new_weapon_number)

@rpc("authority", "call_remote", "reliable")
func update_round_status(s):
	print("Updated round status: " + str(s))
	round_status = s
	if(HUD != null):
		update_hud()

@rpc("authority", "call_remote", "reliable")
func end_round(result):
	print("Result: %s" % str(result))
	winnerLabel.visible = true
	if (result > 0):
		winnerLabel.text = "OS WON"
	elif (result < 0 ):
		winnerLabel.text = "CS WON"
	await get_tree().create_timer(1).timeout
	winnerLabel.visible = false
	winnerLabel.text = ""
	
@rpc("authority", "call_remote", "reliable")
func kill_notification(killer, victim):
	var entry = load("res://scenes/models/kill_log_entry.tscn").instantiate()
	entry.get_node("killer").text = str(killer)
	entry.get_node("victim").text = str(victim)
	killLogList.add_child(entry)
