tool

extends TileMap

class_name Level

export (PackedScene) var next_level

onready var _floor := $Floor as TileMap
onready var _props := $Props as TileMap
onready var _waste := $Waste as TileMap
onready var _player := $Player as Player
onready var _stairs := $Stairs as AnimatedSprite
onready var _reservoir := $Reservoir as AnimatedSprite
onready var _wastesound := $Waste/Sound as AudioStreamPlayer

export var start_x := 0
export var start_y := 0

export var stairs_x := 0
export var stairs_y := 0

var completed := false
var initial_state

func _ready():
	_player.level = self
	_player.tile_position = Vector2(start_x, start_y)
	_stairs.visible = false
	_stairs.modulate = Color.white
	initial_state = {
		waste = {},
		reservoir = _player.reservoir
	}
	for w in _waste.get_used_cells():
		initial_state.waste[w] = _waste.get_cellv(w)

func _process(_delta):
	
	if Engine.editor_hint:
		
		if not _floor is TileMap:
			_floor = $Floor as TileMap
		
		if not _player is Player:
			_player = $Player as Player
		
		player_initial_position()		
		
		if not _stairs is AnimatedSprite:
			_stairs = $Stairs as AnimatedSprite
			
			
		_stairs.position = _floor.map_to_world(Vector2(stairs_x, stairs_y))
		
		if not _reservoir is AnimatedSprite:
			_reservoir = $Reservoir as AnimatedSprite
			
		
		var self_cells = self.get_used_cells()
		if not self_cells.empty():
			_floor.clear()
			for cell in self_cells:
				_floor.set_cellv(cell, self.get_cellv(cell))
			self.clear()
			
		
		update_reservoir()


class Tile:
	var x :int
	var y :int
	var level :Level
	
	func get_tile(var tile_map :TileMap) -> int:
		return tile_map.get_cell(x, y)
		
	func has_tile(var tile_map :TileMap) -> bool:
		return get_tile(tile_map) >= 0
	
	func set_tile(var tile_map :TileMap, var tile :int) -> void:
		tile_map.set_cell(x, y, tile)
	
	func swap_tile(var tile_map:TileMap, var other :Tile) -> void:
		var tile := other.get_tile(tile_map)
		other.set_tile(tile_map, self.get_tile(tile_map))
		self.set_tile(tile_map, tile)
	
	func has_floor() -> bool:
		return has_tile(level._floor)
		
	func has_prop() -> bool:
		return has_tile(level._props)
		
	func has_waste() -> bool:
		return has_tile(level._waste)
		
	func is_stairs():
		return level.completed and (x == level.stairs_x or x == level.stairs_x - 1) and (y == level.stairs_y or y == level.stairs_y - 1)
		
	func is_stairs_entrance():
		return level.completed and x == level.stairs_x - 1 and y == level.stairs_y - 1
		
	func can_walk(var direction := Vector2.ZERO) -> bool:
		if is_stairs_entrance() and direction == Vector2.DOWN:
			return true
		return has_floor() and not has_prop() and not has_waste() and not is_stairs()
		
	func get_next_tile(var direction :Vector2) -> Tile:
		return level.get_tile(x + direction.x, y + direction.y)
		
	func can_push(var direction :Vector2) -> bool:
		if has_waste():
			return get_next_tile(direction).can_push(direction)
		elif has_prop() or is_stairs():
			return false
		elif has_floor():
			return true
		else:
			return false
	
	func get_waste_line(var direction :Vector2, var continuous := false, var max_tiles := -1) -> Array:
		var waste_line := []
		if max_tiles != 0 and (has_waste() and has_floor() or continuous and can_walk()):
			waste_line = get_next_tile(direction).get_waste_line(direction, continuous, max_tiles - 1)
		if has_waste():
				waste_line.push_front(self)
		return waste_line
		
	func push(var direction :Vector2) -> void:
		if can_push(direction):
			var waste_line := get_waste_line(direction)
			if not waste_line.empty():
				var last = waste_line.pop_back()
				last.push_r(direction, waste_line)
	
	func push_r(var direction: Vector2, var waste_line := []) -> void:
		var empty_tile = get_next_tile(direction)
		swap_tile(level._waste, empty_tile)
		if not waste_line.empty():
			waste_line.pop_back().push_r(direction, waste_line)
		return
		
	func dash(var direction: Vector2, var tile_limit = INF, var waste_limit = INF) -> Tile:
		var waste_line = get_waste_line(direction, true, tile_limit)
		waste_line.resize(min(waste_limit, waste_line.size()))
		for tile in waste_line:
			tile.set_tile(level._waste, -1)
		level.check_completed()
		level.waste_sound(waste_line.size())
		return waste_line.back() if not waste_line.empty() else self

func get_tile_v(var position :Vector2) -> Tile:
	return get_tile(position.x, position.y)
	
func get_tile(var x :int, var y :int) -> Tile:
	var tile = Tile.new()
	tile.x = x
	tile.y = y
	tile.level = self
	return tile
	
func check_completed() -> void:
	var left = _waste.get_used_cells()
	if left.empty():
		show_stairs()

func show_stairs() -> void:
	_stairs.frame = 0
	_stairs.visible = true
	_stairs.play()
	($Stairs/Sound as AudioStreamPlayer).play()
	completed = true

func update_reservoir() -> void:
	_reservoir.frame = _player.reservoir
	
func waste_sound(var number :int):
	for _i in range(number):
		_wastesound.play()
		yield(_wastesound, "finished")

func player_initial_position():
	_player.level = self
	_player.tile_position = Vector2(start_x, start_y)
	_player.update_position()
	if not Engine.editor_hint:
		_player.current_direction = _player.movements.back()
		_player.update_animation()

func reset():
	if not completed:
		
		_waste.clear()
		for w in initial_state.waste:
			_waste.set_cellv(w, initial_state.waste[w])
		
		player_initial_position()
		
		_player.reservoir = initial_state.reservoir
		update_reservoir()

func end_of_level():
	_player.visible = false
	remove_child(_player)
	var downstairs = $Stairs/Downstairs as AnimatedSprite
	downstairs.visible = true
	downstairs.play()
	downstairs.connect("animation_finished", self, "next_level")
	
func next_level():
	if next_level:
		get_parent().add_child(next_level.instance())
	get_parent().remove_child(self)
