/*
 * NPLL - Logging
 *
 * Copyright (C) 2025-2026 Techflash
 */

#include <assert.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <npll/drivers.h>
#include <npll/log_internal.h>
#include <npll/menu.h>
#include <npll/output.h>

enum logMethod L_Method = LOG_METHOD_ALL_ODEV;

#define memlogStart (char *)0x817c0000
static char *memlogNext = memlogStart;
static u32 maxSize = 0x00040000;

void L_GetMemlogBounds(const char **start, const char **end) {
	if (start)
		*start = memlogStart;
	if (end)
		*end = memlogNext;
}

static void memlogWriteChar(const char c) {
	char *removeEnd;
	u32 remaining;

	if ((u32)memlogNext >= ((u32)memlogStart + maxSize)) {
		removeEnd = memlogStart;

		while (removeEnd < memlogNext && *removeEnd != '\n')
			removeEnd++;
		if (removeEnd < memlogNext)
			removeEnd++;
		else
			removeEnd = memlogStart + 1;

		remaining = (u32)(memlogNext - removeEnd);
		memmove(memlogStart, removeEnd, remaining);
		memlogNext = memlogStart + remaining;
	}
	*memlogNext = c;
	memlogNext++;
}

void _log_puts(const char *str) {
	switch (L_Method) {
	case LOG_METHOD_ALL_ODEV: {
		while (*str) {
			memlogWriteChar(*str);
			putchar(*str++);
		}
		memlogWriteChar('\r');
		putchar('\r');
		memlogWriteChar('\n');
		putchar('\n');
		break;
	}
	case LOG_METHOD_MENU_WINDOW: {
		while (*str) {
			memlogWriteChar(*str++);
			if (memlogNext[-1] == '\n')
				UI_Invalidate();
		}
		memlogWriteChar('\r');
		memlogWriteChar('\n');
		UI_Invalidate();
		break;
	}
	case LOG_METHOD_NONE: {
		while (*str)
			memlogWriteChar(*str++);
		memlogWriteChar('\r');
		memlogWriteChar('\n');
		break;
	}
	default: {
		assert_unreachable();
	}
	}
}


static void allOdevOut(char c, void *dummy) {
	(void)dummy;

	memlogWriteChar(c);
	putchar(c);
}

static void menuWindowOut(char c, void *dummy) {
	(void)dummy;

	memlogWriteChar(c);
	if (c == '\n')
		UI_Invalidate();
}

static void noneOut(char c, void *dummy) {
	(void)dummy;

	memlogWriteChar(c);
}

void _log_printf(const char *fmt, ...) {
	va_list va;
	void (*out)(char character, void* arg);
	va_start(va, fmt);

	switch (L_Method) {
	case LOG_METHOD_ALL_ODEV: {
		out = allOdevOut;
		break;
	}
	case LOG_METHOD_MENU_WINDOW: {
		out = menuWindowOut;
		break;
	}
	case LOG_METHOD_NONE: {
		out = noneOut;
		break;
	}
	default: {
		assert_unreachable();
	}
	}

	fctvprintf(out, NULL, fmt, va);
	va_end(va);
}

void L_Init(void) {
	/*memlogWriteStr("In-Memory logger is now active\r\n"); */
	memset(memlogStart, 0, maxSize);
}
