#include "EggDuino.h"

namespace {
constexpr size_t kLogCapacity = 80;
constexpr size_t kLogLineLength = 160;
constexpr size_t kIncomingLogCapacity = 128;
constexpr size_t kIncomingLogLineLength = SERIALCOMMAND_BUFFER + 24;

char g_logLines[kLogCapacity][kLogLineLength];
uint32_t g_logSeq[kLogCapacity];
size_t g_logWritePos = 0;
uint32_t g_nextLogSeq = 1;

char g_incomingLogLines[kIncomingLogCapacity][kIncomingLogLineLength];
uint32_t g_incomingLogSeq[kIncomingLogCapacity];
size_t g_incomingLogWritePos = 0;
uint32_t g_nextIncomingLogSeq = 1;

void appendJsonEscaped(String &out, const char *text) {
	out += "\"";
	for (size_t i = 0; text[i] != '\0'; ++i) {
		const char c = text[i];
		switch (c) {
			case '\\':
				out += "\\\\";
				break;
			case '"':
				out += "\\\"";
				break;
			case '\n':
				out += "\\n";
				break;
			case '\r':
				out += "\\r";
				break;
			case '\t':
				out += "\\t";
				break;
			default:
				if (static_cast<unsigned char>(c) < 0x20) {
					out += '?';
				} else {
					out += c;
				}
				break;
		}
	}
	out += "\"";
}

const char *transportLabel(ProtocolTransport transport) {
	switch (transport) {
		case PROTOCOL_TRANSPORT_BLE:
			return "BLE";
		case PROTOCOL_TRANSPORT_WIFI:
			return "WIFI";
		case PROTOCOL_TRANSPORT_SERIAL:
		default:
			return "SERIAL";
	}
}
} // namespace

void Log(const String &message) {
	snprintf(
		g_logLines[g_logWritePos],
		kLogLineLength,
		"[%010lu] %s",
		static_cast<unsigned long>(millis()),
		message.c_str()
	);
	g_logSeq[g_logWritePos] = g_nextLogSeq++;
	g_logWritePos = (g_logWritePos + 1) % kLogCapacity;
}

void Log(const char *message) {
	Log(String(message));
}

String buildLogsJson(uint32_t sinceSeq) {
	String output;
	output.reserve(2048);
	output += "{\"logs\":[";
	uint32_t lastSeq = sinceSeq;
	bool first = true;

	for (size_t i = 0; i < kLogCapacity; ++i) {
		const size_t idx = (g_logWritePos + i) % kLogCapacity;
		const uint32_t seq = g_logSeq[idx];
		if (seq == 0 || seq <= sinceSeq) {
			continue;
		}

		if (!first) {
			output += ",";
		}
		first = false;
		output += "{\"seq\":";
		output += String(seq);
		output += ",\"text\":";
		appendJsonEscaped(output, g_logLines[idx]);
		output += "}";
		lastSeq = seq;
	}

	output += "],\"lastSeq\":";
	output += String(lastSeq);
	output += "}";
	return output;
}

void captureIncomingPrintLine(const char *line, ProtocolTransport transport) {
	if ((line == NULL) || (line[0] == '\0')) {
		return;
	}

	snprintf(
		g_incomingLogLines[g_incomingLogWritePos],
		kIncomingLogLineLength,
		"[%010lu] %s %s",
		static_cast<unsigned long>(millis()),
		transportLabel(transport),
		line
	);
	g_incomingLogSeq[g_incomingLogWritePos] = g_nextIncomingLogSeq++;
	g_incomingLogWritePos = (g_incomingLogWritePos + 1) % kIncomingLogCapacity;
}

String buildIncomingPrintLogText() {
	String output;
	output.reserve(4096);

	for (size_t i = 0; i < kIncomingLogCapacity; ++i) {
		const size_t idx = (g_incomingLogWritePos + i) % kIncomingLogCapacity;
		if (g_incomingLogSeq[idx] == 0) {
			continue;
		}
		output += g_incomingLogLines[idx];
		output += "\n";
	}

	return output;
}

void clearIncomingPrintLog() {
	for (size_t i = 0; i < kIncomingLogCapacity; ++i) {
		g_incomingLogSeq[i] = 0;
		g_incomingLogLines[i][0] = '\0';
	}
	g_incomingLogWritePos = 0;
	g_nextIncomingLogSeq = 1;
}
