// Copyright 2013 Normmatt / 2018 d0k3
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.

#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <stdio.h>

#include "qrcodegen.h"
#include "vram0.h"
#include "ui.h"
#include "rtc.h"
#include "timer.h"
#include "power.h"
#include "hid.h"
#include "fixp.h"

#define STRBUF_SIZE 512 // maximum size of the string buffer
#define FONT_MAX_WIDTH 8
#define FONT_MAX_HEIGHT 10
#define PROGRESS_REFRESH_RATE 30 // the progress bar is only allowed to draw to screen every X milliseconds

static u32 font_width = 0;
static u32 font_height = 0;
static u32 line_height = 0;
static u8 font_bin[FONT_MAX_HEIGHT * 256];

#define PIXEL_OFFSET(x, y)  (((x) * SCREEN_HEIGHT) + (SCREEN_HEIGHT - (y) - 1))

u8* GetFontFromPbm(const void* pbm, const u32 pbm_size, u32* w, u32* h) {
    char* hdr = (char*) pbm;
    u32 hdr_max_size = min(512, pbm_size);
    u32 pbm_w = 0;
    u32 pbm_h = 0;

    // minimum size
    if (hdr_max_size < 7) return NULL;

    // check header magic, then skip over
    if (strncmp(hdr, "P4\n", 3) != 0) return NULL;

    // skip any comments
    u32 p = 3;
    while (hdr[p] == '#') {
        while (hdr[p++] != '\n') {
            if (p >= hdr_max_size) return NULL;
        }
    }

    // parse width
    while ((hdr[p] >= '0') && (hdr[p] <= '9')) {
        if (p >= hdr_max_size) return NULL;
        pbm_w *= 10;
        pbm_w += hdr[p++] - '0';
    }

    // whitespace
    if ((hdr[p++] != ' ') || (p >= hdr_max_size))
        return NULL;

    // parse height
    while ((hdr[p] >= '0') && (hdr[p] <= '9')) {
        if (p >= hdr_max_size) return NULL;
        pbm_h *= 10;
        pbm_h += hdr[p++] - '0';
    }

    // line break
    if ((hdr[p++] != '\n') || (p >= hdr_max_size))
        return NULL;

    // check sizes
    if (pbm_w <= 8) { // 1x256 format
        if ((pbm_w > FONT_MAX_WIDTH) || (pbm_h % 256) ||
            ((pbm_h / 256) > FONT_MAX_HEIGHT) ||
            (pbm_h != (pbm_size - p)))
            return NULL;
    } else { // 16x16 format
        if ((pbm_w % 16) || (pbm_h % 16) ||
            ((pbm_w / 16) > FONT_MAX_WIDTH) ||
            ((pbm_h / 16) > FONT_MAX_HEIGHT) ||
            ((pbm_h * pbm_w / 8) != (pbm_size - p)))
            return NULL;
    }

    // all good
    if (w) *w = pbm_w;
    if (h) *h = pbm_h;
    return (u8*) pbm + p;
}

// sets the font from a given PBM
// if no PBM is given, the PBM is fetched from the default VRAM0 location
bool SetFontFromPbm(const void* pbm, u32 pbm_size) {
    u32 w, h;
    u8* ptr = NULL;

    if (!pbm) {
        u64 pbm_size64 = 0;
        pbm = FindVTarFileInfo(VRAM0_FONT_PBM, &pbm_size64);
        pbm_size = (u32) pbm_size64;
    }

    if (pbm)
        ptr = GetFontFromPbm(pbm, pbm_size, &w, &h);

    if (!ptr) {
        return false;
    } else if (w > 8) {
        font_width = w / 16;
        font_height = h / 16;
        memset(font_bin, 0x00, w * h / 8);

        for (u32 cy = 0; cy < 16; cy++) {
            for (u32 row = 0; row < font_height; row++) {
                for (u32 cx = 0; cx < 16; cx++) {
                    u32 bp0 = (cx * font_width) >> 3;
                    u32 bm0 = (cx * font_width) % 8;
                    u8 byte = ((ptr[bp0] << bm0) | (ptr[bp0+1] >> (8 - bm0))) & (0xFF << (8 - font_width));
                    font_bin[(((cy << 4) + cx) * font_height) + row] = byte;
                }
                ptr += font_width << 1;
            }
        }
    } else {
        font_width = w;
        font_height = h / 256;
        memcpy(font_bin, ptr, h);
    }

    line_height = min(10, font_height + 2);
    return true;
}

void ClearScreen(u16* screen, u32 color)
{
    u32 *screen_wide = (u32*)(void*)screen;
    int width = (screen == TOP_SCREEN) ? SCREEN_WIDTH_TOP : SCREEN_WIDTH_BOT;
    if (color == COLOR_TRANSPARENT)
        color = COLOR_BLACK;

    color |= color << 16;
    for (int i = 0; i < (width * SCREEN_HEIGHT / 2); i++)
        *(screen_wide++) = color;
}

void ClearScreenF(bool clear_main, bool clear_alt, u32 color)
{
    if (clear_main) ClearScreen(MAIN_SCREEN, color);
    if (clear_alt) ClearScreen(ALT_SCREEN, color);
}

u16 GetColor(const u16 *screen, int x, int y)
{
    return screen[PIXEL_OFFSET(x, y)];
}

void DrawPixel(u16 *screen, int x, int y, u32 color)
{
    screen[PIXEL_OFFSET(x, y)] = color;
}

void DrawRectangle(u16 *screen, int x, int y, u32 width, u32 height, u32 color)
{
    screen += PIXEL_OFFSET(x, y) - height + 1;
    while(width--) {
        for (u32 h = 0; h < height; h++)
            screen[h] = color;
        screen += SCREEN_HEIGHT;
    }
}

void DrawBitmap(u16 *screen, int x, int y, u32 w, u32 h, const u16* bitmap)
{
    // on negative values: center the bitmap
    if (x < 0) x = (SCREEN_WIDTH(screen) - w) >> 1;
    if (y < 0) y = (SCREEN_HEIGHT - h) >> 1;

    // bug out on too big bitmaps / too large dimensions
    if ((x < 0) || (y < 0) || (w > SCREEN_WIDTH(screen)) || (h > SCREEN_HEIGHT))
        return;

    screen += PIXEL_OFFSET(x, y);
    while(h--) {
        for (u32 i = 0; i < w; i++)
            screen[i * SCREEN_HEIGHT] = *(bitmap++);
        screen--;
    }
}

void DrawQrCode(u16 *screen, const u8* qrcode)
{
    const u32 size_qr = qrcodegen_getSize(qrcode);
    u32 size_qr_s = size_qr;
    u32 size_canvas = size_qr + 8;

    // handle scaling
    u32 scale = 1;
    for (; size_canvas * (scale+1) < SCREEN_HEIGHT; scale++);
    size_qr_s *= scale;
    size_canvas *= scale;

    // clear screen, draw the canvas
    u32 x_canvas = (SCREEN_WIDTH(screen) - size_canvas) / 2;
    u32 y_canvas = (SCREEN_HEIGHT - size_canvas) / 2;
    ClearScreen(screen, COLOR_STD_BG);
    DrawRectangle(screen, x_canvas, y_canvas, size_canvas, size_canvas, COLOR_WHITE);

    // draw the QR code
    u32 x_qr = (SCREEN_WIDTH(screen) - size_qr_s) / 2;
    u32 y_qr = (SCREEN_HEIGHT - size_qr_s) / 2;
    int xDisplacement = x_qr * SCREEN_HEIGHT;
    for (u32 y = 0; y < size_qr_s; y++) {
        int yDisplacement = SCREEN_HEIGHT - (y_qr + y) - 1;
        u16* screenPos = screen + xDisplacement + yDisplacement;
        for (u32 x = 0; x < size_qr_s; x++) {
            u16 c = qrcodegen_getModule(qrcode, x/scale, y/scale) ? COLOR_BLACK : COLOR_WHITE;
            *(screenPos) = c;
            screenPos += SCREEN_HEIGHT;
        }
    }
}

void DrawCharacter(u16 *screen, int character, int x, int y, u32 color, u32 bgcolor)
{
    for (int yy = 0; yy < (int) font_height; yy++) {
        int xDisplacement = x * SCREEN_HEIGHT;
        int yDisplacement = SCREEN_HEIGHT - (y + yy) - 1;
        u16* screenPos = screen + xDisplacement + yDisplacement;

        u8 charPos = font_bin[character * font_height + yy];
        for (int xx = 7; xx >= (8 - (int) font_width); xx--) {
            if ((charPos >> xx) & 1) {
                *screenPos = color;
            } else if (bgcolor != COLOR_TRANSPARENT) {
                *screenPos = bgcolor;
            }
            screenPos += SCREEN_HEIGHT;
        }
    }
}

void DrawString(u16 *screen, const char *str, int x, int y, u32 color, u32 bgcolor, bool fix_utf8)
{
    size_t max_len = (((screen == TOP_SCREEN) ? SCREEN_WIDTH_TOP : SCREEN_WIDTH_BOT) - x) / font_width;
    size_t len = (strlen(str) > max_len) ? max_len : strlen(str);

    for (size_t i = 0; i < len; i++) {
        char c = (char) (fix_utf8 && str[i] >= 0x80) ? '?' : str[i];
        DrawCharacter(screen, c, x + i * font_width, y, color, bgcolor);
    }
}

void DrawStringF(u16 *screen, int x, int y, u32 color, u32 bgcolor, const char *format, ...)
{
    char str[STRBUF_SIZE];
    va_list va;
    va_start(va, format);
    vsnprintf(str, STRBUF_SIZE, format, va);
    va_end(va);

    for (char* text = strtok(str, "\n"); text != NULL; text = strtok(NULL, "\n"), y += line_height)
        DrawString(screen, text, x, y, color, bgcolor, true);
}

void DrawStringCenter(u16 *screen, u32 color, u32 bgcolor, const char *format, ...)
{
    char str[STRBUF_SIZE];
    va_list va;
    va_start(va, format);
    vsnprintf(str, STRBUF_SIZE, format, va);
    va_end(va);

    u32 w = GetDrawStringWidth(str);
    u32 h = GetDrawStringHeight(str);
    int x = (w >= SCREEN_WIDTH(screen)) ? 0 : (SCREEN_WIDTH(screen) - w) >> 1;
    int y = (h >= SCREEN_HEIGHT) ? 0 : (SCREEN_HEIGHT - h) >> 1;

    DrawStringF(screen, x, y, color, bgcolor, "%s", str);
}

u32 GetDrawStringHeight(const char* str) {
    u32 height = font_height;
    for (char* lf = strchr(str, '\n'); (lf != NULL); lf = strchr(lf + 1, '\n'))
        height += line_height;
    return height;
}

u32 GetDrawStringWidth(const char* str) {
    u32 width = 0;
    char* old_lf = (char*) str;
    char* str_end = (char*) str + strnlen(str, STRBUF_SIZE);
    for (char* lf = strchr(str, '\n'); lf != NULL; lf = strchr(lf + 1, '\n')) {
        if ((u32) (lf - old_lf) > width) width = lf - old_lf;
        old_lf = lf;
    }
    if ((u32) (str_end - old_lf) > width)
        width = str_end - old_lf;
    width *= font_width;
    return width;
}

u32 GetFontWidth(void) {
    return font_width;
}

u32 GetFontHeight(void) {
    return font_height;
}

void WordWrapString(char* str, int llen) {
    char* last_brk = str - 1;
    char* last_spc = str - 1;
    if (!llen) llen = (SCREEN_WIDTH_MAIN / font_width);
    for (char* str_ptr = str;; str_ptr++) {
        if (!*str_ptr || (*str_ptr == ' ')) { // on space or string_end
            if (str_ptr - last_brk > llen) { // if maximum line lenght is exceeded
                if (last_spc > last_brk) { // put a line_brk at the last space
                    *last_spc = '\n';
                    last_brk = last_spc;
                    last_spc = str_ptr;
                } else if (*str_ptr) { // if we have no applicable space
                    *str_ptr = '\n';
                    last_brk = str_ptr;
                }
            } else if (*str_ptr) last_spc = str_ptr;
        } else if (*str_ptr == '\n') last_brk = str_ptr;
        if (!*str_ptr) break;
    }
}

void ResizeString(char* dest, const char* orig, int nsize, int tpos, bool align_right) {
    int osize = strnlen(orig, 256);
    if (nsize < osize) {
        TruncateString(dest, orig, nsize, tpos);
    } else if (!align_right) {
        snprintf(dest, nsize + 1, "%-*.*s", nsize, nsize, orig);
    } else {
        snprintf(dest, nsize + 1, "%*.*s", nsize, nsize, orig);
    }
}

void TruncateString(char* dest, const char* orig, int nsize, int tpos) {
    int osize = strnlen(orig, 256);
    if (nsize < 0) {
        return;
    } else if ((nsize <= 3) || (nsize >= osize)) {
        snprintf(dest, nsize + 1, "%s", orig);
    } else {
        if (tpos + 3 > nsize) tpos = nsize - 3;
        snprintf(dest, nsize + 1, "%-.*s...%-.*s", tpos, orig, nsize - (3 + tpos), orig + osize - (nsize - (3 + tpos)));
    }
}

void FormatNumber(char* str, u64 number) { // str should be 32 byte in size
    u64 mag1000 = 1;
    *str = '\0';
    for (; number / (mag1000 * 1000) > 0; mag1000 *= 1000);
    for (; mag1000 > 0; mag1000 /= 1000) {
        u32 pos = strnlen(str, 31);
        snprintf(str + pos, 31 - pos, "%0*llu%c", (pos) ? 3 : 1, (number / mag1000) % 1000, (mag1000 > 1) ? ',' : '\0');
    }
}

void FormatBytes(char* str, u64 bytes) { // str should be 32 byte in size, just to be safe
    const char* units[] = {" Byte", " kB", " MB", " GB"};

    if (bytes == (u64) -1) snprintf(str, 32, "INVALID");
    else if (bytes < 1024) snprintf(str, 32, "%llu%s", bytes, units[0]);
    else {
        u32 scale = 1;
        u64 bytes100 = (bytes * 100) >> 10;
        for(; (bytes100 >= 1024*100) && (scale < 3); scale++, bytes100 >>= 10);
        snprintf(str, 32, "%llu.%llu%s", bytes100 / 100, (bytes100 % 100) / 10, units[scale]);
    }
}

void ShowStringF(u16* screen, const char *format, ...)
{
    ClearScreen(MAIN_SCREEN, COLOR_STD_BG);
    if (format && *format) { // only if there is something in there
        char str[STRBUF_SIZE];
        va_list va;
        va_start(va, format);
        vsnprintf(str, STRBUF_SIZE, format, va);
        va_end(va);

        DrawStringCenter(screen, COLOR_STD_FONT, COLOR_STD_BG, "%s", str);
    }
}

void ShowString(const char *format, ...)
{
    ClearScreenF(true, false, COLOR_STD_BG);
    if (format && *format) { // only if there is something in there
        char str[STRBUF_SIZE];
        va_list va;
        va_start(va, format);
        vsnprintf(str, STRBUF_SIZE, format, va);
        va_end(va);

        DrawStringCenter(MAIN_SCREEN, COLOR_STD_FONT, COLOR_STD_BG, "%s", str);
    }
}

void ShowIconStringF(u16* screen, u16* icon, int w, int h, const char *format, ...)
{
    static const u32 icon_offset = 10;
    u32 str_width, str_height, tot_height;
    u32 x_str, y_str, x_bmp, y_bmp;

    ClearScreen(screen, COLOR_STD_BG);
    if (!format || !*format) return; // only if there is something in there

    char str[STRBUF_SIZE];
    va_list va;
    va_start(va, format);
    vsnprintf(str, STRBUF_SIZE, format, va);
    va_end(va);

    str_width = GetDrawStringWidth(str);
    str_height = GetDrawStringHeight(str);
    tot_height = h + icon_offset + str_height;
    x_str = (str_width >= SCREEN_WIDTH(screen)) ? 0 : (SCREEN_WIDTH(screen) - str_width) / 2;
    y_str = (str_height >= SCREEN_HEIGHT) ? 0 : h + icon_offset + (SCREEN_HEIGHT - tot_height) / 2;
    x_bmp = (w >= SCREEN_WIDTH(screen)) ? 0 : (SCREEN_WIDTH(screen) - w) / 2;
    y_bmp = (tot_height >= SCREEN_HEIGHT) ? 0 : (SCREEN_HEIGHT - tot_height) / 2;

    DrawBitmap(screen, x_bmp, y_bmp, w, h, icon);
    DrawStringF(screen, x_str, y_str, COLOR_STD_FONT, COLOR_STD_BG, "%s", str);
}

void ShowIconString(u16* icon, int w, int h, const char *format, ...)
{
    char str[STRBUF_SIZE];
    va_list va;
    va_start(va, format);
    vsnprintf(str, STRBUF_SIZE, format, va);
    va_end(va);

    ShowIconStringF(MAIN_SCREEN, icon, w, h, str);
}

bool ShowPrompt(bool ask, const char *format, ...)
{
    bool ret = true;

    char str[STRBUF_SIZE];
    va_list va;
    va_start(va, format);
    vsnprintf(str, STRBUF_SIZE, format, va);
    va_end(va);

    ClearScreenF(true, false, COLOR_STD_BG);
    DrawStringCenter(MAIN_SCREEN, COLOR_STD_FONT, COLOR_STD_BG, "%s\n \n%s", str,
        (ask) ? "(<A> yes, <B> no)" : "(<A> to continue)");

    while (true) {
        u32 pad_state = InputWait(0);
        if (pad_state & BUTTON_A) break;
        else if (pad_state & BUTTON_B) {
            ret = false;
            break;
        }
    }

    ClearScreenF(true, false, COLOR_STD_BG);

    return ret;
}

#ifndef AUTO_UNLOCK
#define PRNG (*(volatile u32*)0x10011000)
bool ShowUnlockSequence(u32 seqlvl, const char *format, ...) {
    static const int seqcolors[7] = { COLOR_STD_FONT, COLOR_BRIGHTGREEN,
        COLOR_BRIGHTYELLOW, COLOR_ORANGE, COLOR_BRIGHTBLUE, COLOR_RED, COLOR_DARKRED };
    const u32 seqlen_max = 7;
    const u32 seqlen = seqlen_max - ((seqlvl < 3) ? 2 : (seqlvl < 4) ? 1 : 0);

    u32 color_bg = COLOR_STD_BG;
    u32 color_font = COLOR_STD_FONT;
    u32 color_off = COLOR_GREY;
    u32 color_on = seqcolors[seqlvl];
    u32 lvl = 0;

    u32 str_width, str_height;
    u32 x, y;

    char str[STRBUF_SIZE];
    va_list va;
    va_start(va, format);
    vsnprintf(str, STRBUF_SIZE, format, va);
    va_end(va);

    str_width = GetDrawStringWidth(str);
    str_height = GetDrawStringHeight(str) + (4*line_height);
    if (str_width < 24 * font_width) str_width = 24 * font_width;
    x = (str_width >= SCREEN_WIDTH_MAIN) ? 0 : (SCREEN_WIDTH_MAIN - str_width) / 2;
    y = (str_height >= SCREEN_HEIGHT) ? 0 : (SCREEN_HEIGHT - str_height) / 2;

    if (seqlvl >= 6) { // special handling
        color_bg = seqcolors[seqlvl];
        color_font = COLOR_BLACK;
        color_off = COLOR_BLACK;
        color_on = COLOR_DARKGREY;
    }

    ClearScreenF(true, false, color_bg);
    DrawStringF(MAIN_SCREEN, x, y, color_font, color_bg, "%s", str);
    #ifndef TIMER_UNLOCK
    DrawStringF(MAIN_SCREEN, x, y + str_height - 28, color_font, color_bg, "To proceed, enter this:");

    // generate sequence
    const char dpad_symbols[] = { '\x1A', '\x1B', '\x18', '\x19' }; // R L U D

    u32 sequence[seqlen_max];
    char seqsymbols[seqlen_max];
    u32 lastlsh = (u32) -1;
    for (u32 n = 0; n < (seqlen-1); n++) {
        u32 lsh = lastlsh;
        while (lsh == lastlsh) lsh = (PRNG & 0x3);
        lastlsh = lsh;
        sequence[n] = BUTTON_RIGHT << lsh;
        seqsymbols[n] = dpad_symbols[lsh];
    }
    sequence[seqlen-1] = BUTTON_A;
    seqsymbols[seqlen-1] = 'A';


    while (true) {
        for (u32 n = 0; n < seqlen; n++) {
            DrawStringF(MAIN_SCREEN, x + (n*4*FONT_WIDTH_EXT), y + str_height - 28 + line_height,
                (lvl > n) ? color_on : color_off, color_bg, "<%c>", seqsymbols[n]);
        }
        if (lvl == seqlen)
            break;
        u32 pad_state = InputWait(0);
        if (!(pad_state & BUTTON_ANY))
            continue;
        else if ((pad_state & BUTTON_ANY) == sequence[lvl])
            lvl++;
        else if (pad_state & BUTTON_B)
            break;
        else if (lvl == 0 || !(pad_state & sequence[lvl-1]))
            lvl = 0;
    }
    #else
    DrawStringF(MAIN_SCREEN, x, y + str_height - 28, color_font, color_bg, "To proceed, hold <X>:");

    while (!CheckButton(BUTTON_B)) {
        for (u32 n = 0; n < seqlen; n++) {
            DrawStringF(MAIN_SCREEN, x + (n*4*FONT_WIDTH_EXT), y + str_height - 18,
                (lvl > n) ? color_on : color_off, color_bg, "<%c>", (lvl > n) ? 'X' : ' ');
        }
        if (lvl == seqlen)
            break;

        u32 prev_lvl = lvl;
        while (lvl == prev_lvl) {
            u64 timer = timer_start();
            while ((lvl != 0) && CheckButton(BUTTON_X) && (timer_msec(timer) < 400));

            if (CheckButton(BUTTON_B)) break;
            else if (CheckButton(BUTTON_X)) lvl++;
            else lvl = 0;
        }
    }
    #endif

    ClearScreenF(true, false, COLOR_STD_BG);
    return (lvl >= seqlen);
}
#endif

u32 ShowSelectPrompt(u32 n, const char** options, const char *format, ...) {
    u32 str_width, str_height;
    u32 x, y, yopt;
    u32 sel = 0;

    char str[STRBUF_SIZE];
    va_list va;
    va_start(va, format);
    vsnprintf(str, STRBUF_SIZE, format, va);
    va_end(va);

    if (n == 0) return 0; // check for low number of options
    // else if (n == 1) return ShowPrompt(true, "%s\n%s?", str, options[0]) ? 1 : 0;

    str_width = GetDrawStringWidth(str);
    str_height = GetDrawStringHeight(str) + (n * (line_height + 2)) + (3 * line_height);
    if (str_width < 24 * font_width) str_width = 24 * font_width;
    for (u32 i = 0; i < n; i++) if (str_width < GetDrawStringWidth(options[i]))
        str_width = GetDrawStringWidth(options[i]);
    x = (str_width >= SCREEN_WIDTH_MAIN) ? 0 : (SCREEN_WIDTH_MAIN - str_width) / 2;
    y = (str_height >= SCREEN_HEIGHT) ? 0 : (SCREEN_HEIGHT - str_height) / 2;
    yopt = y + GetDrawStringHeight(str) + 8;

    ClearScreenF(true, false, COLOR_STD_BG);
    DrawStringF(MAIN_SCREEN, x, y, COLOR_STD_FONT, COLOR_STD_BG, "%s", str);
    DrawStringF(MAIN_SCREEN, x, yopt + (n*(line_height+2)) + line_height, COLOR_STD_FONT, COLOR_STD_BG, "(<A> select, <B> cancel)");
    while (true) {
        for (u32 i = 0; i < n; i++) {
            DrawStringF(MAIN_SCREEN, x, yopt + ((line_height+2)*i), (sel == i) ? COLOR_STD_FONT : COLOR_LIGHTGREY, COLOR_STD_BG, "%2.2s %s",
                (sel == i) ? "->" : "", options[i]);
        }
        u32 pad_state = InputWait(0);
        if (pad_state & BUTTON_DOWN) sel = (sel+1) % n;
        else if (pad_state & BUTTON_UP) sel = (sel+n-1) % n;
        else if (pad_state & BUTTON_A) break;
        else if (pad_state & BUTTON_B) {
            sel = n;
            break;
        }
    }

    ClearScreenF(true, false, COLOR_STD_BG);

    return (sel >= n) ? 0 : sel + 1;
}

u32 ShowFileScrollPrompt(u32 n, const DirEntry** options, bool hide_ext, const char *format, ...) {
    u32 str_height, fname_len;
    u32 x, y, yopt;
    const u32 item_width = SCREEN_WIDTH(MAIN_SCREEN) - 40;
    int sel = 0, scroll = 0;
    u32 n_show = min(n, 10);

    char str[STRBUF_SIZE];
    va_list va;
    va_start(va, format);
    vsnprintf(str, STRBUF_SIZE, format, va);
    va_end(va);

    if (n == 0) return 0; // check for low number of options
    // else if (n == 1) return ShowPrompt(true, "%s\n%s?", str, options[0]) ? 1 : 0;

    str_height = GetDrawStringHeight(str) + (n_show * (line_height + 2)) + (4 * line_height);
    x = (SCREEN_WIDTH_MAIN - item_width) / 2;
    y = (str_height >= SCREEN_HEIGHT) ? 0 : (SCREEN_HEIGHT - str_height) / 2;
    yopt = y + GetDrawStringHeight(str) + 8;
    fname_len = min(64, item_width / FONT_WIDTH_EXT - 14);

    ClearScreenF(true, false, COLOR_STD_BG);
    DrawStringF(MAIN_SCREEN, x, y, COLOR_STD_FONT, COLOR_STD_BG, "%s", str);
    DrawStringF(MAIN_SCREEN, x, yopt + (n_show*(line_height+2)) + line_height, COLOR_STD_FONT, COLOR_STD_BG, "(<A> select, <B> cancel)");
    while (true) {
        for (u32 i = scroll; i < scroll+n_show; i++) {
            char bytestr[16];
            FormatBytes(bytestr, options[i]->size);

            char content_str[64 + 1];
            char temp_str[256];
            strncpy(temp_str, options[i]->name, 255);

            char* dot = strrchr(temp_str, '.');
            if (hide_ext && dot) *dot = '\0';

            ResizeString(content_str, temp_str, fname_len, 8, false);

            DrawStringF(MAIN_SCREEN, x, yopt + ((line_height+2)*(i-scroll)),
                (sel == (int)i) ? COLOR_STD_FONT : COLOR_ENTRY(options[i]), COLOR_STD_BG, "%2.2s %s",
                (sel == (int)i) ? "->" : "", content_str);

            DrawStringF(MAIN_SCREEN, x + item_width - font_width * 11, yopt + ((line_height+2)*(i-scroll)),
                (sel == (int)i) ? COLOR_STD_FONT : COLOR_ENTRY(options[i]), COLOR_STD_BG, "%10.10s",
                (options[i]->type == T_DIR) ? "(dir)" : (options[i]->type == T_DOTDOT) ? "(..)" : bytestr);
        }
        // show [n more]
        if (n - n_show - scroll) {
            char more_str[64 + 1];
            snprintf(more_str, 64, "   [%d more]", (int)(n - (n_show-1) - scroll));
            DrawStringF(MAIN_SCREEN, x, yopt + (line_height+2)*(n_show-1), COLOR_LIGHTGREY, COLOR_STD_BG, "%-*s", item_width / font_width, more_str);
        }
        // show scroll bar
        u32 bar_x = x + item_width + 2;
        const u32 flist_height = (n_show * (line_height + 2));
        const u32 bar_width = 2;
        if (n > n_show) { // draw position bar at the right
            const u32 bar_height_min = 32;
            u32 bar_height = (n_show * flist_height) / n;
            if (bar_height < bar_height_min) bar_height = bar_height_min;
            const u32 bar_y = ((u64) scroll * (flist_height - bar_height)) / (n - n_show) + yopt;

            DrawRectangle(MAIN_SCREEN, bar_x, yopt, bar_width, (bar_y - yopt), COLOR_STD_BG);
            DrawRectangle(MAIN_SCREEN, bar_x, bar_y + bar_height, bar_width, SCREEN_HEIGHT - (bar_y + bar_height), COLOR_STD_BG);
            DrawRectangle(MAIN_SCREEN, bar_x, bar_y, bar_width, bar_height, COLOR_SIDE_BAR);
        } else DrawRectangle(MAIN_SCREEN, bar_x, yopt, bar_width, flist_height, COLOR_STD_BG);

        u32 pad_state = InputWait(0);
        if (pad_state & BUTTON_DOWN) sel++;
        else if (pad_state & BUTTON_UP) sel--;
        else if (pad_state & BUTTON_RIGHT) sel += n_show;
        else if (pad_state & BUTTON_LEFT) sel -= n_show;
        else if (pad_state & BUTTON_A) break;
        else if (pad_state & BUTTON_B) {
            sel = n;
            break;
        }
        if (sel < 0) sel = 0;
        else if (sel >= (int)n) sel = n-1;
        if (sel < scroll) scroll = sel;
        else if (sel == (int) n-1 && sel >= (int)(scroll + n_show - 1)) scroll = sel - n_show + 1;
        else if (sel >= (int)(scroll + (n_show-1) - 1)) scroll = sel - (n_show-1) + 1;
    }

    ClearScreenF(true, false, COLOR_STD_BG);

    return (sel >= (int)n) ? 0 : sel + 1;
}

u32 ShowHotkeyPrompt(u32 n, const char** options, const u32* keys, const char *format, ...) {
    char str[STRBUF_SIZE];
    char* ptr = str;
    va_list va;
    va_start(va, format);
    ptr += vsnprintf(ptr, STRBUF_SIZE, format, va);
    va_end(va);

    ptr += snprintf(ptr, STRBUF_SIZE - (ptr-str), "\n ");
    for (u32 i = 0; i < n; i++) {
        char buttonstr[16];
        ButtonToString(keys[i], buttonstr);
        ptr += snprintf(ptr, STRBUF_SIZE - (ptr-str), "\n<%s> %s", buttonstr, options[i]);
    }
    ptr += snprintf(ptr, STRBUF_SIZE - (ptr-str), "\n \n<%s> %s", "B", "cancel");

    ClearScreenF(true, false, COLOR_STD_BG);
    DrawStringCenter(MAIN_SCREEN, COLOR_STD_FONT, COLOR_STD_BG, "%s", str);

    u32 sel = 0;
    while (!sel) {
        u32 pad_state = InputWait(0);
        if (pad_state & BUTTON_B) break;
        for (u32 i = 0; i < n; i++) {
            if (keys[i] & pad_state) {
                sel = i+1;
                break;
            }
        }
    }

    ClearScreenF(true, false, COLOR_STD_BG);
    return sel;
}

bool ShowInputPrompt(char* inputstr, u32 max_size, u32 resize, const char* alphabet, const char *format, va_list va) {
    const u32 alphabet_size = strnlen(alphabet, 256);
    const u32 input_shown = 22;
    const u32 fast_scroll = 4;
    const u64 aprv_delay = 128;

    u32 str_width, str_height;
    u32 x, y;

    char str[STRBUF_SIZE];
    vsnprintf(str, STRBUF_SIZE, format, va);

    // check / fix up the input string if required
    if (max_size < 2) return false; // catching this, too
    if ((*inputstr == '\0') || (resize && (strnlen(inputstr, max_size - 1) % resize))) {
        memset(inputstr, alphabet[0], resize); // set the string if it is not set or invalid
        inputstr[resize] = '\0';
    }

    str_width = GetDrawStringWidth(str);
    str_height = GetDrawStringHeight(str) + 88;
    if (str_width < (24 * font_width)) str_width = 24 * font_width;
    x = (str_width >= SCREEN_WIDTH_MAIN) ? 0 : (SCREEN_WIDTH_MAIN - str_width) / 2;
    y = (str_height >= SCREEN_HEIGHT) ? 0 : (SCREEN_HEIGHT - str_height) / 2;

    ClearScreenF(true, false, COLOR_STD_BG);
    DrawStringF(MAIN_SCREEN, x, y, COLOR_STD_FONT, COLOR_STD_BG, "%s", str);
    DrawStringF(MAIN_SCREEN, x + 8, y + str_height - 40, COLOR_STD_FONT, COLOR_STD_BG,
        "R - (\x18\x19) fast scroll\nL - clear data%s", resize ? "\nX - remove char\nY - insert char" : "");

    // wait for all keys released
    while (HID_ReadState() & BUTTON_ANY);

    int cursor_a = -1;
    u32 cursor_s = 0;
    u32 scroll = 0;
    u64 aprv = 0;
    bool ret = false;

    while (true) {
        u32 inputstr_size = strnlen(inputstr, max_size - 1);
        if (cursor_s < scroll) scroll = cursor_s;
        else if (cursor_s - scroll >= input_shown) scroll = cursor_s - input_shown + 1;
        while (scroll && (inputstr_size - scroll < input_shown)) scroll--;
        DrawStringF(MAIN_SCREEN, x, y + str_height - 76, COLOR_STD_FONT, COLOR_STD_BG, "%c%-*.*s%c%-*.*s\n%-*.*s^%-*.*s",
            (scroll) ? '<' : '|',
            (inputstr_size > input_shown) ? input_shown : inputstr_size,
            (inputstr_size > input_shown) ? input_shown : inputstr_size,
            inputstr + scroll,
            (inputstr_size - scroll > input_shown) ? '>' : '|',
            (inputstr_size > input_shown) ? 0 : input_shown - inputstr_size,
            (inputstr_size > input_shown) ? 0 : input_shown - inputstr_size,
            "",
            1 + cursor_s - scroll,
            1 + cursor_s - scroll,
            "",
            input_shown - (cursor_s - scroll),
            input_shown - (cursor_s - scroll),
            ""
        );
        if (cursor_a < 0) {
            for (cursor_a = alphabet_size - 1; (cursor_a > 0) && (alphabet[cursor_a] != inputstr[cursor_s]); cursor_a--);
        }

        // alphabet preview
        if (alphabet_size > (SCREEN_WIDTH(MAIN_SCREEN) / font_width)) {
            const u32 aprv_y = y + str_height - 60;
            if (timer_msec(aprv) < aprv_delay) {
                const u32 aprv_pad = 1;
                const u32 aprv_cx = x + ((1 + cursor_s - scroll) * font_width);
                u32 aprv_x = aprv_cx % (font_width + aprv_pad);
                u32 aprv_n = ((SCREEN_WIDTH(MAIN_SCREEN) - aprv_x) / (font_width + aprv_pad)) - 1;
                int aprv_a = cursor_a - ((aprv_cx - aprv_x) / (font_width + aprv_pad));
                while (aprv_a < 0) aprv_a += alphabet_size;
                for (u32 i = 0; i < aprv_n; i++) {
                    DrawCharacter(MAIN_SCREEN, alphabet[aprv_a], aprv_x, aprv_y,
                        (aprv_a == cursor_a) ? COLOR_WHITE : COLOR_GREY, COLOR_STD_BG);
                    if (++aprv_a >= (int) alphabet_size) aprv_a -= alphabet_size;
                    aprv_x += font_width + aprv_pad;
                }
            } else DrawRectangle(MAIN_SCREEN, 0, aprv_y, SCREEN_WIDTH(MAIN_SCREEN), font_height, COLOR_STD_BG);
        }

        u32 pad_state = InputWait(3);
        if (pad_state & (BUTTON_UP|BUTTON_DOWN|BUTTON_R1)) aprv = timer_start();
        if (pad_state & BUTTON_A) {
            ret = true;
            break;
        } else if (pad_state & BUTTON_B) {
            break;
        } else if (pad_state & BUTTON_L1) {
            cursor_a = 0;
            memset(inputstr, alphabet[0], inputstr_size);
            if (resize) {
                cursor_s = 0;
                inputstr[resize] = '\0';
            }
        } else if (pad_state & BUTTON_X) {
            if (resize && (inputstr_size > resize)) {
                char* inputfrom = inputstr + cursor_s - (cursor_s % resize) + resize;
                char* inputto = inputstr + cursor_s - (cursor_s % resize);
                memmove(inputto, inputfrom, max_size - (inputfrom - inputstr));
                inputstr_size -= resize;
                while (cursor_s >= inputstr_size)
                    cursor_s--;
                cursor_a = -1;
            } else if (resize == 1) {
                inputstr[0] = alphabet[0];
                cursor_a = 0;
            }
        } else if (pad_state & BUTTON_Y) {
            if (resize && (inputstr_size < max_size - resize)) {
                char* inputfrom = inputstr + cursor_s - (cursor_s % resize);
                char* inputto = inputstr + cursor_s - (cursor_s % resize) + resize;
                memmove(inputto, inputfrom, max_size - (inputto - inputstr));
                inputstr_size += resize;
                memset(inputfrom, alphabet[0], resize);
                cursor_a = 0;
            }
        } else if (pad_state & BUTTON_UP) {
            cursor_a += (pad_state & BUTTON_R1) ? fast_scroll : 1;
            cursor_a = cursor_a % alphabet_size;
            inputstr[cursor_s] = alphabet[cursor_a];
        } else if (pad_state & BUTTON_DOWN) {
            cursor_a -= (pad_state & BUTTON_R1) ? fast_scroll : 1;
            if (cursor_a < 0) cursor_a = alphabet_size + cursor_a;
            inputstr[cursor_s] = alphabet[cursor_a];
        } else if (pad_state & BUTTON_LEFT) {
            if (cursor_s > 0) cursor_s--;
            cursor_a = -1;
        } else if (pad_state & BUTTON_RIGHT) {
            if (cursor_s < max_size - 2) cursor_s++;
            if (cursor_s >= inputstr_size) {
                memset(inputstr + cursor_s, alphabet[0], resize);
                inputstr[cursor_s + resize] = '\0';
            }
            cursor_a = -1;
        }
    }
    // remove any trailing spaces
    for (char* cc = inputstr + strnlen(inputstr, max_size) - 1;
        (*cc == ' ') && (cc > inputstr); *(cc--) = '\0');

    ClearScreenF(true, false, COLOR_STD_BG);

    return ret;
}

bool ShowStringPrompt(char* inputstr, u32 max_size, const char *format, ...) {
    const char* alphabet = " aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ(){}[]'`^,~*?!@#$%&0123456789=+-_.";
    bool ret = false;
    va_list va;

    va_start(va, format);
    ret = ShowInputPrompt(inputstr, max_size, 1, alphabet, format, va);
    va_end(va);

    return ret;
}

u64 ShowHexPrompt(u64 start_val, u32 n_digits, const char *format, ...) {
    const char* alphabet = "0123456789ABCDEF";
    char inputstr[16 + 1];
    u64 ret = 0;
    va_list va;

    if (n_digits > 16) n_digits = 16;
    snprintf(inputstr, 16 + 1, "%0*llX", (int) n_digits, start_val);

    va_start(va, format);
    if (ShowInputPrompt(inputstr, n_digits + 1, 0, alphabet, format, va)) {
        sscanf(inputstr, "%llX", &ret);
    } else ret = (u64) -1;
    va_end(va);

    return ret;
}

u64 ShowNumberPrompt(u64 start_val, const char *format, ...) {
    const char* alphabet = "0123456789";
    char inputstr[20 + 1];
    u64 ret = 0;
    va_list va;

    snprintf(inputstr, 20 + 1, "%llu", start_val);

    va_start(va, format);
    if (ShowInputPrompt(inputstr, 20 + 1, 1, alphabet, format, va)) {
        sscanf(inputstr, "%llu", &ret);
    } else ret = (u64) -1;
    va_end(va);

    return ret;
}

bool ShowDataPrompt(u8* data, u32* size, const char *format, ...) {
    const char* alphabet = "0123456789ABCDEF";
    char inputstr[128 + 1]; // maximum size of data: 64 byte
    bool ret = false;
    va_list va;

    if (*size == 0) *inputstr = 0;
    else if (*size > 64) *size = 64;

    for (u32 i = 0; i < *size; i++)
        snprintf(inputstr + (2*i), 128 + 1 - (2*i), "%02X", (unsigned int) data[i]);

    va_start(va, format);
    if (ShowInputPrompt(inputstr, 128 + 1, 2, alphabet, format, va)) {
        *size = strnlen(inputstr, 128) / 2;
        for (u32 i = 0; i < *size; i++) {
            char bytestr[2 + 1];
            unsigned int byte;
            strncpy(bytestr, inputstr + (2*i), 2);
            sscanf(bytestr, "%02X", &byte);
            data[i] = (u8) byte;
        }
        ret = true;
    }
    va_end(va);

    return ret;
}


bool ShowRtcSetterPrompt(void* time, const char *format, ...) {
    DsTime* dstime = (DsTime*) time;
    u32 str_width, str_height;
    u32 x, y;

    char str[STRBUF_SIZE];
    va_list va;
    va_start(va, format);
    vsnprintf(str, STRBUF_SIZE, format, va);
    va_end(va);

    // check the initial time
    if (!is_valid_dstime(dstime)) {
        dstime->bcd_h = dstime->bcd_m = dstime->bcd_s = 0x00;
        dstime->bcd_D = dstime->bcd_M = 0x01;
        dstime->bcd_Y = 0x00;
    }

    str_width = GetDrawStringWidth(str);
    str_height = GetDrawStringHeight(str) + (4*line_height);
    if (str_width < (19 * font_width)) str_width = 19 * font_width;
    x = (str_width >= SCREEN_WIDTH_MAIN) ? 0 : (SCREEN_WIDTH_MAIN - str_width) / 2;
    y = (str_height >= SCREEN_HEIGHT) ? 0 : (SCREEN_HEIGHT - str_height) / 2;

    ClearScreenF(true, false, COLOR_STD_BG);
    DrawStringF(MAIN_SCREEN, x, y, COLOR_STD_FONT, COLOR_STD_BG, "%s", str);

    int cursor = 0;
    bool ret = false;
    while (true) {
        static const int val_max[] = { 99, 12, 31, 23, 59, 59 };
        static const int val_min[] = {  0,  1,  1,  0,  0,  0 };
        u8* bcd = &(((u8*)dstime)[(cursor<3) ? (6-cursor) : (6-1-cursor)]);
        int val = BCD2NUM(*bcd);
        int max = val_max[cursor];
        int min = val_min[cursor];
        DrawStringF(MAIN_SCREEN, x, y + str_height - 28, COLOR_STD_FONT, COLOR_STD_BG, "YYYY-MM-DD hh:mm:ss");
        DrawStringF(MAIN_SCREEN, x, y + str_height - 18, COLOR_STD_FONT, COLOR_STD_BG, "20%02lX-%02lX-%02lX %02lX:%02lX:%02lX\n  %-*.*s^^%-*.*s",
            (u32) dstime->bcd_Y, (u32) dstime->bcd_M, (u32) dstime->bcd_D, (u32) dstime->bcd_h, (u32) dstime->bcd_m, (u32) dstime->bcd_s,
            cursor * 3, cursor * 3, "", 17 - 2 - (cursor * 3), 17 - 2 - (cursor * 3), "");

        // user input
        u32 pad_state = InputWait(0);
        if ((pad_state & BUTTON_A) && is_valid_dstime(dstime)) {
            ret = true;
            break;
        } else if (pad_state & BUTTON_B) {
            break;
        } else if (pad_state & BUTTON_UP) {
            val += (pad_state & BUTTON_R1) ? 10 : 1;
            if (val > max) val = max;
        } else if (pad_state & BUTTON_DOWN) {
            val -= (pad_state & BUTTON_R1) ? 10 : 1;
            if (val < min) val = min;
        } else if (pad_state & BUTTON_LEFT) {
            if (--cursor < 0) cursor = 5;
        } else if (pad_state & BUTTON_RIGHT) {
            if (++cursor > 5) cursor = 0;
        }

        // update bcd
        *bcd = NUM2BCD(val);
    }

    ClearScreenF(true, false, COLOR_STD_BG);
    return ret;
}

bool ShowProgress(u64 current, u64 total, const char* opstr)
{
    static u32 last_prog_width = 0;
    static u64 timer = 0;
    const u32 bar_width = 240;
    const u32 bar_height = 12;
    const u32 bar_pos_x = (SCREEN_WIDTH_MAIN - bar_width) / 2;
    const u32 bar_pos_y = (SCREEN_HEIGHT / 2) - bar_height - 2 - 10;
    const u32 text_pos_y = bar_pos_y + bar_height + 2;
    u32 prog_width = ((total > 0) && (current <= total)) ? (current * (bar_width-4)) / total : 0;
    u32 prog_percent = ((total > 0) && (current <= total)) ? (current * 100) / total : 0;
    char tempstr[64];
    char progstr[64];

    static u64 last_msec_elapsed = 0;
    static u64 last_sec_remain = 0;
    if (!current) {
        timer = timer_start();
        last_sec_remain = 0;
    } else if (timer_msec(timer) < last_msec_elapsed + PROGRESS_REFRESH_RATE) return !CheckButton(BUTTON_B);
    last_msec_elapsed = timer_msec(timer);
    u64 sec_elapsed = (total > 0) ? timer_sec( timer ) : 0;
    u64 sec_total = (current > 0) ? (sec_elapsed * total) / current : 0;
    u64 sec_remain = (!last_sec_remain) ? (sec_total - sec_elapsed) : ((last_sec_remain + (sec_total - sec_elapsed) + 1) / 2);
    if (sec_remain >= 60 * 60) sec_remain = 60 * 60 - 1;
    last_sec_remain = sec_remain;

    if (!current || last_prog_width > prog_width) {
        ClearScreenF(true, false, COLOR_STD_BG);
        DrawRectangle(MAIN_SCREEN, bar_pos_x, bar_pos_y, bar_width, bar_height, COLOR_STD_FONT);
        DrawRectangle(MAIN_SCREEN, bar_pos_x + 1, bar_pos_y + 1, bar_width - 2, bar_height - 2, COLOR_STD_BG);
    }
    DrawRectangle(MAIN_SCREEN, bar_pos_x + 2, bar_pos_y + 2, prog_width, bar_height - 4, COLOR_STD_FONT);
    DrawRectangle(MAIN_SCREEN, bar_pos_x + 2 + prog_width, bar_pos_y + 2, (bar_width-4) - prog_width, bar_height - 4, COLOR_STD_BG);

    TruncateString(progstr, opstr, min(63, (bar_width / FONT_WIDTH_EXT) - 7), 8);
    snprintf(tempstr, 64, "%s (%lu%%)", progstr, prog_percent);
    ResizeString(progstr, tempstr, bar_width / FONT_WIDTH_EXT, 8, false);
    DrawString(MAIN_SCREEN, progstr, bar_pos_x, text_pos_y, COLOR_STD_FONT, COLOR_STD_BG, true);
    if (sec_elapsed >= 1) {
        snprintf(tempstr, 16, "ETA %02llum%02llus", sec_remain / 60, sec_remain % 60);
        ResizeString(progstr, tempstr, 16, 8, true);
        DrawString(MAIN_SCREEN, progstr, bar_pos_x + bar_width - 1 - (FONT_WIDTH_EXT * 16),
            bar_pos_y - line_height - 1, COLOR_STD_FONT, COLOR_STD_BG, true);
    }
    DrawString(MAIN_SCREEN, "(hold B to cancel)", bar_pos_x + 2, text_pos_y + 14, COLOR_STD_FONT, COLOR_STD_BG, false);

    last_prog_width = prog_width;

    return !CheckButton(BUTTON_B);
}

int ShowBrightnessConfig(int set_brightness)
{
    const int old_brightness = set_brightness;
    u32 btn_input, bar_count;
    int bar_x_pos, bar_y_pos, bar_width, bar_height;

    const char *brightness_str =
        "[\x1B] Decrease brightness\n"
        "[\x1A] Increase brightness\n"
        " \n"
        "[X] Use volume slider control\n"
        "[A] Set current brightness\n"
        "[B] Cancel";
    static const u16 brightness_slider_colmasks[] = {
        COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_WHITE
    };

    ClearScreen(MAIN_SCREEN, COLOR_STD_BG);

    bar_count = countof(brightness_slider_colmasks);
    bar_width = (SCREEN_WIDTH_MAIN / 8) * 6;
    bar_x_pos = SCREEN_WIDTH_MAIN / 8;

    bar_height = 10;
    bar_y_pos = SCREEN_HEIGHT / 4;

    // default to average brightness if invalid / automatic
    if (set_brightness < BRIGHTNESS_MIN || set_brightness > BRIGHTNESS_MAX)
        set_brightness = (BRIGHTNESS_MAX + BRIGHTNESS_MIN) / 2;

    // draw initial UI stuff
    DrawStringF(MAIN_SCREEN,
        (SCREEN_WIDTH_MAIN - GetDrawStringWidth(brightness_str)) / 2,
        (SCREEN_HEIGHT / 4) * 2, COLOR_STD_FONT, COLOR_STD_BG, brightness_str);

    // draw all color gradient bars
    for (int x = 0; x < bar_width; x++) {
        u32 intensity;
        u16 intensity_mask;

        intensity = FIXP_TO_INT(fixp_changespace(
            INT_TO_FIXP(x),
            INT_TO_FIXP(0), INT_TO_FIXP(bar_width),
            INT_TO_FIXP(0), INT_TO_FIXP(256)
        ));

        intensity_mask = RGB(intensity, intensity, intensity);

        for (u32 b = 0; b < bar_count; b++) {
            u16 *screen_base = &MAIN_SCREEN[PIXEL_OFFSET(bar_x_pos + x, (b * bar_height) + bar_y_pos)];
            for (int y = 0; y < bar_height; y++)
                *(screen_base++) = brightness_slider_colmasks[b] & intensity_mask;
        }
    }

    while(1) {
        int prev_brightness, slider_x_pos, slider_y_pos;

        prev_brightness = set_brightness;
        slider_y_pos = bar_y_pos + (bar_height * 3) + font_height;

        if (set_brightness != BRIGHTNESS_AUTOMATIC) {
            slider_x_pos = bar_x_pos + font_width + FIXP_TO_INT(fixp_changespace(
                INT_TO_FIXP(set_brightness),
                INT_TO_FIXP(BRIGHTNESS_MIN), INT_TO_FIXP(BRIGHTNESS_MAX),
                INT_TO_FIXP(0), INT_TO_FIXP(bar_width)
            ));

            // redraw the slider position character (if necessary)
            DrawCharacter(MAIN_SCREEN, '^', slider_x_pos,
                slider_y_pos, COLOR_STD_FONT, COLOR_STD_BG);
        }

        btn_input = InputWait(0);

        // draw a small rectangle to clear the character
        if (set_brightness != BRIGHTNESS_AUTOMATIC) {
            DrawRectangle(MAIN_SCREEN, slider_x_pos,
                slider_y_pos, font_width, font_height, COLOR_STD_BG);
        }

        if (btn_input & BUTTON_LEFT) {
            set_brightness -= 10;
        } else if (btn_input & BUTTON_RIGHT) {
            set_brightness += 10;
        } else if (btn_input & BUTTON_X) {
            set_brightness = BRIGHTNESS_AUTOMATIC;
            break;
        } else if (btn_input & BUTTON_B) {
            set_brightness = old_brightness;
            break;
        } else if (btn_input & BUTTON_A) {
            break;
        }

        if (set_brightness != BRIGHTNESS_AUTOMATIC)
            set_brightness = clamp(set_brightness, BRIGHTNESS_MIN, BRIGHTNESS_MAX);

        if (set_brightness != prev_brightness)
            SetScreenBrightness(set_brightness);
    }

    ClearScreen(MAIN_SCREEN, COLOR_STD_BG);
    SetScreenBrightness(set_brightness);
    return set_brightness;
}
