extends Node

signal request_slot_freed

# Needed because of https://github.com/godotengine/godot/issues/25052
const MAX_CONCURRENT_REQUESTS: int = 20
const INT_MAX: int = 9223372036854775807

var _active_requests: int = 0
var _requests_queue := PoolIntArray()
# Must always be greater than -1
var _next_request_id: int = 0



func get_image(url: String) -> Image:
	"""
	Args:
		url: A web URL to an image file
	
	Returns:
		An Image object of the image at `url`. If no image is found then an
		empty Image object
	"""
	yield(get_tree(), "idle_frame")
	var image: Image = Cache.get_image(url)
	if image != null:
		return image
	image = Image.new()
	if url == "":
		return image
	
	var request_response: Array = yield(_make_http_request(url), "completed")
	if (
			len(request_response) >= 4
			and request_response[0] == HTTPRequest.RESULT_SUCCESS
	):
		var file_type: String = _get_file_type(url, request_response)
		if file_type in ["bmp", "dib"]:
			image.load_bmp_from_buffer(request_response[3])
		elif file_type in ["jpg", "jpeg", "jpe", "jif", "jfif", "jfi"]:
			image.load_jpg_from_buffer(request_response[3])
		elif file_type in ["png"]:
			image.load_png_from_buffer(request_response[3])
		elif file_type in ["tga", "icb", "vda", "vst"]:
			image.load_tga_from_buffer(request_response[3])
		elif file_type in ["webp"]:
			image.load_webp_from_buffer(request_response[3])
		
		if not image.is_empty():
			Cache.add_image(url, image)
	return image


func get_comments(args: Dictionary) -> Dictionary:
	var comments: Dictionary = {}
	var comment_server: String = Data.get_comment_server()
	var headers := PoolStringArray(["Content-Type: application/json"])
	var data: Dictionary = {
		"jsonrpc": "2.0",
		"id": 1,
		"method": "comment.List",
		"params": args,
	}
	var data_json: String = JSON.print(data)
	
	var request_response: Array = \
			yield(
				_make_http_request(comment_server, headers, true,
					HTTPClient.METHOD_POST, data_json)
				, "completed")
	if (
			len(request_response) >= 4
			and request_response[0] == HTTPRequest.RESULT_SUCCESS
	):
		var json_result: JSONParseResult = \
				JSON.parse(request_response[3].get_string_from_utf8())
		if json_result.get_error() == OK:
			comments = json_result.get_result().get("result", {})
	return comments


func get_comments_reactions(args: Dictionary) -> Dictionary:
	var reactions: Dictionary = {}
	var comment_server: String = Data.get_comment_server()
	var headers := PoolStringArray(["Content-Type: application/json"])
	var data: Dictionary = {
		"jsonrpc": "2.0",
		"id": 1,
		"method": "reaction.List",
		"params": args,
	}
	var data_json: String = JSON.print(data)
	
	var request_response: Array = \
			yield(
				_make_http_request(comment_server, headers, true,
					HTTPClient.METHOD_POST, data_json)
				, "completed")
	if (
			len(request_response) >= 4
			and request_response[0] == HTTPRequest.RESULT_SUCCESS
	):
		var json_result: JSONParseResult = \
				JSON.parse(request_response[3].get_string_from_utf8())
		if json_result.get_error() == OK:
			reactions = json_result.get_result().get("result", {})
	return reactions


func _make_http_request(url: String, custom_headers:=PoolStringArray(),
		ssl_validate_domain: bool=true, method: int=0, request_data: String=""
		) -> Array:
	var request = HTTPRequest.new()
	var result: Array = []
	var request_id: int = _get_request_id()
	yield(_occupy_request_slot(request_id), "completed")
	add_child(request)
	var err = request.request(url, custom_headers, ssl_validate_domain, method,
			request_data)
	if err == OK:
		result = yield(request, "request_completed")
	else:
		# Must have a yield on each code path
		yield(get_tree(), "idle_frame")
		push_error("HTTPClient request error: %d" % err)
	remove_child(request)
	_free_request_slot()
	request.queue_free()
	return result


func _get_file_type(url: String, response: Array) -> String:
	"""
	First checks for the mime-type in the 'Content-Type' header, if that's not
	set then checks the extension of `url` and if that doesn't exist, returns
	an empty string
	"""
	var type: String = ""
	if len(response) > 2:
		for item in response[2]:
			if item.begins_with("Content-Type:"):
				# Get the file type from the MIME-type
				type = item.trim_prefix("Content-Type:")
				type = type.split(";", true, 1)[0]
				type = type.split("/", true, 1)[1]
				type = type.strip_edges()
				break
	if type == "":
		# Get the file type from the file name extension
		type = url.get_extension()
	return type


func _get_request_id() -> int:
	var id: int = _next_request_id
	if _next_request_id == INT_MAX:
		_next_request_id = 0
	else:
		_next_request_id += 1
	return id


func _occupy_request_slot(request_id: int) -> void:
	""" Yield on this function before making a request """
	yield(get_tree(), "idle_frame")
	if _active_requests < MAX_CONCURRENT_REQUESTS:
		_active_requests += 1
	else:
		_requests_queue.append(request_id)
		var unqueued_id: int = yield(self, "request_slot_freed")
		while unqueued_id != request_id:
			unqueued_id = yield(self, "request_slot_freed")
		_active_requests += 1


func _free_request_slot() -> void:
	""" Call this function when done making a request """
	var unqueued_request_id: int = -1
	if _requests_queue.size() > 0:
		# Return the next request ID in the queue to be processed
		unqueued_request_id = _requests_queue[0]
		_requests_queue.remove(0)
	_active_requests -= 1
	emit_signal("request_slot_freed", unqueued_request_id)
