/*
 *   This file is part of fastboot 3DS
 *   Copyright (C) 2017 derrek, profi200, d0k3
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */



#include <string.h>
#include <stdlib.h>
#include "types.h"
#include "arm11/hardware/hid.h"
#include "arm11/hardware/mcu.h"
#include "arm11/menu/battery.h"
#include "arm11/menu/bootinfo.h"
#include "arm11/menu/bootslot.h"
#include "arm11/menu/menu.h"
#include "arm11/menu/menu_util.h"
#include "arm11/menu/menu_color.h"
#include "arm11/config.h"
#include "arm11/console.h"
#include "arm11/debug.h"
#include "arm11/fmt.h"
#include "fs.h" // for drive names

static BatteryState battery;

void menuBuildDescString(char* desc, u32 flags, u32 index, const char* desc_raw)
{
	char* desc_ptr = desc;
	
	// copy raw description
	desc_ptr += ee_sprintf(desc_ptr, desc_raw);
	
	
	// handle flags
	u32	slot = -1;
	if (flags & MENU_FLAG_SLOTS)
	{
		slot = index;
	}
	else
	{
		MENU_GET_FLAGSLOT(slot, flags);
		slot--;
	}
	
	// boot mode description
	if ((flags & MENU_FLAG_BOOTMODE) && (index == N_BOOTSLOTS))
	{
		if (configDataExist(KBootMode))
		{
			char* modestr = (char*) configCopyText(KBootMode);
			if (!modestr) panicMsg("Config error");
			desc_ptr += ee_sprintf(desc_ptr, "\n \ncurrent: %s", modestr);
			free (modestr);
		}
		else desc_ptr += ee_sprintf(desc_ptr, "\n \ncurrent: - not set up -");
	}
	// boot slot description
	else if (slot < N_BOOTSLOTS)
	{
		// get strings for path and keycombo
		char slot_path_store[40+1];
		char* slot_path = NULL;
		char* keycombo = NULL;
		
		if(configDataExist(KBootOption1 + slot))
		{
			slot_path = slot_path_store;
			truncateString(slot_path, (char*) configGetData(KBootOption1 + slot), 52, 8);
		}
		
		if(configDataExist(KBootOption1Buttons + slot))
			keycombo = (char*) configCopyText(KBootOption1Buttons + slot);
		
		// write description to desc string
		desc_ptr += ee_sprintf(desc_ptr, "\n \npath: %s\nboot: %s",
			slot_path ? slot_path : "- slot disabled -",
			!slot_path ? "- slot disabled -" : keycombo ? keycombo : "bootloader (auto)");
		
		if (keycombo) free(keycombo);
	}
	
	
	// wordwrap the whole string
	stringWordWrap(desc, WORDWRAP_WIDTH);
}
	
void menuShowDesc(MenuInfo* curr_menu, PrintConsole* desc_con, u32 index)
{
	// select and clear description console
	consoleSelect(desc_con);
	consoleClear();
	
	
	// print title at the top
#ifdef NDEBUG
	const char* title = "uwuboot :3 " VERS_STRING;
#else
	const char* title = "uwuboot :3 (debug me senpai) (cuter!!)";
#endif
	consoleSetCursor(desc_con, (desc_con->windowWidth - strlen(title)) >> 1, 0);
	ee_printf(ESC_SCHEME_ACCENT0 "%s" ESC_RESET, title);
	
	u8 rtc[8];

	static const char *funny[] = {
		"im so funny", "tetoris is the best song ever", "join splatfestival", "why did i do this to myself", "when is RNEX gonna be done"
	};

	MCU_getRTCTime(rtc);

	int randomnum = rtc[0] % 5;

	// get bootinfo
	const char* mount_paths[] = { FS_DRIVE_NAMES };
	bootInfo bootinfo;
	getBootInfo(&bootinfo);
	
	// print bootinfo
	consoleSetCursor(desc_con, 0, 3);
	ee_printf("Model: Andrea 4DS XXXL\n");
	ee_printf("MY 2DS IS BURNING AHHH\n");
	ee_printf("Battery: %s%lu%s %%" ESC_RESET "\n\n",
		((battery.percent <= 20) && !battery.charging) ? ESC_SCHEME_BAD : ESC_SCHEME_GOOD,
		battery.percent, (battery.charging) ? "+" : "");
	ee_printf("%s\n", funny[randomnum]);
	// for (u32 i = 0; i < sizeof(mount_paths) / sizeof(const char*); i++)
	// {
	// 	ee_printf(ESC_SCHEME_WEAK "%s" ESC_RESET " %s\n", mount_paths[i],
	// 		((bootinfo.mountState >> i) & 0x1) ?
	// 		 ESC_SCHEME_GOOD "mounted" ESC_RESET :
	// 		 ESC_SCHEME_BAD "not mounted" ESC_RESET);
	// }
	
	
	// print config
	u32 conw = desc_con->windowWidth;
	u32 curY = 3;
	
	// print config location
	consoleSetCursor(desc_con, conw - 16, curY++);
	if(!configIsLoaded())
	{
		ee_printf("Config not found");
	}
	else
	{
		FsDevice loc_conf = configGetStorageLocation();
		ee_printf("Config from %s", (loc_conf == FS_DEVICE_SDMC) ? "SDMC" : "NAND");
	}
	
	// print boot mode
	consoleSetCursor(desc_con, conw - 16, curY++);
	if(configDataExist(KBootMode))
	{
		char* bootmode = configCopyText(KBootMode);
		ee_printf("%7.7s bootmode", bootmode);
		if (bootmode) free(bootmode);
	}
	else
	{
		ee_printf("bootmode not set");
	}
	
	// print slot config
	curY++;
	for(u32 i = 0; i < N_BOOTSLOTS; i++)
	{
		consoleSetCursor(desc_con, conw - 16, curY++);
		ee_printf(ESC_SCHEME_WEAK "slot %lu: " ESC_RESET, i + 1);
		if(configDataExist(KBootOption1 + i))
			ee_printf((configDataExist(KBootOption1Buttons + i)) ? "keycombo" : "autoboot");
		else ee_printf("   -    ");
	}
	
	// reboot slot
	u32 prevSlot = readStoredBootslot();
	consoleSetCursor(desc_con, conw - 16, curY++);
	ee_printf(ESC_SCHEME_WEAK "reboot: " ESC_RESET);
	if ((prevSlot >= 1) && (prevSlot <= N_BOOTSLOTS))
		ee_printf(" slot %lu ", prevSlot);
	else
		ee_printf("autoboot");
	
	// print dev mode config
	curY++;
	if(!configDevModeEnabled())
	{
		consoleSetCursor(desc_con, conw - 14, curY);
		ee_printf(ESC_SCHEME_BAD "debug me daddy" ESC_RESET);
	}
	
	
	// get description, check if available
	MenuEntry* entry = &(curr_menu->entries[index]);
	char* name = entry->name;
	char* desc = entry->desc;
	
	// done if no description available
	if (!name || !desc)
		return;
	
	// build description string
	char desc_ww[512];
	menuBuildDescString(desc_ww, curr_menu->flags, index, desc);
	
	// get width, height
	int desc_width = stringGetWidth(desc_ww);
	int desc_height = stringGetHeight(desc_ww);
	desc_width = (desc_width > desc_con->windowWidth) ? desc_con->windowWidth : desc_width;
	desc_height = (desc_height > desc_con->windowHeight) ? desc_con->windowHeight : desc_height;
	
	// write to console
	int desc_x = (desc_con->windowWidth - desc_width) >> 1;
	int desc_y = (desc_con->windowHeight - 1 - desc_height);
	
	consoleSetCursor(desc_con, desc_x, desc_y++);
	ee_printf(ESC_SCHEME_ACCENT1 "%s:\n" ESC_RESET, name);
	
	ee_printf(ESC_SCHEME_WEAK);
	for (char* str = strtok(desc_ww, "\n"); str != NULL; str = strtok(NULL, "\n"))
	{
		consoleSetCursor(desc_con, desc_x, desc_y++);
		ee_printf(str);
	}
	ee_printf(ESC_RESET);
}

/**
 * @brief Draws the menu to the given console.
 * @param curr_menu A struct containing info about the current menu.
 * @param menu_con Console that the menu is displayed on.
 * @param index Current placement of the cursor.
 */
void menuDraw(MenuInfo* curr_menu, PrintConsole* menu_con, u32 index)
{
	// const u32 menu_block_height = curr_menu->n_entries + 5; // +5 for title and button instructions
	int menu_x = (menu_con->windowWidth - MENU_WIDTH) >> 1;
	// int menu_y = MENU_DISP_Y + ((menu_con->windowHeight - menu_block_height) >> 1);
	int menu_y = MENU_OFFSET_TITLE;
	int menu_preset = 0xFF;
	
	// get menu preset
	if (curr_menu->preset)
		menu_preset = curr_menu->preset();
	
	// select menu console
	consoleSelect(menu_con);
	consoleClear();
	
	// menu title
	consoleSetCursor(menu_con, menu_x, menu_y++);
	ee_printf(ESC_SCHEME_ACCENT1 "%s" ESC_RESET, curr_menu->name);
	menu_y++;
	
	// menu entries
	for (u32 i = 0; i < curr_menu->n_entries; i++)
	{
		MenuEntry* entry = &(curr_menu->entries[i]);
		char* name = entry->name;
		bool is_selected = (i == index);
		
		char symbol = ' ';
		if (curr_menu->flags & MENU_FLAG_CONFIG)
			symbol = ((menu_preset >> i) & 0x1) ? '\xFB' : ' ';
		else
			symbol = ((menu_preset >> i) & 0x1) ? '+' : '-';
		
		consoleSetCursor(menu_con, menu_x, menu_y++);
		// ee_printf(((menu_preset >> i) & 0x1) ? ESC_SCHEME_STD : ESC_SCHEME_WEAK);
		ee_printf(ESC_SCHEME_STD);
		if (is_selected) ee_printf(ESC_INVERT);
		ee_printf("[%c] %-*.*s", symbol, MENU_WIDTH-4, MENU_WIDTH-4, name);
		ee_printf(ESC_RESET);
	}
	
	// button instructions
	menu_y = MENU_OFFSET_BUTTONS;
	consoleSetCursor(menu_con, menu_x, menu_y++);
	ee_printf(ESC_SCHEME_WEAK "%-*.*s" ESC_RESET, MENU_WIDTH, MENU_WIDTH, "[A]:Choose [B]:Back");
}

/**
 * @brief Processes and displays the menu and user input.
 * @param menu_con Console that the menu is displayed on.
 * @param desc_con Console that the description is displayed on.
 * @param info A complete description of the menu.
 */
u32 menuProcess(PrintConsole* menu_con, PrintConsole* desc_con, MenuInfo* info)
{
	MenuInfo* curr_menu = info;
	MenuInfo* last_menu = NULL;
	MenuInfo* prev_menu[MENU_MAX_DEPTH];
	u32 prev_index[MENU_MAX_DEPTH];
	u32 menu_lvl = 0;
	u32 index = 0;
	u32 last_index = (u32) -1;
	
	u64 n_vblanks = 0;
	getBatteryState(&battery);
	
	// main menu processing loop
	u32 menu_ret = MENU_OK;
	while (menu_ret < MENU_RET_CONTINUE)
	{
		// handle power button
		if(hidGetExtraKeys(0) & (KEY_POWER | KEY_POWER_HELD))
			break; // deinits & poweroff outside of this function
		
		// handle battery state
		if ((++n_vblanks % 250) == 0)
			getBatteryState(&battery);
		
		// update menu and description (on demand)
		if ((index != last_index) || (curr_menu != last_menu) || (n_vblanks % 250 == 0)) {
			menuDraw(curr_menu, menu_con, index);
			menuShowDesc(curr_menu, desc_con, index);
			last_index = index;
			last_menu = curr_menu;
			updateScreens(); // update screens (VBlank included)
		} else GFX_waitForEvent(GFX_EVENT_PDC0, true); // VBlank
		
		hidScanInput();
		const u32 kDown = hidKeysDown();
		const u32 extraKeys = hidGetExtraKeys(0);
		// const u32 kHeld = hidKeysHeld();
		
		if (extraKeys & KEY_SHELL)
		{
			sleepmode();
		}
		else if ((kDown & KEY_A) && (curr_menu->entries[index].function == NULL))
		{
			u32 next_menu_idx = curr_menu->entries[index].param;
			
			// store previous menu and index for return
			if (menu_lvl < MENU_MAX_DEPTH)
			{
				prev_menu[menu_lvl] = curr_menu;
				prev_index[menu_lvl] = index;
				menu_lvl++;
			}
			// enter submenu
			curr_menu = info + next_menu_idx;
			index = 0;
		}
		else if (kDown & KEY_A)
		{
			// call menu entry function
			MenuEntry* entry = &(curr_menu->entries[index]);
			menu_ret = (*(entry->function))(desc_con, menu_con, entry->param);
			// wait until HOME released
			do
			{
				GFX_waitForEvent(GFX_EVENT_PDC0, true);
				hidScanInput();
			} while (hidGetExtraKeys(0) & KEY_HOME);
			// force redraw (somewhat hacky)
			last_menu = NULL;
		}
		else if ((kDown & KEY_B) && (menu_lvl > 0))
		{
			// return to previous menu
			menu_lvl--;
			curr_menu = prev_menu[menu_lvl];
			index = prev_index[menu_lvl];
		}
		else if ((extraKeys & KEY_HOME) && (menu_lvl > 0))
		{
			// return to HOME menu
			menu_lvl = 0;
			curr_menu = info;
			index = 0;
		}
		else if (kDown & KEY_DDOWN)
		{
			// cursor down
			index = (index == curr_menu->n_entries - 1) ? 0 : index + 1;
		}
		else if (kDown & KEY_DUP)
		{
			// cursor up
			index = (index == 0) ? curr_menu->n_entries - 1 : index - 1;
		}
	}

	return menu_ret;
}
