extends "res://entities/GridBasedMovable.gd"

@export var can_pull = true
@export var tool_change_time = 0.08
@onready var ptm = get_node("RotationPoint/PlayerToolManager")
@onready var audio_player = $AudioStreamPlayer3D
@onready var bomb_counter = $BombCounter

#var active_tool: PLAYERTOOLS = get_node("RotationPoint/PlayerToolManager").active_tool

var requested_mesh_direction
var distant_object_to_take = null
var distant_tool_to_place = null
var distant_bomb_to_place = false
var enable_input = true
var last_keys_pressed_amount = 0
var first_move_started = false 


enum PLAYERTOOLS {NONE, CLEANING_TOOL, PUSH_TOOL, PULL_TOOL, GRINDING_TOOL}


var tool_changing = false

func _ready() -> void:
	super._ready()
	ptm.set_active_tool(PLAYERTOOLS.NONE)
	update_rotation_point(DIRECTIONS.UP)

func _process(delta):
	
	handle_distant_request()
	handle_player_input()
	
	if ray_ground.is_colliding():
		var groundObject = ray_ground.get_collider()
		if groundObject != null:
			if groundObject.is_in_group("enemy"): #spiky things are done in the gridbasedmovable base class
				hit()
	
func get_keys_pressed_amount():
	var keys = 0
	if Input.is_action_pressed("ui_right"):
			keys += 1
	if Input.is_action_pressed("ui_left"):
			keys += 1
	if Input.is_action_pressed("ui_up"):
			keys += 1
	if Input.is_action_pressed("ui_down"):
			keys += 1
	return keys

func handle_distant_request():
	
	
	if is_grid_aligned():
		
		if distant_object_to_take != null:
			if distant_object_to_take.is_in_group("tool") and distant_object_to_take.has_method("take_tool"):
				distant_object_to_take = null
				update_tool()
			elif distant_object_to_take.is_in_group("bomb_item"):
				distant_object_to_take = null
				try_take_bomb()
						
		elif distant_bomb_to_place == true:
			distant_bomb_to_place = false
			put_bomb_down()
			
		elif distant_tool_to_place != null:
			update_tool()
			distant_tool_to_place = null
			#ptm.set_active_tool(PLAYERTOOLS.NONE)
				
		
func hit():
	if health_node.invulnerable == false:
		audio_player.stream = load("res://sfx/ncl_player_hit.ogg")
		#audio_player.volume_db = -1
		audio_player.pitch_scale = randf_range(0.86, 0.93)
		audio_player.play()
	health_node.hurt()
	SceneManager.game_scene.update_labels()
	
func kill():
	pass
	#health_node.kill()

func movement_started():
	first_move_started = true
	if animation_player != null:
		animation_player.play("Player_Move", -1, 1.5 * (1/speed_modifier))
	
		
func put_bomb_down():

	if protection_area.has_overlapping_areas():
		#if a movable has it's PlacementProtectionArea (at front) at Monitorable, then no item can be placed
		#default false for this area, but used for enemies
		return
	if tool_changing == true:
		return
	if !is_grid_aligned():
		distant_bomb_to_place = true
		return
		
	
	if requested_direction != DIRECTIONS.NONE or ray_front.is_colliding():
		return
		
	
	
	if bomb_counter.has_bombs():
		bomb_counter.use_bomb()
	else:
		print ("no bombs left to use!!!")
		return
		
	enable_movement = false	
	await get_tree().create_timer(0.1).timeout
	
	var Bomb_Node
	#if put_down != PLAYERTOOLS.NONE:
	#	if put_down == PLAYERTOOLS.CLEANING_TOOL:
	Bomb_Node = load("res://entities/objects/bomb/Bomb.tscn")
		
	var bomb_instance = Bomb_Node.instantiate()
	audio_player.stream = load("res://sfx/ncl_bomb_drop.ogg")
	audio_player.volume_db = -3
	audio_player.pitch_scale = randf_range(0.86, 0.93)
	audio_player.play()
		
		
	level.add_child(bomb_instance)
	var movedir = direction_to_vector(mesh_direction)
	bomb_instance.position = position + movedir * step_size
	
	bomb_instance.position.y = position.y
	bomb_instance._ready()
		
	bomb_instance.local_tween_position = bomb_instance.position
	
	enable_movement = true	
		

func put_tool_down(put_down):
	tool_changing = true
	enable_movement = false	
	await get_tree().create_timer(tool_change_time).timeout
	tool_changing = false
	var Tool_Node
	if put_down != PLAYERTOOLS.NONE:
		if put_down == PLAYERTOOLS.CLEANING_TOOL:
			Tool_Node = load("res://entities/objects/tools/CleaningToolObject.tscn")
		elif put_down == PLAYERTOOLS.PUSH_TOOL:
			Tool_Node = load("res://entities/objects/tools/PushToolObject.tscn")
		elif put_down == PLAYERTOOLS.PULL_TOOL:
			Tool_Node = load("res://entities/objects/tools/PullToolObject.tscn")
		elif put_down == PLAYERTOOLS.GRINDING_TOOL:
			Tool_Node = load("res://entities/objects/tools/GrindingToolObject.tscn")
		else:
			print ("error: no tool???, put_down == " + str(put_down))
			return
		var tool_instance = Tool_Node.instantiate()
		audio_player.stream = load("res://sfx/ncl_tool.ogg")
		audio_player.volume_db = 6
		audio_player.pitch_scale = randf_range(0.86, 0.93)
		audio_player.play()
		#var game = SceneManager.game_scene
		#var level = game.level_scene_instance
		
		level.add_child(tool_instance)
		var movedir = direction_to_vector(mesh_direction)
		tool_instance.position = position + movedir * step_size
		tool_instance._ready()
		tool_instance.position.y = position.y
		
		tool_instance.local_tween_position = tool_instance.position
	
		enable_movement = true	
		
func try_take_bomb():
	if is_grid_aligned():
		var frontObject = ray_front.get_collider()
		if frontObject != null and frontObject.is_in_group("bomb_item"):
			frontObject.take_bomb()
			bomb_counter.add_bomb()

func update_tool():

	if protection_area.has_overlapping_areas():
		#if a movable has it's PlacementProtectionArea (at front) at Monitorable, then no item can be placed
		#default false for this area, but used for enemies
		return

	if tool_changing == true:
		return
	var lying_tool = null
	var put_down = ptm.active_tool
	var can_put_down = true
	#do it with the old way when grid aligned
	if is_grid_aligned():
		if ray_front.is_colliding():
			
			var collided_with = ray_front.get_collider()
			if collided_with.is_class("GridMap"):
				return
		
		#step 1: check if there is a tool detected lying on the ground
		if ray_front.is_colliding():
			var frontObject = ray_front.get_collider()
			if frontObject != null: #fixme: maybe check if it is really a tool, has tool_type
				if frontObject.is_in_group("tool") and frontObject.has_method("take_tool"):
					lying_tool = frontObject
				
				elif frontObject.get_parent().is_in_group("tool") and frontObject.get_parent().has_method("take_tool"):
					lying_tool = frontObject.get_parent()
				else:
					can_put_down = false
		elif ray_downramp.is_colliding():
			var frontObject = ray_downramp.get_collider()
			if frontObject != null: #fixme: maybe check if it is really a tool, has tool_type
				if frontObject.is_in_group("tool"):
					lying_tool = frontObject
				
				elif frontObject.get_parent().is_in_group("tool") and frontObject.get_parent().has_method("take_tool"):
					lying_tool = frontObject.get_parent()
	
		if put_down == PLAYERTOOLS.NONE: #player has no tool to put down
			if lying_tool != null:
				lying_tool.take_tool()
				ptm.set_active_tool(lying_tool.tool_type + 1)
		else: #player has a tool:
			
			if can_put_down == false or (requested_direction != DIRECTIONS.NONE and lying_tool == null):

				return
			if lying_tool == null:
				put_tool_down(put_down)
				ptm.set_active_tool(PLAYERTOOLS.NONE)
			else:
				lying_tool.take_tool()
				
				ptm.set_active_tool(lying_tool.tool_type + 1)
				put_tool_down(put_down)
				
	else:

		var distant_object = level.find_object_at_position(self, distant_target_position)
		if distant_object != null:
			if distant_object.is_in_group("tool") and distant_object.has_method("take_tool") and distant_object_to_take == null:
				distant_object_to_take = distant_object
		if put_down != PLAYERTOOLS.NONE:	
			distant_tool_to_place = put_down
				

func _try_push(object, direction):
	if push(object, direction) == true:
		audio_player.stream = load("res://sfx/ncl_object_move.ogg")
		audio_player.volume_db = -9
		audio_player.pitch_scale = randf_range(0.8, 0.8)
		#audio_player.play() #fixme improve movement sfx
		
	
func _handle_short_direction_change_impulse():
	if tool_changing:
		return
	if is_grid_aligned() and last_keys_pressed_amount == 0:
		if Input.is_action_just_pressed("ui_right"):
			requested_mesh_direction = DIRECTIONS.RIGHT
		elif Input.is_action_just_pressed("ui_left"):
			requested_mesh_direction = DIRECTIONS.LEFT
		elif Input.is_action_just_pressed("ui_up"):
			requested_mesh_direction = DIRECTIONS.UP
		elif Input.is_action_just_pressed("ui_down"):
			requested_mesh_direction = DIRECTIONS.DOWN
		else:
			requested_mesh_direction = DIRECTIONS.NONE
			
		if requested_mesh_direction != DIRECTIONS.NONE:
			if mesh_direction == requested_mesh_direction:
				#the player is facing in the direction it moves, skip short direction change impulse
				return
		

			enable_movement = false
			await get_tree().create_timer(0.06).timeout
			enable_movement = true
			
			update_rotation_point(requested_mesh_direction)
			#requested_direction = requested_mesh_direction
			
		

		
func _handle_movement_input():
	if !enable_movement or tool_changing:
		return
	if Input.is_action_just_pressed("ui_right"):
		requested_direction = DIRECTIONS.RIGHT
	elif Input.is_action_just_pressed("ui_left"):
		requested_direction = DIRECTIONS.LEFT
	elif Input.is_action_just_pressed("ui_up"):
		requested_direction = DIRECTIONS.UP
	elif Input.is_action_just_pressed("ui_down"):
		requested_direction = DIRECTIONS.DOWN
			
	
	elif Input.is_action_pressed("ui_right") and requested_direction == DIRECTIONS.RIGHT:
		requested_direction = DIRECTIONS.RIGHT
	elif Input.is_action_pressed("ui_left") and requested_direction == DIRECTIONS.LEFT:
			requested_direction = DIRECTIONS.LEFT
	elif Input.is_action_pressed("ui_up") and requested_direction == DIRECTIONS.UP:
		requested_direction = DIRECTIONS.UP
	elif Input.is_action_pressed("ui_down") and requested_direction == DIRECTIONS.DOWN:
		requested_direction = DIRECTIONS.DOWN
		
	elif Input.is_action_pressed("ui_right"):
		requested_direction = DIRECTIONS.RIGHT
	elif Input.is_action_pressed("ui_left"):
		requested_direction = DIRECTIONS.LEFT
	elif Input.is_action_pressed("ui_up"):
		requested_direction = DIRECTIONS.UP
	elif Input.is_action_pressed("ui_down"):
		requested_direction = DIRECTIONS.DOWN

	else:
		requested_direction = DIRECTIONS.NONE
		
	
func _handle_action_input():
	if !enable_movement:
		return
	if Input.is_action_just_pressed("change_tool"):
		update_tool()
	if Input.is_action_just_pressed("bomb"):
		put_bomb_down()
	
	if OS.is_debug_build():
		if Input.is_action_just_pressed("change_tool_debug"):
			if ptm.active_tool == PLAYERTOOLS.NONE:
				ptm.set_active_tool(PLAYERTOOLS.CLEANING_TOOL)
			elif ptm.active_tool == PLAYERTOOLS.CLEANING_TOOL:
				ptm.set_active_tool(PLAYERTOOLS.PUSH_TOOL)
			elif ptm.active_tool == PLAYERTOOLS.PUSH_TOOL:
				ptm.set_active_tool(PLAYERTOOLS.PULL_TOOL)
			elif ptm.active_tool == PLAYERTOOLS.PULL_TOOL:
				ptm.set_active_tool(PLAYERTOOLS.GRINDING_TOOL)
			elif ptm.active_tool == PLAYERTOOLS.GRINDING_TOOL:
				ptm.set_active_tool(PLAYERTOOLS.NONE)
			else:
				ptm.set_active_tool(PLAYERTOOLS.CLEANING_TOOL)
		if Input.is_action_just_pressed("debug_add_bomb"):
			bomb_counter.add_bombs(5)
	
			

	#pushing and other front collision handling (like running into enemies)
	
	if requested_direction != DIRECTIONS.NONE and ray_front.is_colliding() and is_grid_aligned():
		var frontObject = ray_front.get_collider()
		var local_movedir = direction_to_vector(requested_direction)
		var next_target_pos = position + local_movedir * step_size
		if frontObject != null:
			var _distance = (next_target_pos - frontObject.position).length()
			
			#tool or regular + push tool
			if frontObject.is_in_group("tool") or ((frontObject.is_in_group("pushable") and !frontObject.is_in_group("heavy") and ptm.active_tool == PLAYERTOOLS.PUSH_TOOL)):
				if _distance < 1:
					modify_speed_timed(1.15)
				_try_push(frontObject, requested_direction)
			
			#regular
			elif frontObject.is_in_group("pushable") and !frontObject.is_in_group("heavy"):
				if _distance < 1:
					modify_speed_timed(1.45)
				_try_push(frontObject, requested_direction)
				
			#heavy and push tool
			elif frontObject.is_in_group("pushable") and ptm.active_tool == PLAYERTOOLS.PUSH_TOOL:
				if _distance < 1:
					modify_speed_timed(1.75)
				_try_push(frontObject, requested_direction)
				
			#tool or regular + push tool
			elif frontObject.get_parent().is_in_group("tool") or ((frontObject.get_parent().is_in_group("pushable") and !frontObject.get_parent().is_in_group("heavy") and ptm.active_tool == PLAYERTOOLS.PUSH_TOOL)):
				if _distance < 1:
					modify_speed_timed(1.15)
				_try_push(frontObject.get_parent(), requested_direction)
				
			#regular	
			elif frontObject.get_parent().is_in_group("pushable") and !frontObject.is_in_group("heavy"):
				if _distance < 1:
					modify_speed_timed(1.45)
				_try_push(frontObject.get_parent(), requested_direction)
			
			#heavy and push tool
			elif frontObject.get_parent().is_in_group("pushable") and ptm.active_tool == PLAYERTOOLS.PUSH_TOOL:
				if _distance < 1:
					modify_speed_timed(1.75)
				_try_push(frontObject.get_parent(), requested_direction)
				
			elif frontObject.is_in_group("enemy") and _distance < 1:
				hit()
			elif frontObject.get_parent().is_in_group("grindable") and ptm.active_tool == PLAYERTOOLS.GRINDING_TOOL:
				ptm.play_animation()
				frontObject.get_parent().call_deferred("queue_free")
				
			
		
			
		else:
			print ("error: front raycast hit null object")
	elif requested_direction != DIRECTIONS.NONE and is_grid_aligned():
		var push_down = get_push_down_ramp_object()
		if push_down != null:
			var local_movedir = direction_to_vector(requested_direction)
			var next_target_pos = position + local_movedir * step_size
			var _distance = (next_target_pos - push_down.position).length()
			if _distance < 1:
				modify_speed_timed(1.15)
				_try_push(push_down, requested_direction)
				
		
	if requested_direction != DIRECTIONS.NONE and ray_back.is_colliding() and is_grid_aligned():
		
		if Input.is_action_pressed("action") and can_move_to(requested_direction) and can_pull:
			var backObject = ray_back.get_collider()
			if backObject != null:
				
				if backObject.is_in_group("pullable") and ptm.active_tool == PLAYERTOOLS.PULL_TOOL and backObject.can_be_pulled_to(requested_direction):
					#modify_speed_timed(2) #this is buggy right now, so I disabled it
				
					backObject.print_pull_collision()
					backObject.move_to(requested_direction, true)
					ptm.play_start_animation()
					#audio_player.stream = load("res://sfx/ncl_object_move.ogg")
					#audio_player.volume_db = -3
					#audio_player.pitch_scale = randf_range(0.86, 0.93)
					#audio_player.play()
					
					requested_mesh_direction = get_opposite_direction(requested_direction)
					
				
			else:
				print ("error: back raycast hit null object")

	
	if Input.is_action_just_released("action")  and ptm.active_tool == PLAYERTOOLS.PULL_TOOL:
		var backObject = ray_back.get_collider()
		if backObject != null and backObject.is_in_group("pullable"):
			ptm.play_end_animation()
	if Input.is_action_just_pressed("action")  and ptm.active_tool == PLAYERTOOLS.PULL_TOOL:
		var backObject = ray_back.get_collider()
		if backObject != null and backObject.is_in_group("pullable"):
			ptm.play_start_animation()
			#audio_player.stream = load("res://sfx/ncl_object_move.ogg")
			#audio_player.volume_db = -3
			#audio_player.pitch_scale = randf_range(0.86, 0.93)
			#audio_player.play()
	if ray_front.is_colliding() and (Input.is_action_just_pressed("change_tool") or Input.is_action_just_pressed("action")):
		if is_grid_aligned():
			var frontObject = ray_front.get_collider()
			if frontObject != null and frontObject.is_in_group("bomb_item"):
				frontObject.take_bomb()
				bomb_counter.add_bomb()
		else:
			var distant_object = level.find_object_at_position(self, distant_target_position)
			if distant_object != null:
				if distant_object.is_in_group("bomb_item") and distant_object.has_method("take_bomb") and distant_object_to_take == null:
					distant_object_to_take = distant_object
	
	

func handle_player_input():
	
	if !enable_input:
		return
	#when a key is only pressed for a short time, no movement is applied by blocking movement with enable_movement = false, only direction changes
	_handle_short_direction_change_impulse()
	_handle_movement_input()

	_handle_action_input()
	
	last_keys_pressed_amount = get_keys_pressed_amount()	



func _on_Health_no_health() -> void:
	if SceneManager.game_scene.is_finished:
		#no gameover when the level is already finished :-)
		return
	print ("you died")
	
	Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
	get_tree().change_scene_to_file("res://UI/GameOverScreen.tscn")





func wait_after_puddle_cleaned():
	await get_tree().create_timer(0.0666666666667).timeout
	enable_movement = true


