class ESCLoggerBase:
	# Perform emergency savegame
	signal perform_emergency_savegame

	# Valid log levels
	enum { LOG_ERROR, LOG_WARNING, LOG_INFO, LOG_DEBUG, LOG_TRACE }

	# Log file format
	const LOG_FILE_FORMAT: String = "log_%s_%s.log"

	# A map of log level names to log level ints
	var _level_map: Dictionary = {
		"ERROR": LOG_ERROR,
		"WARNING": LOG_WARNING,
		"INFO": LOG_INFO,
		"DEBUG": LOG_DEBUG,
		"TRACE": LOG_TRACE,
	}

	# Configured log level
	var _log_level: int


	# Constructor
	func _init():
		_log_level = _level_map[ESCProjectSettingsManager.get_setting(
			ESCProjectSettingsManager.LOG_LEVEL
		).to_upper()]


	func formatted_message(context: String, msg: String, letter: String) -> String:
		return "ESC ({0}) {1} {2}: {3}".format([_formatted_date(), letter, context, msg])


	func trace(owner: Object, msg: String):
		var context = owner.get_script().resource_path.get_file()
		print(formatted_message(context, msg, "T"))


	# Debug log
	func debug(owner: Object, msg: String):
		var context = owner.get_script().resource_path.get_file()
		print(formatted_message(context, msg, "D"))


	func info(owner: Object, msg: String):
		var context = owner.get_script().resource_path.get_file()
		print(formatted_message(context, msg, "I"))


	# Warning log
	func warn(owner: Object, msg: String):
		var context = owner.get_script().resource_path.get_file()
		print(formatted_message(context, msg, "W"))
		push_warning(formatted_message(context, msg, "W"))
		if ESCProjectSettingsManager.get_setting(
			ESCProjectSettingsManager.TERMINATE_ON_WARNINGS
		):
			assert(false)
			escoria.get_tree().quit()


	# Error log
	func error(owner: Object, msg: String):
		var context = owner.get_script().resource_path.get_file()
		printerr(formatted_message(context, msg, "E"))
		push_error(formatted_message(context, msg, "E"))
		if ESCProjectSettingsManager.get_setting(
			ESCProjectSettingsManager.TERMINATE_ON_ERRORS
		):
			assert(false)
			escoria.get_tree().quit()


	func get_log_level() -> int:
		return _log_level

	func _formatted_date():
		var info = OS.get_datetime()
		info["year"] = "%04d" % info["year"]
		info["month"] = "%02d" % info["month"]
		info["day"] = "%02d" % info["day"]
		info["hour"] = "%02d" % info["hour"]
		info["minute"] = "%02d" % info["minute"]
		info["second"] = "%02d" % info["second"]
		return "{year}-{month}-{day}T{hour}:{minute}:{second}".format(info)


# A logger that logs to the terminal and to a log file.
class ESCLoggerFile extends ESCLoggerBase:
	# Log file handler
	var log_file: File

	# Constructor
	func _init():
		# Open logfile in write mode
		log_file = File.new()

		# This is left alone as this constructor is called from escoria.gd's own
		# constructor
		var log_file_path = ProjectSettings.get_setting(
			ESCProjectSettingsManager.LOG_FILE_PATH
		)
		var date = OS.get_datetime()
		log_file_path = log_file_path.plus_file(LOG_FILE_FORMAT % [
				str(date["year"]) + str(date["month"]) + str(date["day"]),
				str(date["hour"]) + str(date["minute"]) + str(date["second"])
			])
		log_file.open(
			log_file_path,
			File.WRITE
		)

	func trace(owner: Object, msg: String):
		if _log_level >= LOG_TRACE:
			_log_to_file(owner, msg, "T")
			.trace(owner, msg)

	# Debug log
	func debug(owner: Object, msg: String):
		if _log_level >= LOG_DEBUG:
			_log_to_file(owner, msg, "D")
			.debug(owner, msg)

	func info(owner: Object, msg: String):
		if _log_level >= LOG_INFO:
			_log_to_file(owner, msg, "I")
			.info(owner, msg)

	# Warning log
	func warn(owner: Object, msg: String):
		if _log_level >= LOG_WARNING:
			_log_to_file(owner, msg, "W")
			if ESCProjectSettingsManager.get_setting(
				ESCProjectSettingsManager.TERMINATE_ON_WARNINGS
			):
				_log_stack_trace_to_file(owner)
				print_stack()
				close_logs()
			.warn(owner, msg)

	# Error log
	func error(owner: Object, msg: String):
		if _log_level >= LOG_ERROR:
			_log_to_file(owner, msg, "E")
			if ESCProjectSettingsManager.get_setting(
				ESCProjectSettingsManager.TERMINATE_ON_ERRORS
			):
				_log_stack_trace_to_file(owner)
				print_stack()
				close_logs()
			.error(owner, msg)


	# Close the log file cleanly
	func close_logs():
		print("Closing logs peacefully.")
		_log_line_to_file("Closing logs peacefully.")
		log_file.close()


	func _log_to_file(owner: Object, msg: String, letter: String):
		if log_file.is_open():
			var context = ""
			if owner != null:
				context = owner.get_script().resource_path.get_file()
			log_file.store_string(formatted_message(context, msg, letter) + "\n")

	func _log_line_to_file(msg: String):
		if log_file.is_open():
			log_file.store_string(msg + "\n")

	func _log_stack_trace_to_file(owner: Object):
		var frame_number = 0
		for stack in get_stack().slice(2, get_stack().size()):
			_log_line_to_file(
				"Frame %s - %s:%s in function '%s'" % [
					str(frame_number),
					stack["source"],
					stack["line"],
					stack["function"],
				]
			)
			frame_number += 1


# A simple logger that logs to terminal using debug() function
class ESCLoggerVerbose extends ESCLoggerBase:
	func _init():
		pass

	func debug(owner: Object, msg: String):
		var context = owner.get_script().resource_path.get_file()
		print(context, ": ", msg)

