extends Control

const SERVER_STATUS_TEXT: Dictionary = {
	true: "Server is running!",
	false: "Server is not running.",
}

const START_BUTTON_SERVER_TEXT: Dictionary = {
	true: "Stop server",
	false: "Start server",
}

const ACCEPTED_FILE_FORMATS := ["md"]

export(bool) var enable_file_autosave := true # if true, will save the active file's contents every save_file_timeout seconds.
export(float) var save_file_timeout := 4.0 # the time to save document content after the edited signal of TextEdit

enum ContextMenuOptions {
	MOVE_UP,
	MOVE_DOWN,
}

onready var server_path_label := $"%ServerPathLabel"
onready var port_spin_box := $"%PortSpinBox"
onready var start_server_button := $"%StartServerButton"
onready var server_status_label := $"%ServerStatusLabel"
onready var open_browser_button := $"%OpenBrowserButton"

onready var document_title_lineedit := $"%DocTitleLineEdit"
onready var document_date_lineedit := $"%DocDateLineEdit"

onready var file_tree := $"%FileTree"
onready var file_tree_context_menu := $"%FileTreeContextMenu"

onready var document_input_textedit := $"%DocInputTextEdit"
onready var content_preview_richtextlabel := $"%ContentPreviewRichTextLabel"

onready var server_folder_dialog := $"%ServerFolderDialog"

signal server_folder_changed(new_path) # new_path: String
signal start_server_button_pressed(port, path) # port: int, path: String
signal stop_server_button_pressed() # emit from %StartServerButton, when the server is not running.

#signal server_port_changed(new_port) # new_port: int
#signal open_browser_button_pressed(port) # port: int
#signal files_selection_changed(new_files) # new_files: Array<String>

var server_dir: String setget set_server_dir
var is_server_running: bool = false setget set_server_running

var working_files: Array = [] # Array<FileDef>

var current_file: FileDef = null
var current_tree_selection: TreeItem = null


func _ready() -> void:
	# TODO: put this in a config file
	if !OS.has_feature("editor"):
		self.server_dir = OS.get_executable_path().get_base_dir()
	else:
		self.server_dir = ProjectSettings.globalize_path("res://")


func set_server_dir(dir: String) -> void:
	server_dir = dir
	server_path_label.text = dir

	file_tree.clear()
	working_files.clear()

	var root: TreeItem = file_tree.create_item()
	root.set_text(0, "Server files")

	var directory := Directory.new()
	if directory.open(dir) == OK:
		if directory.list_dir_begin() != OK:
			push_error("Directory error") # TODO: show a user-facing error

		var file_name: String = directory.get_next()
		while file_name != "":
			if !directory.current_is_dir() && (file_name.get_extension() in ACCEPTED_FILE_FORMATS):
				var fd = FileDef(file_name, false)
				working_files.append(fd)
				fd.content = _get_file_content(file_name)

				var in_filestxt := _is_file_in_filestxt(file_name)
				fd.include_in_filestxt = in_filestxt
				if in_filestxt:
					var file_metadata := _get_file_metadata(file_name)
					fd.title = file_metadata["title"]
					fd.date = file_metadata["date"]

			file_name = directory.get_next()

		directory.list_dir_end()

	_reconstruct_tree_from_working_files()
	emit_signal("server_folder_changed", server_dir)


func _reconstruct_tree_from_working_files() -> void:
	current_file = null
	current_tree_selection = null

	file_tree.clear()
	var root: TreeItem = file_tree.create_item()
	root.set_text(0, "Server files")

	for i in working_files.size():
		var fd = working_files[i] as FileDef
		var item: TreeItem = file_tree.create_item(root)
		item.set_cell_mode(0, TreeItem.CELL_MODE_CHECK)
		item.set_metadata(0, {"file_def": fd, "id": i})
		item.set_editable(0, true)
		item.set_text(0, fd.file_path)

		item.set_checked(0, fd.include_in_filestxt)


func _on_FileTree_item_edited() -> void:
	var item: TreeItem = file_tree.get_edited()
	var fd: FileDef = item.get_metadata(0)["file_def"]
	fd.include_in_filestxt = item.is_checked(0)


func _on_FileTree_item_selected() -> void:
	var item: TreeItem = file_tree.get_selected()
	current_tree_selection = item
	current_file = item.get_metadata(0)["file_def"] as FileDef

	document_input_textedit.text = current_file.content
	document_date_lineedit.text = current_file.date
	document_title_lineedit.text = current_file.title


func _on_FileTree_item_rmb_selected(position: Vector2) -> void:
	current_tree_selection = file_tree.get_selected()
	file_tree_context_menu.rect_position = position + file_tree.rect_global_position
	file_tree_context_menu.popup()

	if is_server_running:
		return # the file tree can't be edited while the server is running, so disable moving items as well

	if (current_tree_selection.get_metadata(0)["id"] as int) == 0:
		file_tree_context_menu.set_item_disabled(ContextMenuOptions.MOVE_UP, true)
	else:
		file_tree_context_menu.set_item_disabled(ContextMenuOptions.MOVE_UP, false)

	if (current_tree_selection.get_metadata(0)["id"] as int) == working_files.size() - 1:
		file_tree_context_menu.set_item_disabled(ContextMenuOptions.MOVE_DOWN, true)
	else:
		file_tree_context_menu.set_item_disabled(ContextMenuOptions.MOVE_DOWN, false)


func _on_OpenServerFolderButton_pressed() -> void:
	server_folder_dialog.popup()


func set_server_running(running: bool) -> void:
	is_server_running = running

	port_spin_box.editable = !running
	document_date_lineedit.editable = !running
	document_title_lineedit.editable = !running
	document_input_textedit.readonly = running
	open_browser_button.disabled = !running
	_set_file_tree_disabled(running)
	_set_context_menu_disabled(running)

	server_status_label.text = SERVER_STATUS_TEXT[running]
	start_server_button.text = START_BUTTON_SERVER_TEXT[running]


func _generate_filestxt():
	var files := ""

	for file in working_files:
		file = file as FileDef
		if file.include_in_filestxt:
			files += ("%s %s %s" % [file.file_path, file.date, file.title]).strip_edges(false, true) + "\n"

	var f := File.new()
	if f.open(server_dir.plus_file("files.txt"), File.WRITE) == OK:
		f.store_string(files)
		f.close()
	else:
		push_error("File.txt open for save error!") # TODO: show a user-facing error


func _on_StartServerButton_pressed() -> void:
	if is_server_running:
		emit_signal("stop_server_button_pressed")
	else:
		_generate_filestxt()
		emit_signal("start_server_button_pressed", port_spin_box.value, server_dir)


func _is_file_in_filestxt(path: String) -> bool:
	var f := File.new()
	if f.open(server_dir.plus_file("files.txt"), File.READ) == OK:
		var text := f.get_as_text()
		return path in text
	else:
		return false


func _get_file_metadata(path: String) -> Dictionary:
	var f := File.new()

	var res = {
		"file_path": path,
		"date": "",
		"title": "",
		"content": "",
		}

	if f.open(server_dir.plus_file("files.txt"), File.READ) == OK:
		var text := f.get_as_text()
		var lines := text.split("\n")
		for line in lines:
			line = line as String
			if line.empty():
				continue
			var def = line.split(" ", true, 2)
			if def[0] == path:
#				res["file_path"] = def[0]
				res["date"] = def[1] if def.size() > 1 else ""
				res["title"] = def[2] if def.size() > 2 else ""
				break

	res["content"] = _get_file_content(path)

	return res


func _get_file_content(path: String) -> String:
	var content: String = ""
	var f := File.new()
	if f.open(server_dir.plus_file(path), File.READ) == OK:
		content = f.get_as_text()

	return content


func _set_file_tree_disabled(disabled: bool) -> void:
	var root = file_tree.get_root()
	var tree_item = root.get_children() as TreeItem
	while tree_item != null:
		tree_item.set_editable(0, !disabled)
		tree_item = tree_item.get_next()


func _set_context_menu_disabled(disabled: bool) -> void:
	for i in file_tree_context_menu.get_item_count():
		file_tree_context_menu.set_item_disabled(i, disabled)


func _commit_all_files() -> void:
	for file in working_files:
		file = file as FileDef
		file.commit(server_dir)


func _on_FileTreeContextMenu_id_pressed(id: int) -> void:
	var idx = current_tree_selection.get_metadata(0)["id"] as int
	var fd = working_files[idx] as FileDef
	match id:
		ContextMenuOptions.MOVE_DOWN:
			working_files.remove(idx)
			working_files.insert(idx + 1, fd)
			_reconstruct_tree_from_working_files()
		ContextMenuOptions.MOVE_UP:
			working_files.remove(idx)
			working_files.insert(idx - 1, fd)
			_reconstruct_tree_from_working_files()


func _on_DocTitleLineEdit_text_changed(new_text: String) -> void:
	if current_file:
		current_file.title = new_text


func _on_DocDateLineEdit_text_changed(new_text: String) -> void:
	if current_file:
		current_file.date = new_text


func _on_OpenBrowserButton_pressed() -> void:
	if OS.shell_open("http://localhost:%s" % port_spin_box.value) != OK:
		push_error("Error opening browser!") # TODO: show a user-facing error


func _on_RefreshFilesButton_pressed() -> void:
	_commit_all_files()
	_generate_filestxt()
	self.server_dir = server_dir


func _on_DocInputTextEdit_text_changed() -> void:
	if current_file:
		var new_text: String = document_input_textedit.text
		current_file.content = new_text

		if !current_file.timer:
			var t := Timer.new()
			t.wait_time = save_file_timeout
			t.one_shot = true
			current_file.timer = t
			add_child(t)
# warning-ignore:return_value_discarded
			t.connect("timeout", self, "_on_EditedTimeout_timeout", [t, current_file])
		else:
			current_file.timer.stop()
			current_file.timer.start()


func _on_EditedTimeout_timeout(timer: Timer, file: FileDef) -> void:
	file.timer = null
	timer.queue_free()

	file.commit(server_dir)


func FileDef(
	file_path: String,
	include_in_filestxt: bool,
	title: String = "",
	date: String = "") -> FileDef:
		var fd := FileDef.new()
		fd.file_path = file_path
		fd.include_in_filestxt = include_in_filestxt
		fd.title = title
		fd.date = date

		return fd


class FileDef:
	var file_path: String # relative
	var include_in_filestxt: bool = true
	var title: String # optional
	var date: String # optional, YYYY-MM-DD

	var content: String

	var timer: Timer

	func commit(server_dir: String) -> void:
		var f := File.new()
		var open := f.open(server_dir.plus_file(file_path), File.WRITE)
		if open == OK:
			f.store_string(content)

			if timer && !timer.is_stopped(): # if there's an autosave pending, stop it since we're setting it here.
				timer.stop()

		else:
			push_error("Error committing file %s, code %s" % [file_path, open]) # TODO: show a user-facing error
