/*
 * NPLL - System Information Menu
 *
 * Copyright (C) 2026 Techflash
 */

#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <npll/allocator.h>
#include <npll/block.h>
#include <npll/console.h>
#include <npll/cpu.h>
#include <npll/fs.h>
#include <npll/menu.h>

static void sysinfoMenuInit(struct menu *m);
static void sysinfoMenuCleanup(struct menu *m);

static struct menuEntry sysinfoBackEntry = { .name = "Back", .selected = UI_UpLevel };

static struct menuEntry *sysinfoMenuItems[] = {
	&sysinfoBackEntry,
};

struct menu UI_SysInfoMenu = {
	.header = "NPLL - System Information",
	.footer = FOOTER_CONTROLS,
	.entries = sysinfoMenuItems,
	.numEntries = 1,
	.entryCapacity = sizeof(sysinfoMenuItems) / sizeof(sysinfoMenuItems[0]),
	.init = sysinfoMenuInit,
	.cleanup = sysinfoMenuCleanup,
	.destroy = NULL,
	.previous = NULL
};

static const char *pvrToName(u32 pvr) {
	switch (pvr) {
	case 0x00083410: return "Gekko";
	case 0x00087200: return "Broadway";
	case 0x70010201: return "Espresso";
	default: return "Unknown";
	}
}

static void sysinfoMenuInit(struct menu *m) {
	char tmp[256];
	char *name;
	uint i, j;
	u32 total, used, freeBytes, largestAlloc, largestFree;

	/* allocate scratch space */
	m->content = malloc(4096);
	assert(m->content);
	memset(m->content, 0, 4096);

	/* step 1: report NPLL ver */
	strcat(m->content, "NPLL Version: " VERSION "\r\n");

	/* step 2: report console type */
	if (H_ConsoleType == CONSOLE_TYPE_GAMECUBE)
		strcat(m->content, "Console Type: Nintendo GameCube");
	else if (H_ConsoleType == CONSOLE_TYPE_WII) {
		if (H_WiiIsvWii)
			strcat(m->content, "Console Type: Nintendo Wii U (vWii)");
		else
			strcat(m->content, "Console Type: Nintendo Wii");
	}
	else if (H_ConsoleType == CONSOLE_TYPE_WII_U)
		strcat(m->content, "Console Type: Nintendo Wii U (native)");

	strcat(m->content, "\r\n");


	/* step 3: report SoC revision */

	/* step 3a: report Flipper revision if GameCube */
	if (H_ConsoleType == CONSOLE_TYPE_GAMECUBE) {
		switch (H_GCNRev) {
		case PI_CHIPID_REV_A: {
			name = "A (?!)";
			break;
		}
		case PI_CHIPID_REV_B: {
			name = "B (?!)";
			break;
		}
		case PI_CHIPID_REV_C: {
			name = "C (retail)";
			break;
		}
		default: {
			name = NULL;
			break;
		}
		}

		if (name)
			sprintf(tmp, "Flipper SoC Revision: %s", name);
		else
			sprintf(tmp, "Flipper SoC Revision: Unknown (%08x) (?!)", H_GCNRev);

		strcat(m->content, tmp);
		strcat(m->content, "\r\n");
	}

	/* step 3b: report Hollywood revision if Wii/Wii U */
	if (H_ConsoleType == CONSOLE_TYPE_WII || H_ConsoleType == CONSOLE_TYPE_WII_U) {
		if (H_ConsoleType == CONSOLE_TYPE_WII)
			name = "Hollywood SoC Revision";
		if (H_ConsoleType == CONSOLE_TYPE_WII_U || H_WiiIsvWii)
			name = "Hollywood SoC Revision (vWii Compat)";

		if (H_WiiRev == HW_VERSION_PROD_HOLLYWOOD)
			sprintf(tmp, "%s: 0x11 (Production Hollywood)", name);
		else if (H_WiiRev == HW_VERSION_PROD_BOLLYWOOD)
			sprintf(tmp, "%s: 0x21 (Production Bollywood)", name);
		else
			sprintf(tmp, "%s: 0x%02x (Unknown?!?!?!)", name, H_WiiRev);

		strcat(m->content, tmp);
		strcat(m->content, "\r\n");
	}

	/* step 3c: report Latte revision if Wii U */
	if (H_ConsoleType == CONSOLE_TYPE_WII_U) {
		switch (H_WiiURev) {
		case LT_CHIPREVID_REV_LATTE_A11: {
			name = "A11";
			break;
		}
		case LT_CHIPREVID_REV_LATTE_A12: {
			name = "A12";
			break;
		}
		case LT_CHIPREVID_REV_LATTE_A2X: {
			name = "A2X";
			break;
		}
		case LT_CHIPREVID_REV_LATTE_A3X: {
			name = "A3X";
			break;
		}
		case LT_CHIPREVID_REV_LATTE_A4X: {
			name = "A4X";
			break;
		}
		case LT_CHIPREVID_REV_LATTE_A5X: {
			name = "A5X";
			break;
		}
		case LT_CHIPREVID_REV_LATTE_B1X: {
			name = "B1X";
			break;
		}
		default: {
			name = NULL;
			break;
		}
		}

		if (name)
			sprintf(tmp, "Latte SoC Revision: %s", name);
		else
			sprintf(tmp, "Latte SoC Revision: Unknown (%08x)", H_WiiURev);

		strcat(m->content, tmp);
		strcat(m->content, "\r\n");
	}

	/* step 4: report boot method, if applicable */
	if (H_ConsoleType == CONSOLE_TYPE_GAMECUBE) /* TODO: Determine */
		strcat(m->content, "Boot method: Unknown\r\n");
	else if (H_ConsoleType == CONSOLE_TYPE_WII) {
		if (H_WiiBootIOS > 0) {
			sprintf(tmp, "Boot method: IOS %d", H_WiiBootIOS);
			strcat(m->content, tmp);
		}
		else
			strcat(m->content, "Boot method: MINI");

		strcat(m->content, "\r\n");
	}
	else if (H_ConsoleType == CONSOLE_TYPE_WII_U) /* TODO: Determine */
		strcat(m->content, "Boot method: linux-loader\r\n");

	/* step 5: report CPU PVR */
	sprintf(tmp, "CPU PVR: 0x%08x (%s)\r\n", mfspr(PVR), pvrToName(mfspr(PVR)));
	strcat(m->content, tmp);

	/* step 6: memory */
	M_PoolStats(POOL_MEM1, &total, &used, &freeBytes, &largestAlloc, &largestFree);
	sprintf(tmp, "MEM1: %uK total/%uK used/%uK free/%uK max alloc/%uK max free range\r\n", total / 1024, used / 1024, freeBytes / 1024, largestAlloc / 1024, largestFree / 1024);
	strcat(m->content, tmp);
	if (H_ConsoleType != CONSOLE_TYPE_GAMECUBE) {
		M_PoolStats(POOL_MEM2, &total, &used, &freeBytes, &largestAlloc, &largestFree);
		sprintf(tmp, "MEM2: %uK total/%uK used/%uK free/%uK max alloc/%uK max free range\r\n", total / 1024, used / 1024, freeBytes / 1024, largestAlloc / 1024, largestFree / 1024);
		strcat(m->content, tmp);
	}


	/* step 7: report attached storage */
	strcat(m->content, "Attached block devices ('[*]' = currently mounted):\r\n");
	for (i = 0; i < B_NumDevices; i++) {
		sprintf(tmp, "- %s: %llu bytes, %u partitions\r\n",
			B_Devices[i]->name, B_Devices[i]->size, B_Devices[i]->numPartitions);
		strcat(m->content, tmp);
		for (j = 0; j < B_Devices[i]->numPartitions; j++) {
			sprintf(tmp, "  - [%c] %u: %llu bytes @ 0x%llx%s%s%s\r\n",
				FS_MountedPartition == B_Devices[i]->partitions[j] ? '*' : ' ',
				j + 1, B_Devices[i]->partitions[j]->size, B_Devices[i]->partitions[j]->offset,
				FS_MountedPartition == B_Devices[i]->partitions[j] ? " (" : "",
				FS_MountedPartition == B_Devices[i]->partitions[j] ? FS_Mounted->name : "",
				FS_MountedPartition == B_Devices[i]->partitions[j] ? ")" : "");
			strcat(m->content, tmp);
		}
	}


	/* TODO: Clock speeds */
}

static void sysinfoMenuCleanup(struct menu *m) {
	free(m->content);
}
