extends KinematicBody2D

export (bool) var is_interactive = true
export (bool) var has_salt = false
export (int) var max_speed = 200
export (int) var jump_speed = 380
export (int) var gravity = 980
export (float, 0, 1.0) var friction = 0.2
export (float) var acceleration = 15
export (int, 0, 200) var push = 100
export (bool) var squash_and_stretch = false
export (bool) var single_viewport_when_near = false

onready var world = get_parent()
onready var animated_sprite = $AnimatedSprite
onready var tile_map = get_parent().get_node("TileMap")
onready var tile_map_bg = get_parent().get_node("TileMapBg")
onready var starch = get_parent().get_node("Starch")

var separation = 2

var camera = null
var camera_frozen = false

var other_player = null
var viewport_container_both = null
var frozen_view = null
var frozen_view_sprite = null
var frozen_view_sprite_bg = null
var frozen_colliders = null
var frozen_image = null
var frozen_image_width = 600
var frozen_image_height = 600
var frozen_image_texture = null

var is_complete = false
var velocity = Vector2.ZERO
var direction = 1
var return_position = null
var initial_has_salt = false

# For buffered jump and coyote time:
var jump_was_pressed = false
var was_grounded = false

# For squash and stretch:
var previous_velocity = Vector2.ZERO
var hit_the_ground = false

func reset():
	is_complete = false
	velocity = Vector2.ZERO
	direction = 1
	return_position = null
	has_salt = initial_has_salt

	jump_was_pressed = false
	was_grounded = false
	previous_velocity = Vector2.ZERO
	hit_the_ground = false

func _ready():
	# frozen_image = Image.new()
	# frozen_image.create(frozen_image_width, frozen_image_height, false, Image.FORMAT_RGB8)
	# frozen_image_texture = ImageTexture.new()
	
	initial_has_salt = has_salt
	if initial_has_salt:
		add_salt()
	else:
		remove_salt()

var min_int = -9223372036854775808
var max_int = 9223372036854775807
var min_position = Vector2(max_int, max_int)
var max_position = Vector2(min_int, min_int)
var cell_width = 32
var cell_height = 32

func render_frozen_tile_map(maps):
	for map in maps:
		var rect = map.get_used_rect()
		if rect.position < min_position:
			min_position = rect.position
		if rect.end > max_position:
			max_position = rect.end

	var image_width = (max_position.x - min_position.x) * cell_width
	var image_height = (max_position.y - min_position.y) * cell_height

	frozen_image = Image.new()
	frozen_image.create(image_width, image_height, false, Image.FORMAT_RGB8)
	frozen_image_texture = ImageTexture.new()

	for map in maps:
		# var cell_width = map.cell_size.x
		# var cell_height = map.cell_size.y
		for cell in map.get_used_cells():
			var tile_index = map.get_cell(cell.x, cell.y)
			var tile_texture = map.tile_set.tile_get_texture(tile_index)
			var tile_image = tile_texture.get_data()

			var autotile_cell = map.get_cell_autotile_coord(cell.x, cell.y)
			var offset_x = autotile_cell.x * cell_width
			var offset_y = autotile_cell.y * cell_height

			var dest_x = (cell.x * cell_width) - (min_position.x * cell_width)
			var dest_y = (cell.y * cell_height) - (min_position.y * cell_height)

			frozen_image.blit_rect(tile_image, Rect2(offset_x, offset_y, cell_width, cell_height), Vector2(dest_x, dest_y))

func render_frozen_view(maps, sprite):
	var image = Image.new()
	image.create(frozen_image_width, frozen_image_height, false, Image.FORMAT_RGB8)

	var offset_x = (300 - camera.position.x) + 0.5
	var offset_y = -150 + (600 - camera.position.y ) + (separation / 2)
	offset_x = (-min_position.x * cell_width) - offset_x
	offset_y = (-min_position.y * cell_height) - offset_y

	image.blit_rect(frozen_image, Rect2(offset_x, offset_y, frozen_image_width, frozen_image_height), Vector2(0, 0))
	
	frozen_image_texture.create_from_image(image)
	sprite.texture = frozen_image_texture
	
func add_salt():
	has_salt = true
	$Salt.visible = true

func remove_salt():
	has_salt = false
	$Salt.visible = false

var is_tile_map_cached = false
func _physics_process(delta):
	if not is_tile_map_cached and tile_map and tile_map_bg:
		render_frozen_tile_map([tile_map_bg, tile_map])
		is_tile_map_cached = true

	if is_interactive:
		if is_complete:
			animated_sprite.play("complete")
		else:
			handle_input()
			handle_freeze_camera()
			handle_motion(delta)
			handle_jumping()
			handle_collisions()
			animate(delta)
			if squash_and_stretch:
				handle_squash_and_stretch(delta)
			if single_viewport_when_near:
				handle_single_viewport()
	else:
		animated_sprite.play("walk")

func handle_single_viewport():
	var tile_size = 32
	var min_distance = 9 * tile_size
	var y_distance = abs(position.y - other_player.position.y)
	viewport_container_both.visible = y_distance <= min_distance

func handle_input():
	if Input.is_action_pressed("move_left_2"):
		velocity.x = max(velocity.x - acceleration, -max_speed)
		direction = -1
	elif Input.is_action_pressed("move_right_2"):
		velocity.x = min(velocity.x + acceleration, max_speed)
		direction = 1
	else:
		velocity.x = lerp(velocity.x, 0, friction)

func handle_freeze_camera():
	if Input.is_action_just_pressed("freeze_screen_2"):
		camera_frozen = not camera_frozen
		if camera_frozen:
			add_frozen_colliders()
			#render_frozen_view([tile_map_bg], frozen_view_sprite_bg)
			#render_frozen_view([tile_map], frozen_view_sprite)
			render_frozen_view([tile_map_bg, tile_map], frozen_view_sprite)
			camera.target = null
			
			#if starch:
			#	var delta = starch.position - position
			#	var starch_screen_position = Vector2(300, 450) + delta
			#	frozen_view.transfer_object(starch, Vector2(300, 450) + delta)
			
			if starch:
				#if starch.position.y > 300 or position.y > other_player.position.y:
				if starch.position.y > 300:
					var delta = starch.position - position
					var starch_screen_position = Vector2(300, 450) + delta
					frozen_view.transfer_object(starch, Vector2(300, 450) + delta)
				else:
					frozen_view.transfer_object(starch, starch.get_global_transform_with_canvas().get_origin())

			frozen_view.transfer_object(self, Vector2(300, 450))
		else:
			camera.target = self
			frozen_view_sprite_bg.texture = null
			frozen_view_sprite.texture = null
			# remove_frozen_colliders()
			
			if starch:
				frozen_view.return_object(starch, world)
				if starch.position.y > 300:
					var delta = starch.position - position
					#print(starch.position, delta, starch.return_position)
					if starch.position.x < 0 or starch.position.x > 600:
						starch.position = starch.return_position
					else:
						starch.position = starch.position - Vector2(300, 450) - delta + starch.return_position
				else:
					var delta = other_player.return_position - Vector2(300, 150)
					starch.position = starch.position + delta
					
			frozen_view.return_object(self, world)
			if position.y > 300:
				position = position - Vector2(300, 450) + return_position
			else:
				var delta = other_player.return_position - Vector2(300, 150)
				position = position + delta


# func render_frozen_view(maps, sprite):
# 	# frozen_image.lock()
# 	# frozen_image.fill(Color(0, 0, 0, 0))

# 	for map in maps:
# 		var cell_width = map.cell_size.x
# 		var cell_height = map.cell_size.y
# 		for cell in map.get_used_cells():
# 			var tile_index = map.get_cell(cell.x, cell.y)
# 			var tile_texture = map.tile_set.tile_get_texture(tile_index)
# 			var tile_image = tile_texture.get_data()

# 			var autotile_cell = map.get_cell_autotile_coord(cell.x, cell.y)
# 			var offset_x = autotile_cell.x * cell_width
# 			var offset_y = autotile_cell.y * cell_height

# 			var dest_x = (cell.x * cell_width) + (300 - camera.position.x) + 0.5
# 			var dest_y = (cell.y * cell_height) - 150 + (600 - camera.position.y ) + (separation / 2)
# 			frozen_image.blit_rect(tile_image, Rect2(offset_x, offset_y, cell_width, cell_height), Vector2(dest_x, dest_y))

# 	frozen_image_texture.create_from_image(frozen_image)
# 	sprite.texture = frozen_image_texture

# 	#sprite.set_region(true)
# 	#sprite.set_region_rect(Rect2(0, 300, frozen_image_width, frozen_image_height))

# 	# frozen_image.unlock()

func add_frozen_colliders():
	var cell_width = tile_map.cell_size.x
	var cell_height = tile_map.cell_size.y
	var half_cell_width = cell_width / 2
	var half_cell_height = cell_height / 2

	for cell in tile_map.get_used_cells():
		#var collider_x = (cell.x * cell_width) + half_cell_width + 300 - camera.position.x
		#var collider_y = (cell.y * cell_height) + half_cell_height + 150 - camera.position.y
		var collider_x = (cell.x * cell_width) + (300 - camera.position.x) + 0.5 + half_cell_width - separation/2
		var collider_y = (cell.y * cell_height) - 150 + (600 - camera.position.y ) + (separation / 2) + half_cell_height - separation/2
		
		if collider_y > 300:
			var rect = RectangleShape2D.new()
			
			if collider_y - half_cell_height <= 300:
				#var sobra = 300 - collider_y - half_cell_height
				var sobra = 300 - (collider_y - half_cell_height)
				var collider_height = cell_height - sobra #+ separation/2
				rect.set_extents(Vector2(half_cell_width, collider_height/2))
				collider_y = collider_y + collider_height/2 - sobra/2 - separation/2
			else:
				rect.set_extents(Vector2(half_cell_width, half_cell_height))

			var collider = CollisionShape2D.new()
			collider.shape = rect
			collider.translate(Vector2(collider_x, collider_y))
			frozen_colliders.add_child(collider)
			
			var light_occluder = LightOccluder2D.new()
			light_occluder.translate(Vector2(collider_x -half_cell_width , collider_y - half_cell_height))
			var occluder = OccluderPolygon2D.new()
			occluder.polygon = PoolVector2Array([Vector2(0,0), Vector2(cell_width,0), Vector2(cell_width,cell_height), Vector2(0,cell_height)])
			light_occluder.occluder = occluder
			frozen_colliders.add_child(light_occluder)

func handle_motion(delta):
	velocity.y += gravity * delta
	previous_velocity = velocity
	velocity = move_and_slide(velocity, Vector2.UP, false, 4, PI/4, false)

func handle_jumping():
	# Reference: https://www.youtube.com/watch?v=LeaaKRufdMc
	if Input.is_action_just_pressed("move_up_2"):
		jump_was_pressed = true
		buffered_jump_time()

	if not was_grounded and is_on_floor():
		was_grounded = true
		coyote_time()

	if jump_was_pressed and was_grounded:
		jump_was_pressed = false
		was_grounded = false
		velocity.y = -jump_speed
		animated_sprite.play("jump")

func buffered_jump_time():
	yield(get_tree().create_timer(.2), "timeout")
	jump_was_pressed = false

func coyote_time():
	yield(get_tree().create_timer(.2), "timeout")
	was_grounded = false

func handle_collisions():
	for index in get_slide_count():
		var collision = get_slide_collision(index)
		# if collision.collider.is_in_group("bodies"):
		if collision.collider is RigidBody2D:
			collision.collider.apply_central_impulse(-collision.normal * push)

func handle_squash_and_stretch(delta):
	# Reference: https://www.youtube.com/watch?v=iJx6uKqufJo

	# If in the air, squash and stretch based on y-velocity
	if not is_on_floor():
		hit_the_ground = false
		animated_sprite.scale.x = range_lerp(abs(velocity.y), 0, abs(jump_speed), 1.25, 0.75)
		animated_sprite.scale.y = range_lerp(abs(velocity.y), 0, abs(jump_speed), 0.75, 1.75)

	# If hit the ground, squash and stretch based on previous y-velocity
	if not hit_the_ground and is_on_floor():
		hit_the_ground = true
		animated_sprite.scale.x = range_lerp(abs(previous_velocity.y), 0, abs(1700), 1.2, 2.0)
		animated_sprite.scale.y = range_lerp(abs(previous_velocity.y), 0, abs(1700), 0.8, 0.5)

	# Ease sprite scaling
	# https://www.construct.net/en/blogs/ashleys-blog-2/using-lerp-delta-time-924
	animated_sprite.scale.x = lerp(animated_sprite.scale.x, 1, 1 - pow(0.01, delta))
	animated_sprite.scale.y = lerp(animated_sprite.scale.y, 1, 1 - pow(0.01, delta))

func animate(_delta):
	if is_in_air():
		if is_falling():
			animated_sprite.play("fall")
		else:
			animated_sprite.play("jump")
	elif abs(velocity.x) > acceleration:
		animated_sprite.play("walk")
	else:
		animated_sprite.play("idle")

	if direction == 1:
		animated_sprite.flip_h = false
	elif direction == -1:
		animated_sprite.flip_h = true

func is_in_air():
	return not is_on_floor()

func is_falling():
	return is_in_air() and velocity.y > 0

func _on_ConnectArea_area_entered(area):
	if area.name == "ConnectArea1" and not is_complete and not has_salt:
		is_complete = true

func _on_StarchArea_area_entered(area):
	if area.name == "ConnectArea2" and not is_complete:
		$HealSound.play()
		starch.queue_free()
		remove_salt()
