/*
 * Copyright (c) Adubbz
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU General Public License,
 * version 2, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope 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 <algorithm>
#include <cstdarg>
#include <cstdio>
#include <cstring>
#include <limits>
#include <dirent.h>
#include "ui.hpp"
#include "ui_util.hpp"
#include "assert.hpp"

namespace dbk {

    namespace {

        static constexpr u32 ExosphereApiVersionConfigItem = 65000;
        static constexpr u32 ExosphereHasRcmBugPatch       = 65004;
        static constexpr u32 ExosphereEmummcType           = 65007;
        static constexpr u32 ExosphereSupportedHosVersion  = 65011;

        /* Insets of content within windows. */
        static constexpr float HorizontalInset       = 20.0f;
        static constexpr float BottomInset           = 20.0f;

        /* Insets of content within text areas. */
        static constexpr float TextHorizontalInset   = 8.0f;
        static constexpr float TextVerticalInset     = 8.0f;

        static constexpr float ButtonHeight          = 60.0f;
        static constexpr float ButtonHorizontalGap   = 10.0f;

        static constexpr float VerticalGap           = 10.0f;

        u32 g_screen_width;
        u32 g_screen_height;

        constinit u32 g_supported_version = std::numeric_limits<u32>::max();

        std::shared_ptr<Menu> g_current_menu;
        bool g_initialized = false;
        bool g_exit_requested = false;

        PadState g_pad;

        u32 g_prev_touch_count = -1;
        HidTouchScreenState g_start_touch;
        bool g_started_touching = false;
        bool g_tapping = false;
        bool g_touches_moving = false;
        bool g_finished_touching = false;

        /* Update install state. */
        char g_update_path[FS_MAX_PATH];
        bool g_reset_to_factory = false;
        bool g_exfat_supported = false;
        bool g_use_exfat = false;

        constexpr u32 MaxTapMovement = 20;

        void UpdateInput() {
            /* Scan for input and update touch state. */
            padUpdate(&g_pad);
            HidTouchScreenState current_touch;
            hidGetTouchScreenStates(&current_touch, 1);
            const u32 touch_count = current_touch.count;

            if (g_prev_touch_count == 0 && touch_count > 0) {
                hidGetTouchScreenStates(&g_start_touch, 1);
                g_started_touching = true;
                g_tapping = true;
            } else {
                g_started_touching = false;
            }

            if (g_prev_touch_count > 0 && touch_count == 0) {
                g_finished_touching = true;
                g_tapping = false;
            } else {
                g_finished_touching = false;
            }

            /* Check if currently moving. */
            if (g_prev_touch_count > 0 && touch_count > 0) {
                if ((abs(current_touch.touches[0].x - g_start_touch.touches[0].x) > MaxTapMovement || abs(current_touch.touches[0].y - g_start_touch.touches[0].y) > MaxTapMovement)) {
                    g_touches_moving = true;
                    g_tapping = false;
                } else {
                    g_touches_moving = false;
                }
            } else {
                g_touches_moving = false;
            }

            /* Update the previous touch count. */
            g_prev_touch_count = current_touch.count;
        }

        void ChangeMenu(std::shared_ptr<Menu> menu) {
            g_current_menu = menu;
        }

        void ReturnToPreviousMenu() {
            /* Go to the previous menu if there is one. */
            if (g_current_menu->GetPrevMenu() != nullptr) {
                g_current_menu = g_current_menu->GetPrevMenu();
            }
        }

        Result IsPathBottomLevel(const char *path, bool *out) {
            Result rc = 0;
            FsFileSystem *fs;
            char translated_path[FS_MAX_PATH] = {};
            DBK_ABORT_UNLESS(fsdevTranslatePath(path, &fs, translated_path) != -1);

            FsDir dir;
            if (R_FAILED(rc = fsFsOpenDirectory(fs, translated_path, FsDirOpenMode_ReadDirs, &dir))) {
                return rc;
            }

            s64 entry_count;
            if (R_FAILED(rc = fsDirGetEntryCount(&dir, &entry_count))) {
                return rc;
            }

            *out = entry_count == 0;
            fsDirClose(&dir);
            return rc;
        }

        u32 EncodeVersion(u32 major, u32 minor, u32 micro, u32 relstep = 0) {
            return ((major & 0xFF) << 24) | ((minor & 0xFF) << 16) | ((micro & 0xFF) << 8) | ((relstep & 0xFF) << 8);
        }

    }

    void Menu::AddButton(u32 id, const char *text, float x, float y, float w, float h) {
        DBK_ABORT_UNLESS(id < MaxButtons);
        Button button = {
            .id = id,
            .selected = false,
            .enabled = true,
            .x = x,
            .y = y,
            .w = w,
            .h = h,
        };

        strncpy(button.text, text, sizeof(button.text)-1);
        m_buttons[id] = button;
    }

    void Menu::SetButtonSelected(u32 id, bool selected) {
        DBK_ABORT_UNLESS(id < MaxButtons);
        auto &button = m_buttons[id];

        if (button) {
            button->selected = selected;
        }
    }

    void Menu::DeselectAllButtons() {
        for (auto &button : m_buttons) {
            /* Ensure button is present. */
            if (!button) {
                continue;
            }
            button->selected = false;
        }
    }

    void Menu::SetButtonEnabled(u32 id, bool enabled) {
        DBK_ABORT_UNLESS(id < MaxButtons);
        auto &button = m_buttons[id];
        button->enabled = enabled;
    }

    Button *Menu::GetButton(u32 id) {
        DBK_ABORT_UNLESS(id < MaxButtons);
        return !m_buttons[id] ? nullptr : &(*m_buttons[id]);
    }

    Button *Menu::GetSelectedButton() {
        for (auto &button : m_buttons) {
            if (button && button->enabled && button->selected) {
                return &(*button);
            }
        }

        return nullptr;
    }

    Button *Menu::GetClosestButtonToSelection(Direction direction) {
        const Button *selected_button = this->GetSelectedButton();

        if (selected_button == nullptr || direction == Direction::Invalid) {
            return nullptr;
        }

        Button *closest_button = nullptr;
        float closest_distance = 0.0f;

        for (auto &button : m_buttons) {
            /* Skip absent button. */
            if (!button || !button->enabled) {
                continue;
            }

            /* Skip buttons that are in the wrong direction. */
            if ((direction == Direction::Down && button->y <= selected_button->y)  ||
                (direction == Direction::Up && button->y >= selected_button->y)    ||
                (direction == Direction::Right && button->x <= selected_button->x) ||
                (direction == Direction::Left && button->x >= selected_button->x)) {
                continue;
            }

            const float x_dist = button->x - selected_button->x;
            const float y_dist = button->y - selected_button->y;
            const float sq_dist = x_dist * x_dist + y_dist * y_dist;

            /* If we don't already have a closest button, set it. */
            if (closest_button == nullptr) {
                closest_button = &(*button);
                closest_distance = sq_dist;
                continue;
            }

            /* Update the closest button if this one is closer. */
            if (sq_dist < closest_distance) {
                closest_button = &(*button);
                closest_distance = sq_dist;
            }
        }

        return closest_button;
    }

    Button *Menu::GetTouchedButton() {
        HidTouchScreenState current_touch;
        hidGetTouchScreenStates(&current_touch, 1);
        const u32 touch_count = current_touch.count;

        for (u32 i = 0; i < touch_count && g_started_touching; i++) {
            for (auto &button : m_buttons) {
                if (button && button->enabled && button->IsPositionInBounds(current_touch.touches[i].x, current_touch.touches[i].y)) {
                    return &(*button);
                }
            }
        }

        return nullptr;
    }

    Button *Menu::GetActivatedButton() {
        Button *selected_button = this->GetSelectedButton();

        if (selected_button == nullptr) {
            return nullptr;
        }

        const u64 k_down = padGetButtonsDown(&g_pad);

        if (k_down & HidNpadButton_A || this->GetTouchedButton() == selected_button) {
            return selected_button;
        }

        return nullptr;
    }

    void Menu::UpdateButtons() {
        const u64 k_down = padGetButtonsDown(&g_pad);
        Direction direction = Direction::Invalid;

        if (k_down & HidNpadButton_AnyDown) {
            direction = Direction::Down;
        } else if (k_down & HidNpadButton_AnyUp) {
            direction = Direction::Up;
        } else if (k_down & HidNpadButton_AnyLeft) {
            direction = Direction::Left;
        } else if (k_down & HidNpadButton_AnyRight) {
            direction = Direction::Right;
        }

        /* Select the closest button. */
        if (const Button *closest_button = this->GetClosestButtonToSelection(direction); closest_button != nullptr) {
            this->DeselectAllButtons();
            this->SetButtonSelected(closest_button->id, true);
        }

        /* Select the touched button. */
        if (const Button *touched_button = this->GetTouchedButton(); touched_button != nullptr) {
            this->DeselectAllButtons();
            this->SetButtonSelected(touched_button->id, true);
        }
    }

    void Menu::DrawButtons(NVGcontext *vg, u64 ns) {
        for (auto &button : m_buttons) {
            /* Ensure button is present. */
            if (!button) {
                continue;
            }

            /* Set the button style. */
            auto style = ButtonStyle::StandardDisabled;
            if (button->enabled) {
                style = button->selected ? ButtonStyle::StandardSelected : ButtonStyle::Standard;
            }

            DrawButton(vg, button->text, button->x, button->y, button->w, button->h, style, ns);
        }
    }

    void Menu::LogText(const char *format, ...) {
        /* Create a temporary string. */
        char tmp[0x100];
        va_list args;
        va_start(args, format);
        vsnprintf(tmp, sizeof(tmp), format, args);
        va_end(args);

        /* Append the text to the log buffer. */
        strncat(m_log_buffer, tmp, sizeof(m_log_buffer)-1);
    }

    std::shared_ptr<Menu> Menu::GetPrevMenu() {
        return m_prev_menu;
    }

    AlertMenu::AlertMenu(std::shared_ptr<Menu> prev_menu, const char *text, const char *subtext, Result rc) : Menu(prev_menu), m_text{}, m_subtext{}, m_result_text{}, m_rc(rc){
        /* Copy the input text. */
        strncpy(m_text, text, sizeof(m_text)-1);
        strncpy(m_subtext, subtext, sizeof(m_subtext)-1);

        /* Copy result text if there is a result. */
        if (R_FAILED(rc)) {
            snprintf(m_result_text, sizeof(m_result_text), "Result: 0x%08x", rc);
        }
    }

    void AlertMenu::Draw(NVGcontext *vg, u64 ns) {
        const float window_height = WindowHeight + (R_FAILED(m_rc) ? SubTextHeight : 0.0f);
        const float x = g_screen_width / 2.0f - WindowWidth / 2.0f;
        const float y = g_screen_height / 2.0f - window_height / 2.0f;

        DrawWindow(vg, m_text, x, y, WindowWidth, window_height);
        DrawText(vg, x + HorizontalInset, y + TitleGap, WindowWidth - HorizontalInset * 2.0f, m_subtext);

        /* Draw the result if there is one. */
        if (R_FAILED(m_rc)) {
            DrawText(vg, x + HorizontalInset, y + TitleGap + SubTextHeight, WindowWidth - HorizontalInset * 2.0f, m_result_text);
        }

        this->DrawButtons(vg, ns);
    }

    ErrorMenu::ErrorMenu(const char *text, const char *subtext, Result rc) : AlertMenu(nullptr, text, subtext, rc)  {
        const float window_height = WindowHeight + (R_FAILED(m_rc) ? SubTextHeight : 0.0f);
        const float x = g_screen_width / 2.0f - WindowWidth / 2.0f;
        const float y = g_screen_height / 2.0f - window_height / 2.0f;
        const float button_y = y + TitleGap + SubTextHeight + VerticalGap * 2.0f + (R_FAILED(m_rc) ? SubTextHeight : 0.0f);
        const float button_width = WindowWidth - HorizontalInset * 2.0f;

        /* Add buttons. */
        this->AddButton(ExitButtonId, "Exit", x + HorizontalInset, button_y, button_width, ButtonHeight);
        this->SetButtonSelected(ExitButtonId, true);
    }

    void ErrorMenu::Update(u64 ns) {
        u64 k_down = padGetButtonsDown(&g_pad);

        /* Go back if B is pressed. */
        if (k_down & HidNpadButton_B) {
            g_exit_requested = true;
            return;
        }

        /* Take action if a button has been activated. */
        if (const Button *activated_button = this->GetActivatedButton(); activated_button != nullptr) {
            switch (activated_button->id) {
                case ExitButtonId:
                    g_exit_requested = true;
                    break;
            }
        }

        this->UpdateButtons();

        /* Fallback on selecting the exfat button. */
        if (const Button *selected_button = this->GetSelectedButton(); k_down && selected_button == nullptr) {
            this->SetButtonSelected(ExitButtonId, true);
        }
    }

    WarningMenu::WarningMenu(std::shared_ptr<Menu> prev_menu, std::shared_ptr<Menu> next_menu, const char *text, const char *subtext, Result rc) : AlertMenu(prev_menu, text, subtext, rc), m_next_menu(next_menu) {
        const float window_height = WindowHeight + (R_FAILED(m_rc) ? SubTextHeight : 0.0f);
        const float x = g_screen_width / 2.0f - WindowWidth / 2.0f;
        const float y = g_screen_height / 2.0f - window_height / 2.0f;

        const float button_y = y + TitleGap + SubTextHeight + VerticalGap * 2.0f + (R_FAILED(m_rc) ? SubTextHeight : 0.0f);
        const float button_width = (WindowWidth - HorizontalInset * 2.0f) / 2.0f - ButtonHorizontalGap;
        this->AddButton(BackButtonId, "Back", x + HorizontalInset, button_y, button_width, ButtonHeight);
        this->AddButton(ContinueButtonId, "Continue", x + HorizontalInset + button_width + ButtonHorizontalGap, button_y, button_width, ButtonHeight);
        this->SetButtonSelected(ContinueButtonId, true);
    }

    void WarningMenu::Update(u64 ns) {
        u64 k_down = padGetButtonsDown(&g_pad);

        /* Go back if B is pressed. */
        if (k_down & HidNpadButton_B) {
            ReturnToPreviousMenu();
            return;
        }

        /* Take action if a button has been activated. */
        if (const Button *activated_button = this->GetActivatedButton(); activated_button != nullptr) {
            switch (activated_button->id) {
                case BackButtonId:
                    ReturnToPreviousMenu();
                    return;
                case ContinueButtonId:
                    ChangeMenu(m_next_menu);
                    return;
            }
        }

        this->UpdateButtons();

        /* Fallback on selecting the exfat button. */
        if (const Button *selected_button = this->GetSelectedButton(); k_down && selected_button == nullptr) {
            this->SetButtonSelected(ContinueButtonId, true);
        }
    }

    MainMenu::MainMenu() : Menu(nullptr) {
        const float x = g_screen_width / 2.0f - WindowWidth / 2.0f;
        const float y = g_screen_height / 2.0f - WindowHeight / 2.0f;

        this->AddButton(InstallButtonId, "Install", x + HorizontalInset, y + TitleGap, WindowWidth - HorizontalInset * 2, ButtonHeight);
        this->AddButton(ExitButtonId, "Exit", x + HorizontalInset, y + TitleGap + ButtonHeight + VerticalGap, WindowWidth - HorizontalInset * 2, ButtonHeight);
        this->SetButtonSelected(InstallButtonId, true);
    }

    void MainMenu::Update(u64 ns) {
        u64 k_down = padGetButtonsDown(&g_pad);

        if (k_down & HidNpadButton_B) {
            g_exit_requested = true;
        }

        /* Take action if a button has been activated. */
        if (const Button *activated_button = this->GetActivatedButton(); activated_button != nullptr) {
            switch (activated_button->id) {
                case InstallButtonId:
                {
                    const auto file_menu = std::make_shared<FileMenu>(g_current_menu, "/");

                    Result rc = 0;
                    u64 hardware_type;
                    u64 has_rcm_bug_patch;
                    u64 is_emummc;

                    if (R_FAILED(rc = splGetConfig(SplConfigItem_HardwareType, &hardware_type))) {
                        ChangeMenu(std::make_shared<ErrorMenu>("An error has occurred", "Failed to get hardware type.", rc));
                        return;
                    }

                    if (R_FAILED(rc = splGetConfig(static_cast<SplConfigItem>(ExosphereHasRcmBugPatch), &has_rcm_bug_patch))) {
                        ChangeMenu(std::make_shared<ErrorMenu>("An error has occurred", "Failed to check RCM bug status.", rc));
                        return;
                    }

                    if (R_FAILED(rc = splGetConfig(static_cast<SplConfigItem>(ExosphereEmummcType), &is_emummc))) {
                        ChangeMenu(std::make_shared<ErrorMenu>("An error has occurred", "Failed to check emuMMC status.", rc));
                        return;
                    }

                    /* Warn if we're working with a patched unit. */
                    const bool is_erista = hardware_type == 0 || hardware_type == 1;
                    if (is_erista && has_rcm_bug_patch && !is_emummc) {
                        ChangeMenu(std::make_shared<WarningMenu>(g_current_menu, file_menu, "Warning: Patched unit detected", "You may burn fuses or render your switch inoperable."));
                    } else {
                        ChangeMenu(file_menu);
                    }

                    return;
                }
                case ExitButtonId:
                    g_exit_requested = true;
                    return;
            }
        }

        this->UpdateButtons();

        /* Fallback on selecting the install button. */
        if (const Button *selected_button = this->GetSelectedButton(); k_down && selected_button == nullptr) {
            this->SetButtonSelected(InstallButtonId, true);
        }
    }

    void MainMenu::Draw(NVGcontext *vg, u64 ns) {
        DrawWindow(vg, "Daybreak", g_screen_width / 2.0f - WindowWidth / 2.0f, g_screen_height / 2.0f - WindowHeight / 2.0f, WindowWidth, WindowHeight);
        this->DrawButtons(vg, ns);
    }

    FileMenu::FileMenu(std::shared_ptr<Menu> prev_menu, const char *root) : Menu(prev_menu), m_current_index(0), m_scroll_offset(0), m_touch_start_scroll_offset(0), m_touch_finalize_selection(false) {
        Result rc = 0;

        strncpy(m_root, root, sizeof(m_root)-1);

        if (R_FAILED(rc = this->PopulateFileEntries())) {
            fatalThrow(rc);
        }
    }

    Result FileMenu::PopulateFileEntries() {
        /* Open the directory. */
        DIR *dir = opendir(m_root);
        if (dir == nullptr) {
            return fsdevGetLastResult();
        }

        /* Add file entries to the list. */
        struct dirent *ent;
        while ((ent = readdir(dir)) != nullptr) {
            if (ent->d_type == DT_DIR) {
                FileEntry file_entry = {};
                strncpy(file_entry.name, ent->d_name, sizeof(file_entry.name));
                m_file_entries.push_back(file_entry);
            }
        }

        /* Close the directory. */
        closedir(dir);

        /* Sort the file entries. */
        std::sort(m_file_entries.begin(), m_file_entries.end(), [](const FileEntry &a, const FileEntry &b) {
            return strncmp(a.name, b.name, sizeof(a.name)) < 0;
        });

        return 0;
    }

    bool FileMenu::IsSelectionVisible() {
        const float visible_start = m_scroll_offset;
        const float visible_end = visible_start + FileListHeight;
        const float entry_start = static_cast<float>(m_current_index) * (FileRowHeight + FileRowGap);
        const float entry_end = entry_start + (FileRowHeight + FileRowGap);
        return entry_start >= visible_start && entry_end <= visible_end;
    }

    void FileMenu::ScrollToSelection() {
        const float visible_start = m_scroll_offset;
        const float visible_end = visible_start + FileListHeight;
        const float entry_start = static_cast<float>(m_current_index) * (FileRowHeight + FileRowGap);
        const float entry_end = entry_start + (FileRowHeight + FileRowGap);

        if (entry_end > visible_end) {
            m_scroll_offset += entry_end - visible_end;
        } else if (entry_end < visible_end) {
            m_scroll_offset = entry_start;
        }
    }

    bool FileMenu::IsEntryTouched(u32 i) {
        const float x = g_screen_width / 2.0f - WindowWidth / 2.0f;
        const float y = g_screen_height / 2.0f - WindowHeight / 2.0f;

        HidTouchScreenState current_touch;
        hidGetTouchScreenStates(&current_touch, 1);

        /* Check if the tap is within the x bounds. */
        if (current_touch.touches[0].x >= x + TextBackgroundOffset + FileRowHorizontalInset && current_touch.touches[0].x <= WindowWidth - (TextBackgroundOffset + FileRowHorizontalInset) * 2.0f) {
            const float y_min = y + TitleGap + FileRowGap + i * (FileRowHeight + FileRowGap) - m_scroll_offset;
            const float y_max = y_min + FileRowHeight;

            /* Check if the tap is within the y bounds. */
            if (current_touch.touches[0].y >= y_min && current_touch.touches[0].y <= y_max) {
                return true;
            }
        }

        return false;
    }

    void FileMenu::UpdateTouches() {
        /* Setup values on initial touch. */
        if (g_started_touching) {
            m_touch_start_scroll_offset = m_scroll_offset;

            /* We may potentially finalize the selection later if we start off touching it. */
            if (this->IsEntryTouched(m_current_index)) {
                m_touch_finalize_selection = true;
            }
        }

        /* Scroll based on touch movement. */
        if (g_touches_moving) {
            HidTouchScreenState current_touch;
            hidGetTouchScreenStates(&current_touch, 1);

            const int dist_y = current_touch.touches[0].y - g_start_touch.touches[0].y;
            float new_scroll_offset = m_touch_start_scroll_offset - static_cast<float>(dist_y);
            float max_scroll = (FileRowHeight + FileRowGap) * static_cast<float>(m_file_entries.size()) - FileListHeight;

            /* Don't allow scrolling if there is not enough elements. */
            if (max_scroll < 0.0f) {
                max_scroll = 0.0f;
            }

            /* Don't allow scrolling before the first element. */
            if (new_scroll_offset < 0.0f) {
                new_scroll_offset = 0.0f;
            }

            /* Don't allow scrolling past the last element. */
            if (new_scroll_offset > max_scroll) {
                new_scroll_offset = max_scroll;
            }

            m_scroll_offset = new_scroll_offset;
        }

        /* Select any tapped entries. */
        if (g_tapping) {
            for (u32 i = 0; i < m_file_entries.size(); i++) {
                if (this->IsEntryTouched(i)) {
                    /* The current index is checked later. */
                    if (i == m_current_index) {
                        continue;
                    }

                    m_current_index = i;

                    /* Don't finalize selection if we touch something else. */
                    m_touch_finalize_selection = false;
                    break;
                }
            }
        }

        /* Don't finalize selection if we aren't finished and we've either stopped tapping or are no longer touching the selection. */
        if (!g_finished_touching && (!g_tapping || !this->IsEntryTouched(m_current_index))) {
            m_touch_finalize_selection = false;
        }

        /* Finalize selection if the currently selected entry is touched for the second time. */
        if (g_finished_touching && m_touch_finalize_selection) {
            this->FinalizeSelection();
            m_touch_finalize_selection = false;
        }
    }

    void FileMenu::FinalizeSelection() {
        DBK_ABORT_UNLESS(m_current_index < m_file_entries.size());
        FileEntry &entry = m_file_entries[m_current_index];

        /* Determine the selected path. */
        char current_path[FS_MAX_PATH] = {};
        const int path_len = snprintf(current_path, sizeof(current_path), "%s%s/", m_root, entry.name);
        DBK_ABORT_UNLESS(path_len >= 0 && path_len < static_cast<int>(sizeof(current_path)));

        /* Determine if the chosen path is the bottom level. */
        Result rc = 0;
        bool bottom_level;
        if (R_FAILED(rc = IsPathBottomLevel(current_path, &bottom_level))) {
            fatalThrow(rc);
        }

        /* Show exfat settings or the next file menu. */
        if (bottom_level) {
            /* Set the update path. */
            snprintf(g_update_path, sizeof(g_update_path), "%s", current_path);

            /* Change the menu. */
            ChangeMenu(std::make_shared<ValidateUpdateMenu>(g_current_menu));
        } else {
            ChangeMenu(std::make_shared<FileMenu>(g_current_menu, current_path));
        }
    }

    void FileMenu::Update(u64 ns) {
        u64 k_down = padGetButtonsDown(&g_pad);

        /* Go back if B is pressed. */
        if (k_down & HidNpadButton_B) {
            ReturnToPreviousMenu();
            return;
        }

        /* Finalize selection on pressing A. */
        if (k_down & HidNpadButton_A) {
            this->FinalizeSelection();
        }

        /* Update touch input. */
        this->UpdateTouches();

        const u32 prev_index = m_current_index;

        if (k_down & HidNpadButton_AnyDown) {
            /* Scroll down. */
            if (m_current_index >= (m_file_entries.size() - 1)) {
                m_current_index = 0;
            } else {
                m_current_index++;
            }
        } else if (k_down & HidNpadButton_AnyUp) {
            /* Scroll up. */
            if (m_current_index == 0) {
                m_current_index = m_file_entries.size() - 1;
            } else {
                m_current_index--;
            }
        }

        /* Scroll to the selection if it isn't visible. */
        if (prev_index != m_current_index && !this->IsSelectionVisible()) {
            this->ScrollToSelection();
        }
    }

    void FileMenu::Draw(NVGcontext *vg, u64 ns) {
        const float x = g_screen_width / 2.0f - WindowWidth / 2.0f;
        const float y = g_screen_height / 2.0f - WindowHeight / 2.0f;

        DrawWindow(vg, "Select an update directory", x, y, WindowWidth, WindowHeight);
        DrawTextBackground(vg, x + TextBackgroundOffset, y + TitleGap, WindowWidth - TextBackgroundOffset * 2.0f, (FileRowHeight + FileRowGap) * MaxFileRows + FileRowGap);

        nvgSave(vg);
        nvgScissor(vg, x + TextBackgroundOffset, y + TitleGap, WindowWidth - TextBackgroundOffset * 2.0f, (FileRowHeight + FileRowGap) * MaxFileRows + FileRowGap);

        for (u32 i = 0; i < m_file_entries.size(); i++) {
            FileEntry &entry = m_file_entries[i];
            auto style = ButtonStyle::FileSelect;

            if (i == m_current_index) {
                style = ButtonStyle::FileSelectSelected;
            }

            DrawButton(vg, entry.name, x + TextBackgroundOffset + FileRowHorizontalInset, y + TitleGap + FileRowGap + i * (FileRowHeight + FileRowGap) - m_scroll_offset, WindowWidth - (TextBackgroundOffset + FileRowHorizontalInset) * 2.0f, FileRowHeight, style, ns);
        }

        nvgRestore(vg);
    }

    ValidateUpdateMenu::ValidateUpdateMenu(std::shared_ptr<Menu> prev_menu) : Menu(prev_menu), m_has_drawn(false), m_has_info(false), m_has_validated(false) {
        const float x = g_screen_width / 2.0f - WindowWidth / 2.0f;
        const float y = g_screen_height / 2.0f - WindowHeight / 2.0f;
        const float button_width = (WindowWidth - HorizontalInset * 2.0f) / 2.0f - ButtonHorizontalGap;

        /* Add buttons. */
        this->AddButton(BackButtonId, "Back", x + HorizontalInset, y + WindowHeight - BottomInset - ButtonHeight, button_width, ButtonHeight);
        this->AddButton(ContinueButtonId, "Continue", x + HorizontalInset + button_width + ButtonHorizontalGap, y + WindowHeight - BottomInset - ButtonHeight, button_width, ButtonHeight);
        this->SetButtonEnabled(BackButtonId, false);
        this->SetButtonEnabled(ContinueButtonId, false);

        /* Obtain update information. */
        if (R_FAILED(this->GetUpdateInformation())) {
            this->SetButtonEnabled(BackButtonId, true);
            this->SetButtonSelected(BackButtonId, true);
        } else {
            /* Log this early so it is printed out before validation causes stalling. */
            this->LogText("Validating update, this may take a moment...\n");
        }
    }

    Result ValidateUpdateMenu::GetUpdateInformation() {
        Result rc = 0;
        this->LogText("Directory %s\n", g_update_path);

        /* Attempt to get the update information. */
        if (R_FAILED(rc = amssuGetUpdateInformation(&m_update_info, g_update_path))) {
            if (rc == 0x1a405) {
                this->LogText("No update found in folder.\nEnsure your ncas are named correctly!\nResult: 0x%08x\n", rc);
            } else {
                this->LogText("Failed to get update information.\nResult: 0x%08x\n", rc);
            }
            return rc;
        }

        /* Print update information. */
        this->LogText("- Version: %d.%d.%d\n", (m_update_info.version >> 26) & 0x1f, (m_update_info.version >> 20) & 0x1f, (m_update_info.version >> 16) & 0xf);
        if (m_update_info.exfat_supported) {
            this->LogText("- exFAT: Supported\n");
        } else {
            this->LogText("- exFAT: Unsupported\n");
        }
        this->LogText("- Firmware variations: %d\n", m_update_info.num_firmware_variations);

        /* Mark as having obtained update info. */
        m_has_info = true;
        return rc;
    }

    void ValidateUpdateMenu::ValidateUpdate() {
        Result rc = 0;

        /* Validate the update. */
        if (R_FAILED(rc = amssuValidateUpdate(&m_validation_info, g_update_path))) {
            this->LogText("Failed to validate update.\nResult: 0x%08x\n", rc);
            return;
        }

        /* Check the result. */
        if (R_SUCCEEDED(m_validation_info.result)) {
            this->LogText("Update is valid!\n");

            if (R_FAILED(m_validation_info.exfat_result)) {
                const u32 version = m_validation_info.invalid_key.version;
                this->LogText("exFAT Validation failed with result: 0x%08x\n", m_validation_info.exfat_result);
                this->LogText("Missing content:\n- Program id: %016lx\n- Version: %d.%d.%d\n", m_validation_info.invalid_key.id, (version >> 26) & 0x1f, (version >> 20) & 0x1f, (version >> 16) & 0xf);

                /* Log the missing content id. */
                this->LogText("- Content id: ");
                for (size_t i = 0; i < sizeof(NcmContentId); i++) {
                    this->LogText("%02x", m_validation_info.invalid_content_id.c[i]);
                }
                this->LogText("\n");
            }

            /* Enable the back and continue buttons and select the continue button. */
            this->SetButtonEnabled(BackButtonId, true);
            this->SetButtonEnabled(ContinueButtonId, true);
            this->SetButtonSelected(ContinueButtonId, true);
        } else {
            /* Log the missing content info. */
            const u32 version = m_validation_info.invalid_key.version;
            this->LogText("Validation failed with result: 0x%08x\n", m_validation_info.result);
            this->LogText("Missing content:\n- Program id: %016lx\n- Version: %d.%d.%d\n", m_validation_info.invalid_key.id, (version >> 26) & 0x1f, (version >> 20) & 0x1f, (version >> 16) & 0xf);

            /* Log the missing content id. */
            this->LogText("- Content id: ");
            for (size_t i = 0; i < sizeof(NcmContentId); i++) {
                this->LogText("%02x", m_validation_info.invalid_content_id.c[i]);
            }
            this->LogText("\n");

            /* Enable the back button and select it. */
            this->SetButtonEnabled(BackButtonId, true);
            this->SetButtonSelected(BackButtonId, true);
        }

        /* Mark validation as being complete. */
        m_has_validated = true;
    }

    void ValidateUpdateMenu::Update(u64 ns) {
        /* Perform validation if it hasn't been done already. */
        if (m_has_info && m_has_drawn && !m_has_validated) {
            this->ValidateUpdate();
        }

        u64 k_down = padGetButtonsDown(&g_pad);

        /* Go back if B is pressed. */
        if (k_down & HidNpadButton_B) {
            ReturnToPreviousMenu();
            return;
        }

        /* Take action if a button has been activated. */
        if (const Button *activated_button = this->GetActivatedButton(); activated_button != nullptr) {
            switch (activated_button->id) {
                case BackButtonId:
                    ReturnToPreviousMenu();
                    return;
                case ContinueButtonId:
                    /* Don't continue if validation hasn't been done or has failed. */
                    if (!m_has_validated || R_FAILED(m_validation_info.result)) {
                        break;
                    }

                    /* Check if exfat is supported. */
                    g_exfat_supported = m_update_info.exfat_supported && R_SUCCEEDED(m_validation_info.exfat_result);
                    if (!g_exfat_supported) {
                        g_use_exfat = false;
                    }

                    /* Create the next menu. */
                    std::shared_ptr<Menu> next_menu = std::make_shared<ChooseResetMenu>(g_current_menu);

                    /* Warn the user if they're updating with exFAT supposed to be supported but not present/corrupted. */
                    if (m_update_info.exfat_supported && R_FAILED(m_validation_info.exfat_result)) {
                        next_menu = std::make_shared<WarningMenu>(g_current_menu, next_menu, "Warning: exFAT firmware is missing or corrupt", "Are you sure you want to proceed?");
                    }

                    /* Warn the user if they're updating to a version higher than supported. */
                    const u32 version = m_validation_info.invalid_key.version;
                    if (EncodeVersion((version >> 26) & 0x1f, (version >> 20) & 0x1f, (version >> 16) & 0xf) > g_supported_version) {
                        next_menu = std::make_shared<WarningMenu>(g_current_menu, next_menu, "Warning: firmware is too new and not known to be supported", "Are you sure you want to proceed?");
                    }

                    /* Change to the next menu. */
                    ChangeMenu(next_menu);
                    return;
            }
        }

        this->UpdateButtons();
    }

    void ValidateUpdateMenu::Draw(NVGcontext *vg, u64 ns) {
        const float x = g_screen_width / 2.0f - WindowWidth / 2.0f;
        const float y = g_screen_height / 2.0f - WindowHeight / 2.0f;

        DrawWindow(vg, "Update information", x, y, WindowWidth, WindowHeight);
        DrawTextBackground(vg, x + HorizontalInset, y + TitleGap, WindowWidth - HorizontalInset * 2.0f, TextAreaHeight);
        DrawTextBlock(vg, m_log_buffer, x + HorizontalInset + TextHorizontalInset, y + TitleGap + TextVerticalInset, WindowWidth - (HorizontalInset + TextHorizontalInset) * 2.0f, TextAreaHeight - TextVerticalInset * 2.0f);

        this->DrawButtons(vg, ns);
        m_has_drawn = true;
    }

    ChooseResetMenu::ChooseResetMenu(std::shared_ptr<Menu> prev_menu) : Menu(prev_menu) {
        const float x = g_screen_width / 2.0f - WindowWidth / 2.0f;
        const float y = g_screen_height / 2.0f - WindowHeight / 2.0f;
        const float button_width = (WindowWidth - HorizontalInset * 2.0f) / 2.0f - ButtonHorizontalGap;

        /* Add buttons. */
        this->AddButton(ResetToFactorySettingsButtonId, "Reset to factory settings", x + HorizontalInset, y + TitleGap, button_width, ButtonHeight);
        this->AddButton(PreserveSettingsButtonId, "Preserve settings", x + HorizontalInset + button_width + ButtonHorizontalGap, y + TitleGap, button_width, ButtonHeight);
        this->SetButtonSelected(PreserveSettingsButtonId, true);
    }

    void ChooseResetMenu::Update(u64 ns) {
        u64 k_down = padGetButtonsDown(&g_pad);

        /* Go back if B is pressed. */
        if (k_down & HidNpadButton_B) {
            ReturnToPreviousMenu();
            return;
        }

        /* Take action if a button has been activated. */
        if (const Button *activated_button = this->GetActivatedButton(); activated_button != nullptr) {
            switch (activated_button->id) {
                case ResetToFactorySettingsButtonId:
                    g_reset_to_factory = true;
                    break;
                case PreserveSettingsButtonId:
                    g_reset_to_factory = false;
                    break;
            }

            std::shared_ptr<Menu> next_menu;

            if (g_exfat_supported) {
                next_menu = std::make_shared<ChooseExfatMenu>(g_current_menu);
            } else {
                next_menu = std::make_shared<WarningMenu>(g_current_menu, std::make_shared<InstallUpdateMenu>(g_current_menu), "Ready to begin update installation", "Are you sure you want to proceed?");
            }

            if (g_reset_to_factory) {
                ChangeMenu(std::make_shared<WarningMenu>(g_current_menu, next_menu, "Warning: Factory reset selected", "Saves and installed games will be permanently deleted."));
            } else {
                ChangeMenu(next_menu);
            }
        }

        this->UpdateButtons();

        /* Fallback on selecting the exfat button. */
        if (const Button *selected_button = this->GetSelectedButton(); k_down && selected_button == nullptr) {
            this->SetButtonSelected(PreserveSettingsButtonId, true);
        }
    }

    void ChooseResetMenu::Draw(NVGcontext *vg, u64 ns) {
        const float x = g_screen_width / 2.0f - WindowWidth / 2.0f;
        const float y = g_screen_height / 2.0f - WindowHeight / 2.0f;

        DrawWindow(vg, "Select settings mode", x, y, WindowWidth, WindowHeight);
        this->DrawButtons(vg, ns);
    }

    ChooseExfatMenu::ChooseExfatMenu(std::shared_ptr<Menu> prev_menu) : Menu(prev_menu) {
        const float x = g_screen_width / 2.0f - WindowWidth / 2.0f;
        const float y = g_screen_height / 2.0f - WindowHeight / 2.0f;
        const float button_width = (WindowWidth - HorizontalInset * 2.0f) / 2.0f - ButtonHorizontalGap;

        /* Add buttons. */
        this->AddButton(Fat32ButtonId, "Install (FAT32)", x + HorizontalInset, y + TitleGap, button_width, ButtonHeight);
        this->AddButton(ExFatButtonId, "Install (FAT32 + exFAT)", x + HorizontalInset + button_width + ButtonHorizontalGap, y + TitleGap, button_width, ButtonHeight);

        /* Set the default selected button based on the user's current install. We aren't particularly concerned if fsIsExFatSupported fails. */
        bool exfat_supported = false;
        fsIsExFatSupported(&exfat_supported);

        if (exfat_supported) {
            this->SetButtonSelected(ExFatButtonId, true);
        } else {
            this->SetButtonSelected(Fat32ButtonId, true);
        }
    }

    void ChooseExfatMenu::Update(u64 ns) {
        u64 k_down = padGetButtonsDown(&g_pad);

        /* Go back if B is pressed. */
        if (k_down & HidNpadButton_B) {
            ReturnToPreviousMenu();
            return;
        }

        /* Take action if a button has been activated. */
        if (const Button *activated_button = this->GetActivatedButton(); activated_button != nullptr) {
            switch (activated_button->id) {
                case Fat32ButtonId:
                    g_use_exfat = false;
                    break;
                case ExFatButtonId:
                    g_use_exfat = true;
                    break;
            }

            ChangeMenu(std::make_shared<WarningMenu>(g_current_menu, std::make_shared<InstallUpdateMenu>(g_current_menu), "Ready to begin update installation", "Are you sure you want to proceed?"));
        }

        this->UpdateButtons();

        /* Fallback on selecting the exfat button. */
        if (const Button *selected_button = this->GetSelectedButton(); k_down && selected_button == nullptr) {
            this->SetButtonSelected(ExFatButtonId, true);
        }
    }

    void ChooseExfatMenu::Draw(NVGcontext *vg, u64 ns) {
        const float x = g_screen_width / 2.0f - WindowWidth / 2.0f;
        const float y = g_screen_height / 2.0f - WindowHeight / 2.0f;

        DrawWindow(vg, "Select driver variant", x, y, WindowWidth, WindowHeight);
        this->DrawButtons(vg, ns);
    }

    InstallUpdateMenu::InstallUpdateMenu(std::shared_ptr<Menu> prev_menu) : Menu(prev_menu), m_install_state(InstallState::NeedsDraw), m_progress_percent(0.0f) {
        const float x = g_screen_width / 2.0f - WindowWidth / 2.0f;
        const float y = g_screen_height / 2.0f - WindowHeight / 2.0f;
        const float button_width = (WindowWidth - HorizontalInset * 2.0f) / 2.0f - ButtonHorizontalGap;

        /* Add buttons. */
        this->AddButton(ShutdownButtonId, "Shutdown", x + HorizontalInset, y + WindowHeight - BottomInset - ButtonHeight, button_width, ButtonHeight);
        this->AddButton(RebootButtonId, "Reboot", x + HorizontalInset + button_width + ButtonHorizontalGap, y + WindowHeight - BottomInset - ButtonHeight, button_width, ButtonHeight);
        this->SetButtonEnabled(ShutdownButtonId, false);
        this->SetButtonEnabled(RebootButtonId, false);

        /* Prevent the home button from being pressed during installation. */
        hiddbgDeactivateHomeButton();
    }

    void InstallUpdateMenu::MarkForReboot() {
        this->SetButtonEnabled(ShutdownButtonId, true);
        this->SetButtonEnabled(RebootButtonId, true);
        this->SetButtonSelected(RebootButtonId, true);
        m_install_state = InstallState::AwaitingReboot;
    }

    Result InstallUpdateMenu::TransitionUpdateState() {
        Result rc = 0;
        if (m_install_state == InstallState::NeedsSetup) {
            /* Setup the update. */
            if (R_FAILED(rc = amssuSetupUpdate(nullptr, UpdateTaskBufferSize, g_update_path, g_use_exfat))) {
                this->LogText("Failed to setup update.\nResult: 0x%08x\n", rc);
                this->MarkForReboot();
                return rc;
            }

            /* Log setup completion. */
            this->LogText("Update setup complete.\n");
            m_install_state = InstallState::NeedsPrepare;
        } else if (m_install_state == InstallState::NeedsPrepare) {
            /* Request update preparation. */
            if (R_FAILED(rc = amssuRequestPrepareUpdate(&m_prepare_result))) {
                this->LogText("Failed to request update preparation.\nResult: 0x%08x\n", rc);
                this->MarkForReboot();
                return rc;
            }

            /* Log awaiting prepare. */
            this->LogText("Preparing update...\n");
            m_install_state = InstallState::AwaitingPrepare;
        } else if (m_install_state == InstallState::AwaitingPrepare) {
            /* Check if preparation has a result. */
            if (R_FAILED(rc = asyncResultWait(&m_prepare_result, 0)) && rc != 0xea01) {
                this->LogText("Failed to check update preparation result.\nResult: 0x%08x\n", rc);
                this->MarkForReboot();
                return rc;
            } else if (R_SUCCEEDED(rc)) {
                if (R_FAILED(rc = asyncResultGet(&m_prepare_result))) {
                    this->LogText("Failed to prepare update.\nResult: 0x%08x\n", rc);
                    this->MarkForReboot();
                    return rc;
                }
            }

            /* Check if the update has been prepared. */
            bool prepared;
            if (R_FAILED(rc = amssuHasPreparedUpdate(&prepared))) {
                this->LogText("Failed to check if update has been prepared.\nResult: 0x%08x\n", rc);
                this->MarkForReboot();
                return rc;
            }

            /* Mark for application if preparation complete. */
            if (prepared) {
                this->LogText("Update preparation complete.\nApplying update...\n");
                m_install_state = InstallState::NeedsApply;
                return rc;
            }

            /* Check update progress. */
            NsSystemUpdateProgress update_progress = {};
            if (R_FAILED(rc = amssuGetPrepareUpdateProgress(&update_progress))) {
                this->LogText("Failed to check update progress.\nResult: 0x%08x\n", rc);
                this->MarkForReboot();
                return rc;
            }

            /* Update progress percent. */
            if (update_progress.total_size > 0.0f) {
                m_progress_percent = static_cast<float>(update_progress.current_size) / static_cast<float>(update_progress.total_size);
            } else {
                m_progress_percent = 0.0f;
            }
        } else if (m_install_state == InstallState::NeedsApply) {
            /* Apply the prepared update. */
            if (R_FAILED(rc = amssuApplyPreparedUpdate())) {
                this->LogText("Failed to apply update.\nResult: 0x%08x\n", rc);
            } else {
                /* Log success. */
                this->LogText("Update applied successfully.\n");

                if (g_reset_to_factory) {
                    if (R_FAILED(rc = nsResetToFactorySettingsForRefurbishment())) {
                        /* Fallback on ResetToFactorySettings. */
                        if (rc == MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer)) {
                            if (R_FAILED(rc = nsResetToFactorySettings())) {
                                this->LogText("Failed to reset to factory settings.\nResult: 0x%08x\n", rc);
                                this->MarkForReboot();
                                return rc;
                            }
                        } else {
                            this->LogText("Failed to reset to factory settings for refurbishment.\nResult: 0x%08x\n", rc);
                            this->MarkForReboot();
                            return rc;
                        }
                    }

                    this->LogText("Successfully reset to factory settings.\n", rc);
                }
            }

            this->MarkForReboot();
            return rc;
        }

        return rc;
    }

    void InstallUpdateMenu::Update(u64 ns) {
        /* Transition to the next update state. */
        if (m_install_state != InstallState::NeedsDraw && m_install_state != InstallState::AwaitingReboot) {
            this->TransitionUpdateState();
        }

        /* Take action if a button has been activated. */
        if (const Button *activated_button = this->GetActivatedButton(); activated_button != nullptr) {
            switch (activated_button->id) {
                case ShutdownButtonId:
                    if (R_FAILED(appletRequestToShutdown())) {
                        spsmShutdown(false);
                    }
                    break;
                case RebootButtonId:
                    if (R_FAILED(appletRequestToReboot())) {
                        spsmShutdown(true);
                    }
                    break;
            }
        }

        this->UpdateButtons();
    }

    void InstallUpdateMenu::Draw(NVGcontext *vg, u64 ns) {
        const float x = g_screen_width / 2.0f - WindowWidth / 2.0f;
        const float y = g_screen_height / 2.0f - WindowHeight / 2.0f;

        DrawWindow(vg, "Installing update", x, y, WindowWidth, WindowHeight);
        DrawProgressText(vg, x + HorizontalInset, y + TitleGap, m_progress_percent);
        DrawProgressBar(vg, x + HorizontalInset, y + TitleGap + ProgressTextHeight, WindowWidth - HorizontalInset * 2.0f, ProgressBarHeight, m_progress_percent);
        DrawTextBackground(vg, x + HorizontalInset, y + TitleGap + ProgressTextHeight + ProgressBarHeight + VerticalGap, WindowWidth - HorizontalInset * 2.0f, TextAreaHeight);
        DrawTextBlock(vg, m_log_buffer, x + HorizontalInset + TextHorizontalInset, y + TitleGap + ProgressTextHeight + ProgressBarHeight + VerticalGap + TextVerticalInset, WindowWidth - (HorizontalInset + TextHorizontalInset) * 2.0f, TextAreaHeight - TextVerticalInset * 2.0f);

        this->DrawButtons(vg, ns);

        /* We have drawn now, allow setup to occur. */
        if (m_install_state == InstallState::NeedsDraw) {
            this->LogText("Beginning update setup...\n");
            m_install_state = InstallState::NeedsSetup;
        }
    }

    bool InitializeMenu(u32 screen_width, u32 screen_height) {
        Result rc = 0;

        /* Configure and initialize the gamepad. */
        padConfigureInput(1, HidNpadStyleSet_NpadStandard);
        padInitializeDefault(&g_pad);

        /* Initialize the touch screen. */
        hidInitializeTouchScreen();

        /* Set the screen width and height. */
        g_screen_width = screen_width;
        g_screen_height = screen_height;

        /* Mark as initialized. */
        g_initialized = true;

        /* Attempt to get the exosphere version. */
        u64 version;
        if (R_FAILED(rc = splGetConfig(static_cast<SplConfigItem>(ExosphereApiVersionConfigItem), &version))) {
            ChangeMenu(std::make_shared<ErrorMenu>("Atmosphere not found", "Daybreak requires Atmosphere to be installed.", rc));
            return false;
        }

        const u32 version_micro = (version >> 40) & 0xff;
        const u32 version_minor = (version >> 48) & 0xff;
        const u32 version_major = (version >> 56) & 0xff;

        /* Validate the exosphere version. */
        const bool ams_supports_sysupdate_api = EncodeVersion(version_major, version_minor, version_micro) >= EncodeVersion(0, 14, 0);
        if (!ams_supports_sysupdate_api) {
            ChangeMenu(std::make_shared<ErrorMenu>("Outdated Atmosphere version", "Daybreak requires Atmosphere 0.14.0 or later.", rc));
            return false;
        }

        /* Ensure DayBreak is ran as a NRO. */
        if (envIsNso()) {
            ChangeMenu(std::make_shared<ErrorMenu>("Unsupported Environment", "Please launch Daybreak via the Homebrew menu.", rc));
            return false;
        }

        /* Attempt to get the supported version. */
        if (R_SUCCEEDED(rc = splGetConfig(static_cast<SplConfigItem>(ExosphereSupportedHosVersion), &version))) {
            g_supported_version = static_cast<u32>(version);
        }

        /* Initialize ams:su. */
        if (R_FAILED(rc = amssuInitialize())) {
            fatalThrow(rc);
        }

        /* Change the current menu to the main menu. */
        g_current_menu = std::make_shared<MainMenu>();

        return true;
    }

    bool InitializeMenu(u32 screen_width, u32 screen_height, const char *update_path) {
        if (InitializeMenu(screen_width, screen_height)) {

            /* Set the update path. */
            strncpy(g_update_path, update_path, sizeof(g_update_path));

            /* Change the menu. */
            ChangeMenu(std::make_shared<ValidateUpdateMenu>(g_current_menu));

            return true;
        }

        return false;
    }

    void UpdateMenu(u64 ns) {
        DBK_ABORT_UNLESS(g_initialized);
        DBK_ABORT_UNLESS(g_current_menu != nullptr);
        UpdateInput();
        g_current_menu->Update(ns);
    }

    void RenderMenu(NVGcontext *vg, u64 ns) {
        DBK_ABORT_UNLESS(g_initialized);
        DBK_ABORT_UNLESS(g_current_menu != nullptr);

        /* Draw background. */
        DrawBackground(vg, g_screen_width, g_screen_height);

        /* Draw stars. */
        DrawStar(vg, 40.0f, 64.0f, 3.0f);
        DrawStar(vg, 110.0f, 300.0f, 3.0f);
        DrawStar(vg, 200.0f, 150.0f, 4.0f);
        DrawStar(vg, 370.0f, 280.0f, 3.0f);
        DrawStar(vg, 450.0f, 40.0f, 3.5f);
        DrawStar(vg, 710.0f, 90.0f, 3.0f);
        DrawStar(vg, 900.0f, 240.0f, 3.0f);
        DrawStar(vg, 970.0f, 64.0f, 4.0f);
        DrawStar(vg, 1160.0f, 160.0f, 3.5f);
        DrawStar(vg, 1210.0f, 350.0f, 3.0f);

        g_current_menu->Draw(vg, ns);
    }

    bool IsExitRequested() {
        return g_exit_requested;
    }

}