extends Node

# Settings
var _is_enabled: bool = ProjectSettings.get_setting(&"netfox/extras/auto_tile_windows", false)
var _is_borderless: bool = ProjectSettings.get_setting(&"netfox/extras/borderless", false)
var _tile_screen: int = ProjectSettings.get_setting(&"netfox/extras/screen", 0)

# Hash the game name, so we always get a valid filename
var _prefix: String = "netfox-window-tiler-%x" % [ProjectSettings.get_setting(&"application/config/name").hash()]

var _sid: String = "%x" % [hash(int(Time.get_unix_time_from_system() / 2.))]
var _uid: String = "%d" % [Time.get_unix_time_from_system() * 1000_0000.]

static var _logger: NetfoxLogger = NetfoxLogger._for_extras("WindowTiler")

func _ready() -> void:
	# Running on a non-editor (export template) build
	if OS.has_feature("template"):
		return

	# Running in headless mode
	if DisplayServer.get_name() == "headless":
		return

	# Running in CI
	for env_var in ["CI", "NETFOX_CI"]:
		if OS.get_environment(env_var) != "":
			_logger.debug("Environment variable %s set, disabling", [env_var])
			return

	# Cleanup in case some files were left
	_cleanup()

	# Running embedded in editor
	if _is_embedded():
		return

	# Don't tile if disabled
	if not _is_enabled:
		return

	_logger.debug("Tiling with sid: %s, uid: %s", [_sid, _uid])

	var err = _make_lock(_sid, _uid)
	if err != Error.OK:
		_logger.warning("Failed to create lock for tiling, reason: %s", [error_string(err)])
		return

	# Search for locks, stop once no new locks are found
	var locks = []
	await get_tree().create_timer(0.25).timeout
	for i in range(20):
		await get_tree().create_timer(0.1).timeout
		var new_locks = _list_lock_ids()

		if locks == new_locks:
			break

		locks = new_locks

	var tile_count = locks.size()
	var idx = locks.find(_uid)

	_logger.debug("Tiling as idx %d / %d - %s in %s", [idx, tile_count, _uid, locks])
	_tile_window(idx, tile_count)

func _is_embedded() -> bool:
	if Engine.has_method("is_embedded_in_editor"):
		return Engine.call("is_embedded_in_editor")
	return false

func _make_lock(sid: String, uid: String) -> Error:
	var path = "%s/%s-%s-%s" % [OS.get_cache_dir(), _prefix, sid, uid]
	var file := FileAccess.open(path, FileAccess.WRITE)

	if file == null:
		return FileAccess.get_open_error()

	file.close()
	return Error.OK

func _list_lock_ids() -> Array[String]:
	var result: Array[String] = []
	var dir := DirAccess.open(OS.get_cache_dir())

	if dir:
		for f in dir.get_files():
			if f.begins_with(_prefix):
				result.append(_get_uid(f))

	return result

func _cleanup():
	var result: Array[String] = []
	var dir := DirAccess.open(OS.get_cache_dir())

	if dir:
		for f in dir.get_files():
			if f.begins_with(_prefix) and _get_sid(f) != _sid:
					_logger.trace("Cleaned up lock: %s", [f])
					dir.remove(OS.get_cache_dir() + "/" + f)

func _get_sid(filename: String) -> String:
	return filename.substr(_prefix.length() + 1).get_slice("-", 0)

func _get_uid(filename: String) -> String:
	return filename.substr(_prefix.length() + 1).get_slice("-", 1)

func _tile_window(i: int, total: int) -> void:
	var screen = _tile_screen
	var screen_rect = DisplayServer.screen_get_usable_rect(screen)

	var window: Window = get_tree().get_root()
	window.set_current_screen(screen)

	if total == 1:
		DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_MAXIMIZED)
		return

	window.borderless = _is_borderless

	# Divide up the screen
	var windows_per_row = int(ceil(sqrt(total)))
	var windows_per_col = int(ceil(total / float(windows_per_row)))
	var window_size = Vector2(
		screen_rect.size.x / windows_per_row, screen_rect.size.y / windows_per_col
	)

	window.set_size(window_size)

	# Position of the window based on index.
	var row = i / windows_per_row
	var col = i % windows_per_row

	var x = screen_rect.position.x + col * window_size.x
	var y = screen_rect.position.y + row * window_size.y

	window.set_position(Vector2(x, y))
