extends Area2D
# Handle player logic and events

signal crash
signal arena_removed
signal drift_started
signal drift_ended

enum Direction {
	UP = 0
	RIGHT = 1
	DOWN = 2
	LEFT = 3
}

enum Side {
	LEFT = -1
	RIGHT = 1
}

const cell_size = 64
const cell_half_size = 32
const LEVEL_SPAWN_DURATION = 2

# State
var running = false
var drifting = false
var landed = false

# Movement
var posix
var posiy

var dirx = 0
var diry = 0

var dire = 0
var dire_delta = 0

var target_pos

# Controls
var turn_left_action:String
var turn_right_action:String

var grid:TileMap

onready var tween = $Tween


func _ready():
	assert(turn_left_action)
	assert(turn_right_action)

	tween.connect_into(self)


func _unhandled_input(event):
	if event.is_pressed():
		if event.is_action(turn_left_action):
			prepare_turn(Side.LEFT)
			get_tree().set_input_as_handled()
		elif event.is_action(turn_right_action):
			prepare_turn(Side.RIGHT)
			get_tree().set_input_as_handled()


func _on_round_start():
	assert(grid)
	run()


func _on_round_won():
	stop()


func _on_arena_removed():
	grid = null
	landed = false
	emit_signal("arena_removed")


func _on_tween_completed(_o, key):
	if (key == ":position"):
		move()


func _on_crash(body):
	if landed and running:
		stop()
		emit_signal("crash")


func spawn(newGrid, newPosition, newOrientation):
	# Update arena
	grid = newGrid

	# Adjust spawn situation
	dire_delta = 0
	posix = int (newPosition.x / cell_size)
	posiy = int (newPosition.y / cell_size)
	dire = int((newOrientation + 45) / 90)
	apply_turn()

	# Animate spawning
	tween.rotate_char(self, rotation_degrees, newOrientation, LEVEL_SPAWN_DURATION)
	tween.move_char(self, newPosition, LEVEL_SPAWN_DURATION)
	tween.start()


func generate_wall():
	# TODO : use enum for blocks
	grid.set_cell(posix - dirx, posiy - diry, 1)


func prepare_turn(left_or_right:int):
	if !running:
		return

	var current_angle = (dire + dire_delta) * 90

	dire_delta += left_or_right
	if dire_delta > Side.RIGHT:
		dire_delta = Side.RIGHT
		return
	elif dire_delta < Side.LEFT:
		dire_delta = Side.LEFT
		return

	var aim_angle = current_angle + 90 * left_or_right

	tween.rotate_char(self, current_angle, aim_angle)
	tween.start()


func can_turn():
	return !has_block_on(dire_delta)


func apply_turn():
	dire += dire_delta
	dire_delta = 0

	if dire < 0:
		dire = 3
	elif dire > 3:
		dire = 0

	dirx = 0
	diry = 0
	if dire == Direction.UP:
		diry -= 1
	elif dire == Direction.RIGHT:
		dirx += 1
	elif dire == Direction.DOWN:
		diry += 1
	elif dire == Direction.LEFT:
		dirx -= 1
	else:
		push_error("dire out of range")


func apply_drift():
	if !drifting:
		drifting = true
		emit_signal("drift_started")


func end_drift():
	if drifting:
		drifting = false
		emit_signal("drift_ended")


func move():
	if !running:
		return

	generate_wall()

	if is_transversal():
		if can_turn():
			end_drift()
			apply_turn()
		else:
			apply_drift()
	else:
		end_drift()

	go_forward()


func go_forward():
	posix += dirx
	posiy += diry

	target_pos = Vector2(posix * cell_size + cell_half_size, posiy * cell_size + cell_half_size)
	tween.move_char(self, target_pos)
	tween.start()


func has_block_on(left_or_right:int):
	var bposx:int = posix - diry * left_or_right
	var bposy:int = posiy + dirx * left_or_right

	# TODO : use enum for blocks
	return grid.get_cell(bposx, bposy) == 1


func stop():
	if running:
		remove_from_group("running")
		generate_wall()
	running = false


func run():
	landed = true # TODO : land at game signal `land`
	running = true
	add_to_group("running")
	move()


func is_running():
	return running

func is_transversal():
	return dire_delta != 0