## Class inheriting HttpRouter for handling file serving requests
##
## NOTE: This class mainly handles behind the scenes stuff.
class_name HttpFileRouter
extends HttpRouter

## Full path to the folder which will be exposed to web
var path: String = ""

## Relative path to the index page, which will be served when a request is made to "/" (server root)
var index_page: String = "index.html"

## Relative path to the fallback page which will be served if the requested file was not found
var fallback_page: String = ""

## An ordered list of extensions that will be checked
## if no file extension is provided by the request
var extensions: PackedStringArray = ["html"]

## A list of extensions that will be excluded if requested
var exclude_extensions: PackedStringArray = []

## Creates an HttpFileRouter intance
## [br]
## [br][param path] - Full path to the folder which will be exposed to web.
## [br][param options] - Optional Dictionary of options which can be configured:
## [br] - [param fallback_page]: Full path to the fallback page which will be served if the requested file was not found
## [br] - [param extensions]: A list of extensions that will be checked if no file extension is provided by the request
## [br]	- [param exclude_extensions]: A list of extensions that will be excluded if requested
func _init(
	path: String,
	options: Dictionary = {
		index_page = index_page,
		fallback_page = fallback_page,
		extensions = extensions,
		exclude_extensions = exclude_extensions,
	}
	) -> void:
	self.path = path
	self.index_page = options.get("index_page", "")
	self.fallback_page = options.get("fallback_page", "")
	self.extensions = options.get("extensions", [])
	self.exclude_extensions = options.get("exclude_extensions", [])

## Handle a GET request
## [br]
## [br][param request] - The request from the client
## [br][param response] - The response to send to the clinet
func handle_get(request: HttpRequest, response: HttpResponse) -> void:
	var serving_path: String = path + request.path
	var file_exists: bool = _file_exists(serving_path)

	if request.path == "/" and not file_exists:
		if index_page.length() > 0:
			serving_path = path + "/" + index_page
			file_exists = _file_exists(serving_path)

	if request.path.get_extension() == "" and not file_exists:
		for extension in extensions:
			serving_path = path + request.path + "." + extension
			file_exists = _file_exists(serving_path)
			if file_exists:
				break

	# GDScript must be excluded, unless it is used as a preprocessor (php-like)
	if (file_exists and not serving_path.get_extension() in ["gd"] + Array(exclude_extensions)):
		response.send_raw(
			200,
			_serve_file(serving_path),
			_get_mime(serving_path.get_extension())
			)
	else:
		if fallback_page.length() > 0:
			serving_path = path + "/" + fallback_page
			response.send_raw(200 if index_page == fallback_page else 404, _serve_file(serving_path), _get_mime(fallback_page.get_extension()))
		else:
			response.send_raw(404)

# Reads a file as text
#
# #### Parameters
# - file_path: Full path to the file
func _serve_file(file_path: String) -> PackedByteArray:
	var content: PackedByteArray = []
	var file: FileAccess = FileAccess.open(file_path, FileAccess.READ)
	var error = file.get_open_error()
	if error:
		content = ("Couldn't serve file, ERROR = %s" % error).to_ascii_buffer()
	else:
		content = file.get_buffer(file.get_length())
	file.close()
	return content

# Check if a file exists
#
# #### Parameters
# - file_path: Full path to the file
func _file_exists(file_path: String) -> bool:
	return FileAccess.file_exists(file_path)

# Get the full MIME type of a file from its extension
#
# #### Parameters
# - file_extension: Extension of the file to be served
func _get_mime(file_extension: String) -> String:
	var type: String = "application"
	var subtype : String = "octet-stream"
	match file_extension:
		# Web files
		"css","html","csv","js","mjs":
			type = "text"
			subtype = "javascript" if file_extension in ["js","mjs"] else file_extension
		"php":
			subtype = "x-httpd-php"
		"ttf","woff","woff2":
			type = "font"
			subtype = file_extension
		# Image
		"png","bmp","gif","png","webp":
			type = "image"
			subtype = file_extension
		"jpeg","jpg":
			type = "image"
			subtype = "jpg"
		"tiff", "tif":
			type = "image"
			subtype = "jpg"
		"svg":
			type = "image"
			subtype = "svg+xml"
		"ico":
			type = "image"
			subtype = "vnd.microsoft.icon"
		# Documents
		"doc":
			subtype = "msword"
		"docx":
			subtype = "vnd.openxmlformats-officedocument.wordprocessingml.document"
		"7z":
			subtype = "x-7x-compressed"
		"gz":
			subtype = "gzip"
		"tar":
			subtype = "application/x-tar"
		"json","pdf","zip":
			subtype = file_extension
		"txt":
			type = "text"
			subtype = "plain"
		"ppt":
			subtype = "vnd.ms-powerpoint"
		# Audio
		"midi","mp3","wav":
			type = "audio"
			subtype = file_extension
		"mp4","mpeg","webm":
			type = "audio"
			subtype = file_extension
		"oga","ogg":
			type = "audio"
			subtype = "ogg"
		"mpkg":
			subtype = "vnd.apple.installer+xml"
		# Video
		"ogv":
			type = "video"
			subtype = "ogg"
		"avi":
			type = "video"
			subtype = "x-msvideo"
		"ogx":
			subtype = "ogg"
	return type + "/" + subtype