class_name Quest extends Resource

#The list of available quests
static var list : Array[Quest]


enum Status{
	OPEN,
	TAKEN,
	IN_PROGRESS,
	COMPLETED,
	FAILED,
	CLOSED
}

enum Locations{
	NESTORS_WOODS
}

class Event:
	var enemy_types: Dictionary[String, PackedScene] = {
		"goo": preload("res://templates/enemies/goo.tscn")
	}
	enum Type{
		WAIT,
		COMBAT,
		CHOICE
	}
	
	enum CombatState{
		FIGHTING,
		VICTORY,
		DEFEAT
	}
	var hidden : bool = false
	var type : Type = Type.WAIT
	var enemies : Array[String] = []
	var progress_point : float = 0
	var time : float = 1
	var time_elapsed
	var complete : bool = false
	signal completed()
	signal failed()
	
	var participants : Array = []
	var turn_queue : Array = []
	var busy_list : Array = []
	var combat_state
	var dex_speed : int
	
	func setup() -> void:
		pass
	
	func save_dict() -> Dictionary:
		var d : Dictionary = {}
		d.hidden = hidden
		d.type = type
		d.enemies = enemies
		d.progress_point = progress_point
		d.time = time
		d.time_elapsed = time_elapsed
		d.complete = complete
		return d
	
	func start(quest : Quest) -> void:
		match(type):
			Type.WAIT:
				return
			Type.COMBAT:
				combat_state = CombatState.FIGHTING
				var enemy_list = []
				for enemy_name in enemies:
					enemy_list.append(enemy_types[enemy_name].instantiate())
				quest.questview.set_questor_animation("idle")
				for enemy in enemy_list:
					enemy.flip_h()
					quest.questview.pause_setting()
					quest.questview.place_enemy(enemy, true)
					quest.questview.set_enemy_animation(enemy, "idle")
				start_combat([quest.questor.quest_sprite], enemy_list)

	func start_combat(adventurers : Array, enemies : Array) -> void:
		participants = []
		participants.append_array(adventurers)
		participants.append_array(enemies)
		time = 1.25
		time_elapsed = 0
		var c_order : Array = []
		var dex_speed = 0
		for p in participants:
			p.show_lifebar(true)
			c_order.append([p, p.stats.DEX])
			if p.stats.DEX > dex_speed:
				dex_speed = p.stats.DEX
		
		c_order.sort_custom(func(a,b): return a[1] > b[1])
		var delay = 5
		var last_time = 0
		for c in c_order:
			c[0].busy.connect(_on_busy.bind(c[0]))
			c[0].action_complete.connect(_on_combat_action_complete.bind(c[0]))
			c[0].died.connect(_on_death.bind(c[0]))
			var time = delay * dex_speed
			if c[1] != 0:
				time = delay * dex_speed / c[1]
			turn_queue.append({"combatant":c[0], "time": time - last_time})
			last_time = time
	
	func execute_attack(combatant : QuestSprite, target : QuestSprite) -> void:
		var tween = combatant.create_tween()
		tween.tween_interval(.25)
		tween.tween_callback(combatant.attack.bind(target))
	
	func add_to_turn_queue(combatant) -> void:
		#Calculate time
		var time = dex_speed / max(1, combatant.stats.DEX)
		#Walk through list to find insertion point
		var idx = -1
		for i in range(len(turn_queue)):
			if turn_queue[i].time > time:
				idx = i
				break
			else:
				time -= turn_queue[i].time
		var entry = {"combatant":combatant, "time":time}
		if idx == -1:
			turn_queue.append(entry)
		else:
			turn_queue[idx].time -= time
			turn_queue.insert(idx,entry)
	
	func _on_busy(combatant : QuestSprite) -> void:
		busy_list.append(combatant)
	
	func _on_death(killer : QuestSprite, combatant : QuestSprite) -> void:
		busy_list.erase(combatant)
		remove_from_queue(combatant)
		participants.erase(combatant)
		if killer != combatant:
			var xp = Game.calculate_kill_exp(killer, combatant)
			print("%s has earned %d exp" % [killer.name, xp])
			killer.exp += xp
		var enemy_list : Array = get_enemy_list(killer)
		if len(enemy_list) == 0:
			if killer is QuestorSprite:
				victory()
			else:
				defeat()
	
	func victory():
		print("Questor won!")
		combat_state = CombatState.VICTORY
		for p : QuestorSprite in participants:
			p.check_levelup()
			#TODO: Notify player if level up occurs
		time = 3
		time_elapsed = 0
	
	func defeat():
		print("Questor lost!")
		combat_state = CombatState.DEFEAT
		failed.emit()

	func remove_from_queue(combatant : QuestSprite) -> void:
		var idx = -1
		for i in range(len(turn_queue)):
			if turn_queue[i].combatant == combatant:
				idx = i
				break
		if idx != -1:
			turn_queue.remove_at(idx)
		else:
			printerr("Tried to remove someone not in the turn queue")
		
	func _on_combat_action_complete(requeue : bool, combatant : QuestSprite) -> void:
		if combatant.position != combatant.reset_position:
			combatant.position_reset()
			combatant.arrived.connect(_on_combat_action_complete.bind(requeue, combatant), CONNECT_ONE_SHOT)
			return
	
		busy_list.erase(combatant)
		if requeue:
			add_to_turn_queue(combatant)
	
	func execute_action(combatant) -> void:
		busy_list = [combatant]
		#TODO: Come up with other options than just swinging at each other
		var enemies : Array = get_enemy_list(combatant)
		var target = enemies.pick_random()
		combatant.approach(target, combatant.melee_range)
		combatant.arrived.connect(execute_attack.bind(combatant, target), CONNECT_ONE_SHOT)
	
	func get_enemy_list(combatant) -> Array:
		var lst = []
		for p in participants:
			if p != combatant:
				lst.append(p)
		return lst
	
	func resolve_combat() -> void:
		for p in participants:
			p.show_lifebar(false)
		pass
			
	func process(delta : float) -> void:
		#TODO: Make quest combat work
		match(type):
			Type.COMBAT:
				if time != 0:
					time_elapsed += delta
					if time_elapsed >= time:
						time = 0
						time_elapsed = 0
					else:
						return
				match(combat_state):
					CombatState.FIGHTING:
						if len(busy_list) < 1:
							if len(turn_queue) > 0:
								turn_queue[0].time -= delta
								if turn_queue[0].time <= 0:
									var c = turn_queue.pop_front()
									print("%s taking a turn!" % [c.combatant.name])
									if len(turn_queue) > 0:
										turn_queue[0].time += c.time
									execute_action(c.combatant)
							else:
								resolve_combat()
					CombatState.VICTORY:
							resolve_combat()
							complete = true
							completed.emit()
			Type.WAIT:
				time_elapsed += delta
				if time_elapsed >= time:
					complete = true
					completed.emit()

var base_name : String = ""
var name : String = "A Basic Quest"
var desc : String = "The default quest, with no special anything."
var difficulty : int = 1
var location : Locations
var steps : int = 1
var rewards : Dictionary
var guild_rewards : Dictionary
var covenant_cost : int = 1
var length : float = 10
var events : Array[Event] = []


var progress : float = 0
var current_step : int = 0
var taken : bool = false
var status : Status = Status.OPEN
var questview : QuestView = null

var questor : Adventurer = null
signal status_changed(status : Status)

func _init() -> void:
	pass

func initiate(member : Adventurer) -> void:
	questor = member
	status = Status.TAKEN
	status_changed.emit(Status.TAKEN)

func fail() -> void:
	status = Status.FAILED
	status_changed.emit(Status.FAILED)

func complete() -> void:
	status = Status.COMPLETED
	status_changed.emit(Status.COMPLETED)
	questview.show_quest_complete()
	for reward in rewards.keys():
		if reward == "gold":
			questor.gain_gold(rewards[reward])
		elif reward == "exp":
			questor.gain_exp(rewards[reward])
		#TODO: Implement other reward types
		#elif rewards[reward] is Item:
		#	questor.gain_item()
		#else it's a guild item they'll bring back for us
	Game.notice("%s completed the quest '%s'!" % [questor.full_name(), name])

func num_events() -> int:
	return len(events)

#TODO: Put in quest requirements
func is_eligible(member : Adventurer) -> bool:
	return !taken

func is_taken() -> bool:
	return status == Status.TAKEN

func location_name() -> String:
	match(location):
		Locations.NESTORS_WOODS: return "Nestor's Woods"
	return "ERROR"

func difficulty_name() -> String:
	match(difficulty):
		0: return "None"
		1: return "Trivial"
		2: return "Moderate"
		3: return "Severe"
		4: return "Extreme"
		5: return "Legendary"
		_: return "Unknown"

static func load_quest_list() -> void:
	var path = ProjectSettings.get_setting_with_override("data/quests/directory")
	var dir = DirAccess.open(path)
	var quest : Quest
	if dir:
		dir.list_dir_begin()
		var filename = dir.get_next()
		while filename != "":
			if not dir.current_is_dir() and filename.get_extension() == "gd":
				var file = load(path + "/" + filename).new()
				if file is Quest:
					list.append(file)
			filename = dir.get_next()
		dir.list_dir_end()

static func generate(parameters : Dictionary) -> Quest:
	return null

func save_dict() -> Dictionary:
	var d : Dictionary = {}
	d.name = name
	d.base_name = base_name
	d.desc = desc
	d.difficulty = difficulty
	d.location = location
	d.steps = steps
	#TODO: Convert these!
	#d.rewards = rewards
	#d.guild_rewards = guild_rewards

	d.covenant_cost = covenant_cost
	d.length = length
	d.progress = progress
	d.current_step = current_step
	d.taken = taken
	d.status = status
	
	var lst : Array = []
	for evt in events:
		lst.append(evt.save_dict())
	d.events = lst
	return d
