//..............................................................................
//
// The example shows how to properly process requests in a data stream.
// First of all, we buffer HTTP requests all the way to a double LF. After
// an HTTP request is fully buffered, we use the Jancy `reswitch` to parse and
// process GET requests (by returning files from the the users's home directory.
//
//..............................................................................

import "io_base.jncx"
import "io_File.jnc"
import "sys_Thread.jnc"
import "std_Buffer.jnc"

//..............................................................................

void main() {
	HttpServer server;

	// receive loop

	char buffer[256];
	for (;;) {
		size_t size = receive(buffer, sizeof(buffer));
		server.parse(buffer, size);
	}
}

//..............................................................................

// implementation

class HttpServer {
protected:
	enum ParseState {
		Body,
		Lf,
		Cr,
	}

protected:
	string_t m_rootDir;
	std.Buffer m_buffer;
	ParseState m_parseState;

public:
	construct();

	void parse(
		char const* p,
		size_t size
	);

protected:
	void processRequest(string_t request);
	void transmitFile(string_t fileName);
}

// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

HttpServer.construct() {
	m_rootDir = io.getHomeDir();
	m_parseState = ParseState.Body;
}

void HttpServer.parse(
	char const* p,
	size_t size
) {
	// buffer request until we see double LF (LF CR? LF)

	char const* p0 = p;
	char const* end = p + size;
	while (p < end) {
		switch (m_parseState) {
		case ParseState.Body:
			char const* lf = memchr(p, '\n', end - p);
			if (!lf)
				break2; // out of while

			m_parseState = ParseState.Lf;
			continue;

		case ParseState.Lf:
			switch (*p++) {
			case '\n':
				break2; // out of parent switch

			case '\r':
				m_parseState = ParseState.Cr;
				continue;

			default:
				m_parseState = ParseState.Body;
				continue;
			}

		case ParseState.Cr:
			if (*p != '\n') {
				m_parseState = ParseState.Body;
				continue;
			}
		}

		m_buffer.append(p0, p - p0);
		processRequest(string_t(m_buffer.m_p, m_buffer.m_size));

		// reset and prepare to process next request

		m_parseState = ParseState.Body;
		m_buffer.clear();
		p0 = p;
	}

	m_buffer.append(p0, end - p0);
}

void HttpServer.processRequest(string_t request) {
	switch (request) {
	case r"GET[ \t]+([^\s]+)[^\n]+\n": // only care about the first line of GET
		transmitFile~($1);
		break;

	// process other HTTP commands if necessary
	}
}

void HttpServer.transmitFile(string_t fileName)  {
	string_t filePath = $"%1/%2"(m_rootDir, fileName);
	g_logWriter.write(log.StdRecordCode.PlainTextSep, $"Opening file: $filePath...");

	io.File file;
	file.open(filePath, io.FileOpenFlags.ReadOnly);

	g_logWriter.write(log.StdRecordCode.PlainTextSep, $"Transmitting file (%d bytes)..."(file.m_size));

	transmit(
		"HTTP/1.1 200 OK\r\n"
		"Content-Type: application/octet-stream\r\n"
		$"Content-Length: %1\r\n\r\n"(file.m_size)
	);

	char buffer[4096];

	for (;;) {
		size_t size = file.read(buffer, sizeof(buffer));
		if (!size) // all done
			break;

		transmitAll(buffer, size);
	}

	g_logWriter.write(log.StdRecordCode.PlainTextSep, "File transmitted!");

catch:
	string_t message = std.getLastErrorDescription();
	g_logWriter.write(log.StdRecordCode.PlainTextSep, $"Error: $message");

	transmit(
		"HTTP/1.1 500 Internal Server Error\r\n"
		"Content-Type: text/plain\r\n"
		$"Content-Length: %1\r\n\r\n%2"(
			message.m_length,
			message
		)
	);
}

//..............................................................................
