extends Reference

class Request:
	extends Reference

	var peer: StreamPeerTCP

	var method := ""
	var request_path := ""
	var request_query := ""
	var headers := PoolStringArray()
	var request_data := PoolByteArray()

	func _init(stream_peer: StreamPeerTCP) -> void:
		peer = stream_peer

	func append_header(header: String) -> void:
		headers.append(header)

	func append_request_data_byte(b: int) -> void:
		request_data.append(b)

class RequestParser:
	extends Reference

	enum State {
		SUCCESS,
		FAILURE,
		FIRST_LINE_READ,
		FIRST_LINE_EXPECT_NL,
		HEADER_READ,
		HEADER_EXPECT_NL,
		BODY,
	}

	var request: Request

	var state: int = State.FIRST_LINE_READ
	var curr_line := ""
	var content_length := 0

	func _init(the_request: Request) -> void:
		request = the_request

	func fetch() -> bool:
		while true:
			if not request.peer.is_connected_to_host():
				print_debug("Connection lost")
				return false

			var available_bytes := request.peer.get_available_bytes()
			if available_bytes == 0:
				OS.delay_msec(100)
				continue

			var arr := request.peer.get_data(available_bytes)
			if arr[0] != OK:
				print_debug("Failed to get data (error ", arr[0], ")")
				return false
			for b in arr[1]:
				match state:
					State.FIRST_LINE_READ:
						state = _append_to_line(b, state, State.FIRST_LINE_EXPECT_NL)
					State.FIRST_LINE_EXPECT_NL:
						if b != ord('\n'):
							print_debug("Got \\r with no \\n")
							return false
						state = _parse_first_line()
						curr_line = ""
					State.HEADER_READ:
						state = _append_to_line(b, state, State.HEADER_EXPECT_NL)
					State.HEADER_EXPECT_NL:
						if b != ord('\n'):
							print_debug("Got \\r with no \\n")
							return false
						state = _parse_header()
						curr_line = ""
					State.BODY:
						state = _append_to_body(b)
					_:
						push_error("Invalid state " + str(state))
						return false

				if state == State.SUCCESS:
					print_debug("Request parsed successfully")
					return true
				if state == State.FAILURE:
					return false

		push_error("Reached outside of fetch loop")
		return false

	func _append_to_line(b: int, old_state: int, new_state: int) -> int:
		var c := char(b)
		if c == '\r':
			return new_state
		curr_line += c
		return old_state

	func _parse_first_line() -> int:
		var parts := curr_line.split(" ")
		if parts.size() != 3:
			print_debug("Malformed first line")
			return State.FAILURE

		request.method = parts[0]

		var path_and_query = parts[1].split("?", true, 1)
		request.request_path = path_and_query[0]
		if path_and_query.size() == 2:
			request.request_query = path_and_query[1]

		var protocol: String = parts[2]
		if not protocol.begins_with("HTTP/"):
			print_debug("Malformed protocol")
			return State.FAILURE

		return State.HEADER_READ

	func _parse_header() -> int:
		if curr_line == "":
			if content_length:
				return State.BODY
			return State.SUCCESS

		var header_parts := curr_line.split(": ", true, 1)
		if header_parts.size() != 2:
			print_debug("Malformed header")
			return State.FAILURE
		var header_name: String = header_parts[0]
		var header_value: String = header_parts[1]

		if header_name == "Content-Length":
			if content_length != 0:
				print_debug("Content-Length was set again")
				return State.FAILURE
			if not header_value.is_valid_integer():
				print_debug("Content-Length is invalid")
				return State.FAILURE
			content_length = int(header_value)

		request.append_header(curr_line)
		return State.HEADER_READ

	func _append_to_body(b: int) -> int:
		request.append_request_data_byte(b)
		content_length -= 1
		if content_length == 0:
			return State.SUCCESS
		return State.BODY

class Response:
	extends Reference

	var response_code := 200
	var headers := PoolStringArray()
	var body := PoolByteArray()

	func respond(peer: StreamPeerTCP) -> void:
		_put(peer, "HTTP/1.1 %d\r\n" % response_code)

		for header in headers:
			_put(peer, "%s\r\n" % header)

		if body.empty():
			_put(peer, "\r\n")
		else:
			_put(peer, "Content-Length: %d\r\n\r\n" % body.size())
# warning-ignore:return_value_discarded
			peer.put_data(body)

	func _put(peer: StreamPeerTCP, contents: String) -> void:
# warning-ignore:return_value_discarded
		peer.put_data(contents.to_ascii())

var _responder_instance: Object = self
var _responder_function := "_respond"
var _server_thread := Thread.new()
var _server := TCP_Server.new()
var _server_shutdown := false

func set_responder(instance: Object, function: String):
	_responder_instance = instance
	_responder_function = function

func listen(port: int, bind_address := "*") -> int:
	stop()
	var err := _server.listen(port, bind_address)
	if err == OK:
		err = _server_thread.start(self, "_listen_thread")
	return err

func stop() -> void:
	if _server.is_listening():
		_server.stop()
	if _server_thread.is_active():
		_server_shutdown = true
		_server_thread.wait_to_finish()

# Call this function directly to run the server in the main thread
# For debugging purposes only
func _listen_thread(_null) -> void:
	_server_shutdown = false
	_take_connections()

func _take_connections() -> void:
	while not _server_shutdown:
		if not _server.is_connection_available():
			OS.delay_msec(100)
			continue
		var peer := _server.take_connection()
		print_debug("Got peer: ", peer.get_connected_host(), ":", peer.get_connected_port())
		var request := Request.new(peer)
		var request_parser := RequestParser.new(request)
		if request_parser.fetch():
			var response: Response = _responder_instance.call(_responder_function, request)
			response.respond(peer)
		peer.disconnect_from_host()

# If extending this class, you must override this function
func _respond(_request: Request) -> Response:
	push_error("You must call set_responder or override _respond")
	return null