extends CharacterBody2D

const SPEED = 500.0
const ACCEL = 7.0
var direction = 0
var addedveloc = 0
var forcedVeloc = 0

const JUMP_VELOCITY = -450.0
const MAX_JUMPS = 1
var jumps = MAX_JUMPS

var wallKayote = 0

const FALL_SPEED = -JUMP_VELOCITY 
var falling = false

var floorTime = 0
var kayote = 0

var gravity = ProjectSettings.get_setting("physics/2d/default_gravity")

var checkpointPos = Vector2.ZERO

@export var ghostMode = false
var ghostFrame = 0
var startPos = Vector2.ZERO

var can_move = true

var camera : Camera2D

@export var savedInputsPath : String

@onready var sceneReload = load(get_tree().current_scene.scene_file_path)

var savedInputs = []

var wallPower = 0

var squishers = -1

func actionPressed(inp):
	if ghostMode:
		if inp in savedInputs[ghostFrame]:
			return true
		return false
	else:
		return Input.is_action_pressed(inp)

func actionJustPressed(inp):
	if ghostMode:
		if inp in savedInputs[ghostFrame] and not inp in savedInputs[ghostFrame - 1]:
			return true
		return false
	else:
		return Input.is_action_just_pressed(inp)

func getAxis(inp1, inp2):
	if ghostMode:
		var dir = 0
		if actionPressed("left"):
			dir -= 1
		if actionPressed("right"):
			dir += 1
		return clamp(dir, -1, 1)
	else:
		return Input.get_axis(inp1, inp2)

func isWallSliding():
	for collisionNumb in get_slide_collision_count():
		var collision = get_slide_collision(collisionNumb)
		var object = collision.get_collider()
		
		if object is TileMap:
			var tileMap : TileMap = object
			var tilePos = tileMap.get_coords_for_body_rid(collision.get_collider_rid())
			var tileData = tileMap.get_cell_tile_data(0, tilePos)
			
			if tileData and not tileData.get_custom_data("Slidable") == true:
				return false
	return is_on_wall_only() and direction

func getGravMulti():
	var axis = (gen.boolToNumb(actionPressed("up") or forceLowGrav, 1))
	if velocity.y < 0:
		return axis
	return 1

func launch(veloc):
	addedveloc = veloc
	velocity.x = veloc

func forceLaunch(veloc):
	forcedVeloc = veloc
	velocity.x = veloc

signal Jumped
signal Died

var deathParticles = preload("res://Particles/Player/deathParticles.tscn")

var doubleWallJumped = false

var forceLowGrav = true

var landed = false

func die():
	Died.emit()
	var parti = deathParticles.instantiate()
	parti.modulate = $Sprite.self_modulate
	add_child(parti)
	can_move = false
	$Sprite.visible = false
	
	gen.savedPos = checkpointPos
	
	await Camera.deathAnim().finished
	
	get_tree().change_scene_to_packed(sceneReload)

func _ready():
	RenderingServer.set_default_clear_color(Color8(0, 0, 0))
	if find_child("Camera"):
		camera = $Camera
	
	if ghostMode:
		startPos = global_position
		var rc = load(savedInputsPath)
		
		if rc:
			var inputKeyRaw = rc["actions"]
			var frameDataRaw = rc["data"]
			
			var inputKey = []
			var frameData = {}
			
			for action in inputKeyRaw:
				inputKey.append(action)
			
			for frame in frameDataRaw:
				frameData[frame] = {}
				for x in frameDataRaw[frame]:
					frameData[frame][x] = frameDataRaw[frame][x]
			var frameCount = gen.getRecordingFrameCount(frameData)
			
			var held = []
			
			for frame in range(frameCount):
				savedInputs.append([])
				if frameData.has(frame):
					for actID in frameData[frame]:
						var pressed = frameData[frame][actID]
						var inputName = inputKey[actID]
						if pressed == 1:
							held.append(inputName)
						elif inputName in held:
							held.remove_at(held.find(inputName))
				savedInputs[frame] += held
	else:
		Camera.player = self
		var parti = deathParticles.instantiate()
		parti.modulate = $Sprite.self_modulate
		add_child(parti)
		$Sprite.visible = true
		$"../Smoother".reset_node(self)
		#camera.position_smoothing_enabled = true
		if gen.savedPos != Vector2.ZERO:
			global_position = gen.savedPos
		gen.savedPos = Vector2.ZERO
		velocity = Vector2(0, 0)
		#camera.reset_smoothing()

@onready var trail = $"Sprite/Trail"
var ghosting = true

var ghostParti = preload("res://Particles/Ghosts/spawnParticles.tscn")

var lastFloor = null

func spawnParti():
	var parti = ghostParti.instantiate()
	add_child(parti)
	parti.top_level = true
	parti.global_position = global_position

func _physics_process(delta):
	if ghostFrame + 1 >= savedInputs.size() and ghostMode:
		ghostFrame = 0 
		trail.points = []
		$Sprite.visible = false
		ghosting = false 
		spawnParti()
		global_position = startPos
		velocity = Vector2.ZERO
		await get_tree().create_timer(3).timeout
		spawnParti()
		ghosting = true
		$Sprite.visible = true
	
	if (ghosting or !ghostMode) and can_move:
		var space_state = get_world_2d().direct_space_state
		
		if ghostFrame == 0:
			spawnParti()
		ghostFrame += 1
		
		direction = round(getAxis("left", "right"))
	
		wallKayote -= delta
		
		if is_on_floor() and !landed:
			velocity.y = 0
		
		if not is_on_floor():
			if velocity.y < 2000:
				velocity.y += gravity * delta / getGravMulti()
			
			floorTime = 0
			kayote -= delta
			if lastFloor:
				if "velocity" in lastFloor:
					velocity += lastFloor.velocity
				lastFloor = null
		else:
			#velocity.y = 0
			doubleWallJumped = false
			kayote = 0.3
			floorTime += delta
			jumps = MAX_JUMPS
			falling = false
			
			
			for collisionNumb in get_slide_collision_count():
				var collision = get_slide_collision(collisionNumb)
				var object = collision.get_collider()
				
				if abs(collision.get_angle()) <= floor_max_angle:
					lastFloor = object
		
		if velocity.y <= 0:
			forceLowGrav = false
		
		if isWallSliding():
			wallKayote = 0.3
			falling = false
		
		if actionJustPressed("respawn") or position.y > 5000:
			die()
		
		if actionJustPressed("jump"):
			if wallKayote > 0 and not is_on_floor():
				if doubleWallJumped:
					wallKayote = 0
				else:
					doubleWallJumped = true
					wallKayote += 0.1
			
				if actionPressed("down"):
					launch(get_wall_normal().x * SPEED * 1.75)
					velocity.y = clamp((velocity.y / 2) + JUMP_VELOCITY / 1.8, -INF, JUMP_VELOCITY / 1.8)
				else:
					launch(get_wall_normal().x * SPEED * 1.4)
					velocity.y = clamp(velocity.y + JUMP_VELOCITY, -INF, JUMP_VELOCITY)
				Jumped.emit()
				
				#if result and "velocity" in result.collider:
					#velocity.x += result.collider.velocity.x
			elif jumps > 0:
				velocity.y = clamp(velocity.y + JUMP_VELOCITY, -INF, JUMP_VELOCITY)
				doubleWallJumped = false
				if kayote < 0:
					jumps -= 1
				else:
					var query = PhysicsRayQueryParameters2D.create(global_position + (Vector2.UP * 32), global_position + (Vector2.DOWN * 64))
					query.exclude = [self]
					var result = space_state.intersect_ray(query)
					
					if result and "velocity" in result.collider:
						velocity += result.collider.velocity
				
				falling = false
				if actionPressed("down") and kayote > 0 and abs(velocity.x) < SPEED * 1.5:
					launch(direction * SPEED * 1.5)
				kayote = 0
				Jumped.emit()
		
		if actionJustPressed("down") and not falling and not isWallSliding():
			falling = true
			velocity.y = clamp(velocity.y + FALL_SPEED, FALL_SPEED, INF)
		
		if isWallSliding():
			var upDown = clamp(getAxis("up", "down"), -0.5, 1)
			var holdMulti = (upDown * 2) + 1
			
			var query = PhysicsRayQueryParameters2D.create(global_position + (get_wall_normal() * 32), global_position + (get_wall_normal() * -64))
			query.exclude = [self]
			var result = space_state.intersect_ray(query)

			if result and "velocity" in result.collider and abs(result.collider.velocity.x) > 50:
				velocity.y = lerpf(velocity.y, 0, delta * 30)
			else:
				velocity.y = lerpf(velocity.y, clamp(velocity.y, JUMP_VELOCITY, 100 * holdMulti), delta * 10)
		
		var finalSpeed = clamp(abs(velocity.x), SPEED, INF) 
		if floorTime > 0.05:
			finalSpeed = clamp(lerp(finalSpeed, SPEED, delta * 20), SPEED, INF)
		
		velocity.x = lerp(velocity.x, direction * finalSpeed, delta * ACCEL)
		
		if abs(velocity.x) < abs(addedveloc):
			addedveloc = lerp(addedveloc, velocity.x, ACCEL * 2 * delta)
			velocity.x += addedveloc * delta * 15
		
		if abs(velocity.x) < abs(forcedVeloc):
			forcedVeloc = lerp(forcedVeloc, velocity.x, ACCEL * 2 * delta)
			velocity.x += forcedVeloc * delta * 35
			
		addedveloc = lerpf(addedveloc, 0, delta * 6)
		if not is_on_floor():
			forcedVeloc = lerpf(forcedVeloc, 0, delta)
		else:
			forcedVeloc = lerpf(forcedVeloc, 0, delta * 5)

		landed = is_on_floor()
		move_and_slide()

func _process(_delta):
	if ghostMode:
		if velocity.length() > 5:
			trail.add_point(global_position)
		elif trail.get_point_count() > 0:
			trail.remove_point(0)
		
		if trail.get_point_count() > 100:
			trail.remove_point(0)


func _on_squish_detection_body_shape_entered(_body_rid, _body, _body_shape_index, _local_shape_index):
	squishers += 1
	if squishers >= 2:
		die()


func _on_squish_detection_body_shape_exited(_body_rid, _body, _body_shape_index, _local_shape_index):
	squishers -= 1
