extends Node

enum TEAMS {
	DEFENCE,
	ATTACK,
	SPECTATE,
	UNASSIGNED
}

enum ROUND_STATES {
	NOT_SET = 0,
	BUY = 1,
	ROUND = 2,
	AFTER_ROUND = 3,
	AFTER_PLANT = 4,
	AFTER_SESSION = 5,
}

const ATTACK_LAYER: int = 0b10000
const DEFENCE_LAYER: int = 0b100000

signal round_started
signal round_state_changed(state: int)
signal player_stopped_interacting(id: int)

var player_nodes: Dictionary[int,Player] = {}
var object_containers: Array[ObjectContainer]

var dynamic_objects_spawner: MultiplayerSpawner
var plants: Array[PlantSite]
var plant_deadzones: Dictionary[StringName, Area3D]

var current_round: int = 0
var attacker_score: int = 0
var defender_score: int = 0
var attackers_alive: int = 0
var defenders_alive: int = 0

var bomb_timer: Timer
var round_timer: Timer
var buy_timer: Timer

var round_state: ROUND_STATES
var session_started: bool = false

var reference_round_time: float

func _ready() -> void:
	if multiplayer.is_server() == false:
		return
	
	bomb_timer = Timer.new()
	bomb_timer.wait_time = 45.0
	bomb_timer.one_shot = true
	bomb_timer.timeout.connect(end_round.bind(TEAMS.ATTACK))
	
	round_timer = Timer.new()
	round_timer.wait_time = 150
	round_timer.one_shot = true
	round_timer.timeout.connect(end_round.bind(TEAMS.DEFENCE))
	
	buy_timer = Timer.new()
	buy_timer.wait_time = 1
	buy_timer.one_shot = true
	buy_timer.timeout.connect(begin_main_stage)
	
	add_child(bomb_timer)
	add_child(round_timer)
	add_child(buy_timer)

func _process(_delta: float) -> void:
	if multiplayer.is_server() == false or not session_started:
		return
	match round_state:
		ROUND_STATES.BUY:
			reference_round_time = buy_timer.time_left
		ROUND_STATES.ROUND:
			reference_round_time = round_timer.time_left
		ROUND_STATES.AFTER_PLANT:
			reference_round_time = bomb_timer.time_left
		_:
			reference_round_time = 0
	update_clock.rpc(reference_round_time)

@rpc("authority","call_remote","unreliable")
func update_clock(time: float):
	reference_round_time = time

@rpc("authority","call_remote","reliable")
func start_session() -> void:
	if not is_server_request():
		return
	if multiplayer.is_server():
		start_session.rpc()
	
	current_round = 0
	attacker_score = 0
	defender_score = 0
	session_started = true
	
	start_round()

@rpc("authority","call_remote","reliable")
func end_session() -> void:
	if not is_server_request():
		return
	if multiplayer.is_server():
		end_session.rpc()
	
	bomb_timer.stop()
	round_timer.stop()
	buy_timer.stop()
	object_containers.clear()

	session_started = false

@rpc("authority","call_remote","reliable")
func start_round() -> void:
	if not is_server_request():
		return
	if multiplayer.is_server():
		buy_timer.start()
		start_round.rpc()
		round_timer.stop()
		bomb_timer.stop()
		for container in object_containers:
			container.despawn()
	attackers_alive = 0
	defenders_alive = 0
	
	current_round += 1
	
	round_started.emit.call_deferred()
	round_state = ROUND_STATES.BUY
	round_state_changed.emit.call_deferred(round_state)

@rpc("authority","call_remote","reliable")
func end_round(win_team: int) -> void:
	if not is_server_request():
		return
	if multiplayer.is_server():
		get_tree().create_timer(5).timeout.connect(start_round)
		end_round.rpc(win_team)
	
	if win_team == TEAMS.DEFENCE:
		defender_score += 1
	elif win_team == TEAMS.ATTACK:
		attacker_score += 1
	
	round_state = ROUND_STATES.AFTER_ROUND
	round_state_changed.emit(round_state)

@rpc("authority","call_remote","reliable")
func begin_main_stage() -> void:
	if not is_server_request():
		return
	if multiplayer.is_server():
		round_timer.start()
		begin_main_stage.rpc()
	
	round_state = ROUND_STATES.ROUND
	round_state_changed.emit(round_state)

@rpc("authority","call_remote","reliable")
func begin_bomb_stage() -> void:
	if not is_server_request():
		return
	if multiplayer.is_server():
		bomb_timer.start()
		round_timer.stop()
		begin_bomb_stage.rpc()
	
	round_state = ROUND_STATES.AFTER_PLANT
	round_state_changed.emit(round_state)

func defuse_win() -> void:
	if not multiplayer.is_server():
		return
	bomb_timer.stop()
	end_round(TEAMS.DEFENCE)

func add_dead(team: int):
	if multiplayer.is_server() == false:
		return
	if team == TEAMS.ATTACK:
		attackers_alive -= 1
		if attackers_alive == 0 and round_state != ROUND_STATES.AFTER_ROUND and round_state != ROUND_STATES.AFTER_PLANT:
			end_round(TEAMS.DEFENCE)
	if team == TEAMS.DEFENCE:
		defenders_alive -= 1
		if defenders_alive == 0 and round_state != ROUND_STATES.AFTER_ROUND:
			end_round(TEAMS.ATTACK)

func is_server_request() -> bool:
	return multiplayer.is_server() or multiplayer.get_remote_sender_id() == 1

## Spawns dynamic object at game scene [br]
## Dictionary keys: [br]
## (Required) scene - path/uuid to scene [br]
## (Optional) position - position to spawn [br]
## (Optional) impulse - impulse to apply [br]
## (Optional but required for each other) [br]
## ammo, remaining_ammo, slot - data for dropped weapon [br]
## for more, see dyn_objects_spawner.gd
func spawn(data: Dictionary) -> void:
	if multiplayer.is_server() == false:
		return
	
	var object = dynamic_objects_spawner.spawn(data)
	
	if data.has("position"):
		object.global_position = data.position

func despawn(path: NodePath) -> void:
	if multiplayer.is_server() == false:
		printerr(str(multiplayer.get_remote_sender_id())+" tried to despawn internally on "+str(multiplayer.get_unique_id()))
		return
	
	get_node(path).queue_free()

func shoot(id:int , damage: int, distance: float) -> void:
	if multiplayer.is_server() == false:
		return
	
	var player: Player = player_nodes[id]
	var player_camera: Camera3D = player.get_node("Camera3D")
	var space: PhysicsDirectSpaceState3D = player.get_world_3d().direct_space_state
	var endpoint: Vector3 = player_camera.global_position - player_camera.global_basis.z * distance
	
	var ray = PhysicsRayQueryParameters3D.create(player_camera.global_position,endpoint,1)
	ray.exclude = [player.get_rid()]
	ray.collide_with_areas = false
	match player.team:
		TEAMS.DEFENCE:
			ray.collision_mask |= ATTACK_LAYER
		TEAMS.ATTACK:
			ray.collision_mask |= DEFENCE_LAYER
		_:
			ray.collision_mask |= ATTACK_LAYER | DEFENCE_LAYER
	
	var collision = space.intersect_ray(ray)
	
	if collision != {} and collision["collider"] is Player:
		collision["collider"].take_damage.rpc_id(int(collision["collider"].name),damage)


@rpc("any_peer","call_local","reliable")
func interact() -> void:
	if multiplayer.is_server() == false:
		return
	var id = multiplayer.get_remote_sender_id()
	
	var player: Player = player_nodes[id]
	var player_camera: Camera3D = player.get_node("Camera3D")
	var space: PhysicsDirectSpaceState3D = player.get_world_3d().direct_space_state
	var endpoint: Vector3 = player_camera.global_position - player_camera.global_basis.z * 3
	
	var ray = PhysicsRayQueryParameters3D.create(player_camera.global_position,endpoint,1)
	ray.exclude = [player.get_rid()]
	ray.collide_with_areas = false
	ray.collision_mask |= 64
	
	var collision = space.intersect_ray(ray)
	if collision != {} and collision["collider"] is Interactible:
		collision["collider"].interaction_start(id)

@rpc("any_peer","call_local","reliable")
func stop_interact() -> void:
	if multiplayer.is_server() == false:
		return
	var id = multiplayer.get_remote_sender_id()
	player_stopped_interacting.emit(id)

func is_on_site() -> bool:
	for plant in plants:
		if plant.is_player_on_site(multiplayer.get_unique_id()):
			return true
	return false

func get_site() -> PlantSite:
	for plant in plants:
		if plant.is_player_on_site(multiplayer.get_unique_id()):
			return plant
	return null

func kill_site(site: StringName) -> void:
	if multiplayer.is_server() == false:
		return

	for body in plant_deadzones[site].get_overlapping_bodies():
		if body is Player:
			body.kill_request.rpc()
