function parseUrl(url) {
	// XXX: Hack.
	let match = url.match(new RegExp("(\\w+)://([^/:]+)(?::(\\d+))?(.*)"));
	return {
		protocol: match[1],
		host: match[2],
		path: match[4],
		port: match[3] ? parseInt(match[3]) : match[1] == "http" ? 80 : 443,
	};
}

function parseResponse(data) {
	let firstLine;
	let headers = {};
	while (true) {
		let endLine = data.indexOf('\r\n');
		let line = data.substring(0, endLine);
		data = data.substring(endLine + 2);
		if (!line.length) {
			break;
		} else if (!firstLine) {
			firstLine = line;
		} else {
			let colon = line.indexOf(":");
			headers[line.substring(colon)] = line.substring(colon + 1);
		}
	}
	return {body: data};
}

export function fetch(url, options, allowed_hosts) {
	let parsed = parseUrl(url);
	return new Promise(function(resolve, reject) {
		if ((allowed_hosts ?? []).indexOf(parsed.host) == -1) {
			throw new Error(`fetch() request to host ${parsed.host} is not allowed.`);
		}
		let socket = new Socket();
		let buffer = new Uint8Array(0);

		return socket.connect(parsed.host, parsed.port).then(function() {
			socket.read(function(data) {
				if (data && data.length) {
					let newBuffer = new Uint8Array(buffer.length + data.length);
					newBuffer.set(buffer, 0);
					newBuffer.set(data, buffer.length);
					buffer = newBuffer;
				} else {
					let result = parseHttpResponse(buffer);
					if (!result) {
						reject(new Exception('Parse failed.'));
					}
					if (typeof result == 'number') {
						if (result == -2) {
							reject('Incomplete request.');
						} else {
							reject('Bad request.');
						}
					} else if (typeof result == 'object') {
						resolve({
							body: buffer.slice(result.bytes_parsed),
							status: result.status,
							message: result.message,
							headers: result.headers,
						});
					} else {
						reject(new Exception('Unexpected parse result.'));
					}
					resolve(parseResponse(utf8Decode(buffer)));
				}
			});

			if (parsed.port == 443) {
				return socket.startTls();
			}
		}).then(function() {
			let body = typeof options?.body == 'string' ? utf8Encode(options.body) : (options.body || new Uint8Array(0));
			let headers = utf8Encode(`${options?.method ?? 'GET'} ${parsed.path} HTTP/1.0\r\nHost: ${parsed.host}\r\nConnection: close\r\nContent-Length: ${body.length}\r\n\r\n`);
			let fullRequest = new Uint8Array(headers.length + body.length);
			fullRequest.set(headers, 0);
			fullRequest.set(body, headers.length);
			socket.write(fullRequest);
		}).catch(function(error) {
			reject(error);
		});
	});
}