/**
 * PROJECT:         ExectOS
 * COPYRIGHT:       See COPYING.md in the top level directory
 * FILE:            xtldr/textui.cc
 * DESCRIPTION:     Text console User Interface (TUI) support for XT Boot Loader
 * DEVELOPERS:      Rafal Kupiec <belliash@codingworkshop.eu.org>
 *                  Aiken Harris <harraiken91@gmail.com>
 */

#include <xtldr.hh>


/**
 * Determines dialog box size based on enabled components and message length.
 *
 * @param Handle
 *        Supplies a pointer to the dialog box handle.
 *
 * @param Message
 *        Supplies a pointer to the message string put on the dialog box.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTCDECL
VOID
TextUi::DetermineDialogBoxSize(IN OUT PXTBL_DIALOG_HANDLE Handle,
                               IN PCWSTR Message)
{
    UINT_PTR Width, Height, LineLength;
    SIZE_T Index, MessageLength;
    UCHAR Attributes;
    ULONG Mask;

    /* Set minimum dialog window size */
    Height = 4;
    Width = 36;

    /* Zero line length */
    LineLength = 0;

    /* Adjust window height according to enabled components */
    Mask = 1;
    Attributes = Handle->Attributes;
    while(Mask)
    {
        /* Check enabled components that affect dialog window size */
        switch(Attributes & Mask)
        {
            case XTBL_TUI_DIALOG_ACTIVE_BUTTON:
            case XTBL_TUI_DIALOG_INACTIVE_BUTTON:
                Height += 1;
                break;
            case XTBL_TUI_DIALOG_ACTIVE_INPUT:
            case XTBL_TUI_DIALOG_INACTIVE_INPUT:
            case XTBL_TUI_DIALOG_PROGRESS_BAR:
                Height += 2;
                break;
        }

        /* Update component attributes mask */
        Attributes &= ~Mask;
        Mask <<= 1;
    }

    /* Check if input field is active */
    if(Handle->Attributes & (XTBL_TUI_DIALOG_ACTIVE_INPUT | XTBL_TUI_DIALOG_INACTIVE_INPUT))
    {
        /* Set maximum dialog window width to fit input field */
        Width = XTBL_TUI_MAX_DIALOG_WIDTH;
    }

    /* Get message length and count dialog window dimensions */
    MessageLength = RTL::WideString::WideStringLength(Message, 0);
    for(Index = 0; Index < MessageLength; Index++)
    {
        /* Check if this is multiline message */
        if(Message[Index] == L'\n' || Index == MessageLength - 1)
        {
            /* Check if this line exceeds current dialog window width */
            if(LineLength > Width)
            {
                /* Update dialog window width */
                Width = LineLength;
            }

            /* Increase dialog window height to fit next line */
            Height++;
            LineLength = 0;
        }
        else
        {
            /* Increase dialog window width to fit next character */
            LineLength++;
        }
    }

    /* Add more space to dialog window to fit side borders */
    Width += 4;

    /* Get console resolution */
    Console::QueryMode(&Handle->ResX, &Handle->ResY);

    /* Make sure dialog window fits in the buffer */
    if(Width > XTBL_TUI_MAX_DIALOG_WIDTH)
    {
        /* Set maximum dialog window width */
        Width = XTBL_TUI_MAX_DIALOG_WIDTH;
    }

    /* Make sure dialog window fits on the screen (X axis) and it is not too small for input field */
    if(Width > (Handle->ResX - 2))
    {
        /* Set maximum dialog window width */
        Width = Handle->ResX - 2;
    }

    /* Make sure dialog window fits on the screen (Y axis)*/
    if(Height > (Handle->ResY - 2))
    {
        /* Set maximum dialog window height */
        Height = Handle->ResY - 2;
    }

    /* Set dialog window final dimensions */
    Handle->PosX = (Handle->ResX - Width) / 2;
    Handle->PosY = (Handle->ResY - Height) / 2;
    Handle->Width = Width;
    Handle->Height = Height;
}

/**
 * Displays a simple TUI-based boot menu.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTCDECL
VOID
TextUi::DisplayBootMenu()
{
    XTBL_DIALOG_HANDLE Handle;
    PXTBL_BOOTMENU_ITEM MenuEntries = NULLPTR;
    ULONG Index;
    ULONG HighligtedEntryId, OldHighligtedEntryId, NumberOfEntries, TopVisibleEntry, VisibleEntries;
    BOOLEAN RedrawBootMenu, RedrawEntries;
    UINT_PTR EventIndex;
    EFI_EVENT Events[2];
    EFI_INPUT_KEY Key;
    EFI_EVENT TimerEvent;
    EFI_STATUS Status;
    LONG TimeOut;
    PWCHAR TimeOutString;

    /* Draw boot menu */
    DrawBootMenu(&Handle);

    /* Initialize boot menu list */
    TopVisibleEntry = 0;
    Status = Configuration::InitializeBootMenuList(Handle.Width - 4, &MenuEntries, &NumberOfEntries, &HighligtedEntryId);
    if(Status != STATUS_EFI_SUCCESS)
    {
        /* Failed to initialize boot menu list, exit into XTLDR shell */
        return;
    }

    /* Calculate how many entries can be visible in the menu box */
    VisibleEntries = Handle.Height - 2;

    /* Adjust the view if the default entry is not initially visible */
    if(HighligtedEntryId >= VisibleEntries)
    {
        /* Scroll the view to make the highlighted entry the last visible one */
        TopVisibleEntry = HighligtedEntryId - VisibleEntries + 1;
    }

    /* Get timeout from the configuration */
    Configuration::GetValue(L"TIMEOUT", &TimeOutString);
    TimeOut = -1;

    /* Check if timeout is specified */
    if(TimeOutString != NULLPTR)
    {
        /* Convert timeout string to number */
        TimeOut = 0;
        while(*TimeOutString >= '0' && *TimeOutString <= '9')
        {
            TimeOut *= 10;
            TimeOut += *TimeOutString - '0';
            TimeOutString++;
        }
    }

    /* Set redraw flags to not redraw the menu itself, but fill it with entries */
    RedrawBootMenu = FALSE;
    RedrawEntries = TRUE;

    /* Infinite boot menu loop */
    while(TRUE)
    {
        /* Redraw boot menu frame if requested */
        if(RedrawBootMenu)
        {
            DrawBootMenu(&Handle);
            RedrawBootMenu = FALSE;
            RedrawEntries = TRUE;
        }

        /* Sanity check to ensure we do not display more entries than possible */
        if(VisibleEntries > NumberOfEntries)
        {
            VisibleEntries = NumberOfEntries;
        }

        /* Check if there is anything to show in the boot menu */
        if(NumberOfEntries > 0)
        {
            /* Check if we need to redraw boot menu entries */
            if(RedrawEntries)
            {
                /* Iterate through all menu entries */
                for(Index = 0; Index < VisibleEntries; Index++)
                {
                    /* Draw menu entry */
                    DrawBootMenuEntry(&Handle, MenuEntries[TopVisibleEntry + Index].EntryName,
                                      Index, (BOOLEAN)((TopVisibleEntry + Index) == HighligtedEntryId));
                }

                /* Clear redraw entries flag */
                RedrawEntries = FALSE;
            }
        }
        else
        {
            /* No menu entries found, show error message */
            DisplayErrorDialog(L"XTLDR", L"No boot menu entries found in the configuration. Falling back to shell.");

            /* Exit into XTLDR shell */
            return;
        }

        /* Create a timer event for controlling the timeout of the boot menu */
        Status = XtLoader::GetEfiSystemTable()->BootServices->CreateEvent(EFI_EVENT_TIMER, EFI_TPL_CALLBACK,
                                                                          NULLPTR, NULLPTR, &TimerEvent);
        if(Status == STATUS_EFI_SUCCESS)
        {
            /* Setup new EFI timer */
            Status = XtLoader::GetEfiSystemTable()->BootServices->SetTimer(TimerEvent, TimerPeriodic, 10000000);
        }

        /* Check is EFI timer was successfully created */
        if(Status != STATUS_EFI_SUCCESS)
        {
            /* Timer creation failed, disable the timer */
            TimeOut = -1;
        }

        /* Initialize EFI events */
        Events[0] = XtLoader::GetEfiSystemTable()->ConIn->WaitForKey;
        Events[1] = TimerEvent;

        /* Flush keyboard buffer out of any keystrokes */
        XtLoader::GetEfiSystemTable()->ConIn->Reset(XtLoader::GetEfiSystemTable()->ConIn, FALSE);

        /* Store old highlighted entry */
        OldHighligtedEntryId = HighligtedEntryId;

        /* Infinite boot menu event loop */
        while(TRUE)
        {
            /* Wait for EFI event */
            EfiUtils::WaitForEfiEvent(2, Events, &EventIndex);

            /* Check which event was received */
            if(EventIndex == 0)
            {
                /* Key pressed, check if timer is still active */
                if(TimeOut != -1)
                {
                    /* Disable the timer */
                    TimeOut = -1;

                    /* Cancel timer event */
                    XtLoader::GetEfiSystemTable()->BootServices->SetTimer(TimerEvent, TimerCancel, 0);

                    /* Remove the timer message */
                    Console::ClearLine(Handle.PosY + Handle.Height + 4);
                }

                /* Read key stroke */
                Console::ReadKeyStroke(&Key);

                if(Key.ScanCode == 0x03 || Key.UnicodeChar == 0x0D)
                {
                    /* ENTER or RightArrow key pressed, boot the highlighted OS */
                    Console::SetAttributes(Handle.DialogColor | Handle.TextColor);
                    Console::ClearLine(Handle.PosY + Handle.Height + 4);
                    Console::SetCursorPosition(4, Handle.PosY + Handle.Height + 4);
                    Console::Print(L"Booting '%S' now...\n", MenuEntries[HighligtedEntryId].FullName);

                    /* Boot the highlighted (chosen) OS */
                    Status = Protocol::InvokeBootProtocol(MenuEntries[HighligtedEntryId].ShortName,
                                                          MenuEntries[HighligtedEntryId].Options);
                    if(Status != STATUS_SUCCESS)
                    {
                        /* Failed to boot OS */
                        Debug::Print(L"ERROR: Failed to boot '%S' (Status Code: 0x%zX)\n",
                                     MenuEntries[HighligtedEntryId].FullName, Status);
                        DisplayErrorDialog(L"XTLDR", L"Failed to startup the selected Operating System.");
                        RedrawBootMenu = TRUE;
                    }

                    /* Break from boot menu event loop to redraw whole boot menu */
                    break;
                }
                else if(Key.ScanCode == 0x01)
                {
                    /* UpArrow key pressed, go to previous entry if possible */
                    if(HighligtedEntryId > 0)
                    {
                        /* Highlight previous entry */
                        OldHighligtedEntryId = HighligtedEntryId;
                        HighligtedEntryId--;

                        /* Check if we need to scroll the view */
                        if(HighligtedEntryId < TopVisibleEntry)
                        {
                            /* Scroll the view */
                            TopVisibleEntry = HighligtedEntryId;
                            RedrawEntries = TRUE;
                            break;
                        }

                        /* Redraw new highlighted entry and the old one */
                        DrawBootMenuEntry(&Handle, MenuEntries[OldHighligtedEntryId].EntryName,
                                          OldHighligtedEntryId - TopVisibleEntry, (BOOLEAN)FALSE);
                        DrawBootMenuEntry(&Handle, MenuEntries[HighligtedEntryId].EntryName,
                                          HighligtedEntryId - TopVisibleEntry, (BOOLEAN)TRUE);
                    }
                }
                else if(Key.ScanCode == 0x02)
                {
                    /* DownArrow key pressed, go to next entry if possible */
                    if(HighligtedEntryId < NumberOfEntries - 1)
                    {
                        /* Highlight next entry */
                        OldHighligtedEntryId = HighligtedEntryId;
                        HighligtedEntryId++;

                        /* Check if we need to scroll the view */
                        if(HighligtedEntryId >= TopVisibleEntry + VisibleEntries)
                        {
                            /* Scroll the view */
                            TopVisibleEntry = HighligtedEntryId - VisibleEntries + 1;
                            RedrawEntries = TRUE;
                            break;
                        }

                        /* Redraw new highlighted entry and the old one */
                        DrawBootMenuEntry(&Handle, MenuEntries[OldHighligtedEntryId].EntryName,
                                          OldHighligtedEntryId - TopVisibleEntry, (BOOLEAN)FALSE);
                        DrawBootMenuEntry(&Handle, MenuEntries[HighligtedEntryId].EntryName,
                                          HighligtedEntryId - TopVisibleEntry, (BOOLEAN)TRUE);
                    }
                }
                else if(Key.ScanCode == 0x09)
                {
                    /* PageUp key pressed, go to top entry */
                    if(HighligtedEntryId != 0)
                    {
                        /* Highlight first entry */
                        HighligtedEntryId = 0;
                        TopVisibleEntry = 0;
                        RedrawEntries = TRUE;
                        break;
                    }
                }
                else if(Key.ScanCode == 0x0A)
                {
                    /* PageDown key pressed, go to bottom entry */
                    if(HighligtedEntryId != NumberOfEntries - 1)
                    {
                        /* Highlight last entry */
                        HighligtedEntryId = NumberOfEntries - 1;
                        TopVisibleEntry = (NumberOfEntries > VisibleEntries) ? (NumberOfEntries - VisibleEntries) : 0;
                        RedrawEntries = TRUE;
                        break;
                    }
                }
                else if(Key.ScanCode == 0x0B)
                {
                    /* F1 key pressed, show help */
                    DisplayInfoDialog(L"XTLDR", L"XTLDR, the XTOS Boot Loader for UEFI and EFI-based machines.\n"
                                                L" \n"
                                                L"Use arrow keys (Up/Down) to change the highlighted entry and\n"
                                                L"PgUp/PgDown keys to jump to the first/last position.\n"
                                                L" \n"
                                                L"Press ENTER key to boot the highlighted boot menu entry.\n"
                                                L"Press 'e' key to edit the highlighted menu entry.\n"
                                                L"Press 's' key to exit into XTLDR shell (enters advanced mode).\n"
                                                L" \n"
                                                L"F1 shows this help, F10 reboots into UEFI firmware interface,\n"
                                                L"F11 reboots the machine and F12 turns it off.\n"
                                                L" \n"
                                                L" \n"
                                                L"XTLDR is a part of the ExectOS Operating System.\n"
                                                L"Visit https://exectos.eu.org/ for more information.");

                    /* Break from boot menu event loop to redraw whole boot menu */
                    RedrawBootMenu = TRUE;
                    break;
                }
                else if(Key.ScanCode == 0x14)
                {
                    /* F10 key pressed, reboot into UEFI setup interface */
                    EfiUtils::EnterFirmwareSetup();
                    DisplayErrorDialog(L"XTLDR", L"Reboot into firmware setup interface not supported!");
                    RedrawBootMenu = TRUE;

                    /* Break from boot menu event loop to redraw whole boot menu */
                    break;
                }
                else if(Key.ScanCode == 0x15)
                {
                    /* F11 key pressed, reboot the machine */
                    EfiUtils::RebootSystem();
                    DisplayErrorDialog(L"XTLDR", L"Failed to reboot the machine!");
                    RedrawBootMenu = TRUE;

                    /* Break from boot menu event loop to redraw whole boot menu */
                    break;
                }
                else if(Key.ScanCode == 0x16)
                {
                    /* F12 key pressed, shutdown the machine */
                    EfiUtils::ShutdownSystem();
                    DisplayErrorDialog(L"XTLDR", L"Failed to shutdown the machine!");
                    RedrawBootMenu = TRUE;

                    /* Break from boot menu event loop to redraw whole boot menu */
                    break;
                }
                else if(Key.UnicodeChar == 0x65)
                {
                    /* 'e' key pressed, edit the highlighted entry */
                    DisplayEditMenu(&MenuEntries[HighligtedEntryId]);
                    RedrawBootMenu = TRUE;

                    /* Break from boot menu event loop to redraw whole boot menu */
                    break;
                }
                else if(Key.UnicodeChar == 0x73)
                {
                    /* 's' key pressed, exit into XTLDR shell */
                    return;
                }
            }
            else
            {
                /* Timer tick, check if time out expired */
                if(TimeOut > 0)
                {
                    /* Update a message and decrease timeout value */
                    Console::SetAttributes(Handle.DialogColor | Handle.TextColor);
                    Console::ClearLine(Handle.PosY + Handle.Height + 4);
                    Console::SetCursorPosition(4, Handle.PosY + Handle.Height + 4);
                    Console::Print(L"The highlighted position will be booted automatically in %ld seconds.", TimeOut);
                    TimeOut--;
                }
                else if(TimeOut == 0)
                {
                    /* Time out expired, update a message */
                    Console::SetAttributes(Handle.DialogColor | Handle.TextColor);
                    Console::ClearLine(Handle.PosY + Handle.Height + 4);
                    Console::SetCursorPosition(4, Handle.PosY + Handle.Height + 4);
                    Console::Print(L"Booting '%S' now...\n", MenuEntries[HighligtedEntryId].FullName);

                    /* Disable the timer just in case booting OS fails */
                    TimeOut = -1;

                    /* Boot the highlighted (default) OS */
                    Status = Protocol::InvokeBootProtocol(MenuEntries[HighligtedEntryId].ShortName,
                                                          MenuEntries[HighligtedEntryId].Options);
                    if(Status != STATUS_SUCCESS)
                    {
                        /* Failed to boot OS */
                        Debug::Print(L"ERROR: Failed to boot '%S' (Status Code: 0x%zX)\n",
                                     MenuEntries[HighligtedEntryId].FullName, Status);
                        DisplayErrorDialog(L"XTLDR", L"Failed to startup the selected Operating System.");
                        RedrawBootMenu = TRUE;
                    }
                    break;
                }
            }
        }
    }
}

/**
 * Displays a simple TUI-based edit menu.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTCDECL
VOID
TextUi::DisplayEditMenu(IN PXTBL_BOOTMENU_ITEM MenuEntry)
{
    ULONG HighligtedOptionId, Index, NumberOfOptions, OldHighligtedOptionId, TopVisibleEntry, VisibleEntries;
    XTBL_DIALOG_HANDLE Handle;
    BOOLEAN RedrawEditMenu, RedrawEntries;
    EFI_INPUT_KEY Key;
    UINT_PTR EventIndex;
    PWCHAR NewValue, OriginalValue, Value, ValueToEdit;
    PCWSTR OptionName, *EditableOptions;
    EFI_STATUS Status;

    /* Draw edit menu */
    DrawEditMenu(&Handle);

    /* Get the list of user editable options */
    Configuration::GetEditableOptions(&EditableOptions, &NumberOfOptions);

    /* Calculate how many entries can be visible in the menu box */
    VisibleEntries = Handle.Height - 2;

    /* Assume the first option is highlighted by default */
    HighligtedOptionId = 0;
    OldHighligtedOptionId = 0;
    TopVisibleEntry = 0;

    /* Set redraw flags to not redraw the menu itself, but fill it with entries */
    RedrawEditMenu = FALSE;
    RedrawEntries = TRUE;

    /* Infinite edit menu loop */
    while(TRUE)
    {
        /* Redraw edit menu frame if requested */
        if(RedrawEditMenu)
        {
            DrawEditMenu(&Handle);
            RedrawEditMenu = FALSE;
            RedrawEntries = TRUE;
        }

        /* Sanity check to ensure we do not display more entries than possible */
        if(VisibleEntries > NumberOfOptions)
        {
            VisibleEntries = NumberOfOptions;
        }

        /* Check if we need to redraw boot menu entries */
        if(RedrawEntries)
        {
            /* Iterate through all menu entries */
            for(Index = 0; Index < VisibleEntries; Index++)
            {
                /* Draw menu entry */
                Configuration::GetBootOptionValue(MenuEntry->Options, EditableOptions[TopVisibleEntry + Index], &Value);
                DrawEditMenuEntry(&Handle, EditableOptions[TopVisibleEntry + Index], Value, Index,
                                  (BOOLEAN)((TopVisibleEntry + Index) == HighligtedOptionId));

                /* Free allocated value string if needed */
                if(Value != NULLPTR)
                {
                    Memory::FreePool(Value);
                }
            }

            /* Clear redraw entries flag */
            RedrawEntries = FALSE;
        }

        /* Wait for EFI event and read key stroke */
        EfiUtils::WaitForEfiEvent(1, &(XtLoader::GetEfiSystemTable()->ConIn->WaitForKey), &EventIndex);
        Console::ReadKeyStroke(&Key);

        /* Check key press scan code */
        if(Key.UnicodeChar == 0x0D || Key.UnicodeChar == 0x65)
        {
            /* ENTER or 'e' key pressed, edit the highlighted option */
            OptionName = EditableOptions[HighligtedOptionId];
            Configuration::GetBootOptionValue(MenuEntry->Options, OptionName, &OriginalValue);

            /* If the original value is NULLPTR, use an empty string for editing */
            if(OriginalValue == NULLPTR)
            {
                ValueToEdit = (PWCHAR)L"";
            }
            else
            {
                ValueToEdit = OriginalValue;
            }

            /* Display input dialog to edit the option value */
            NewValue = ValueToEdit;
            DisplayInputDialog(OptionName, L"Enter new value:", &NewValue);

            /* Check if the value was changed */
            if(NewValue != ValueToEdit)
            {
                /* Update the boot option with the new value and free the old value */
                Configuration::SetBootOptionValue(MenuEntry->Options, OptionName, NewValue);
                Memory::FreePool(NewValue);
            }

            /* Free the original value if it was allocated */
            if(OriginalValue != NULLPTR)
            {
                Memory::FreePool(OriginalValue);
            }

            /* Mark the edit menu for redraw */
            RedrawEditMenu = TRUE;
        }
        else if(Key.ScanCode == 0x01)
        {
            /* UpArrow key pressed, go to previous entry if possible */
            if(HighligtedOptionId > 0)
            {
                /* Highlight previous entry */
                OldHighligtedOptionId = HighligtedOptionId;
                HighligtedOptionId--;

                /* Check if we need to scroll the view */
                if(HighligtedOptionId < TopVisibleEntry)
                {
                    /* Scroll the view */
                    TopVisibleEntry = HighligtedOptionId;
                    RedrawEntries = TRUE;
                    continue;
                }

                /* Redraw old highlighted entry */
                Configuration::GetBootOptionValue(MenuEntry->Options, EditableOptions[OldHighligtedOptionId], &Value);
                DrawEditMenuEntry(&Handle, EditableOptions[OldHighligtedOptionId], Value, OldHighligtedOptionId - TopVisibleEntry, (BOOLEAN)FALSE);

                /* Free allocated value string if needed */
                if(Value != NULLPTR)
                {
                    Memory::FreePool(Value);
                }

                /* Redraw new highlighted entry */
                Configuration::GetBootOptionValue(MenuEntry->Options, EditableOptions[HighligtedOptionId], &Value);
                DrawEditMenuEntry(&Handle, EditableOptions[HighligtedOptionId], Value, HighligtedOptionId - TopVisibleEntry, (BOOLEAN)TRUE);

                /* Free allocated value string if needed */
                if(Value != NULLPTR)
                {
                    Memory::FreePool(Value);
                }
            }
        }
        else if(Key.ScanCode == 0x02)
        {
            /* DownArrow key pressed, go to next entry if possible */
            if(HighligtedOptionId < NumberOfOptions - 1)
            {
                /* Highlight next entry */
                OldHighligtedOptionId = HighligtedOptionId;
                HighligtedOptionId++;

                /* Check if we need to scroll the view */
                if(HighligtedOptionId >= TopVisibleEntry + VisibleEntries)
                {
                    /* Scroll the view */
                    TopVisibleEntry = HighligtedOptionId - VisibleEntries + 1;
                    RedrawEntries = TRUE;
                    continue;
                }

                /* Redraw old highlighted entry */
                Configuration::GetBootOptionValue(MenuEntry->Options, EditableOptions[OldHighligtedOptionId], &Value);
                DrawEditMenuEntry(&Handle, EditableOptions[OldHighligtedOptionId], Value, OldHighligtedOptionId - TopVisibleEntry, (BOOLEAN)FALSE);

                /* Free allocated value string if needed */
                if(Value != NULLPTR)
                {
                    Memory::FreePool(Value);
                }

                /* Redraw new highlighted entry */
                Configuration::GetBootOptionValue(MenuEntry->Options, EditableOptions[HighligtedOptionId], &Value);
                DrawEditMenuEntry(&Handle, EditableOptions[HighligtedOptionId], Value, HighligtedOptionId - TopVisibleEntry, (BOOLEAN)TRUE);

                /* Free allocated value string if needed */
                if(Value != NULLPTR)
                {
                    Memory::FreePool(Value);
                }
            }
        }
        else if(Key.ScanCode == 0x09)
        {
            /* PageUp key pressed, go to top entry */
            if(HighligtedOptionId != 0)
            {
                /* Highlight first entry */
                HighligtedOptionId = 0;
                TopVisibleEntry = 0;
                RedrawEntries = TRUE;
            }
        }
        else if(Key.ScanCode == 0x0A)
        {
            /* PageDown key pressed, go to bottom entry */
            if(HighligtedOptionId != NumberOfOptions - 1)
            {
                /* Highlight last entry */
                HighligtedOptionId = NumberOfOptions - 1;
                TopVisibleEntry = (NumberOfOptions > VisibleEntries) ? (NumberOfOptions - VisibleEntries) : 0;
                RedrawEntries = TRUE;
            }
        }
        else if(Key.ScanCode == 0x14)
        {
            /* F10 key pressed, boot the OS */
            Console::SetAttributes(Handle.DialogColor | Handle.TextColor);
            Console::ClearLine(Handle.PosY + Handle.Height + 4);
            Console::SetCursorPosition(4, Handle.PosY + Handle.Height + 4);
            Console::Print(L"Booting '%S' now...\n", MenuEntry->FullName);

            /* Boot the OS */
            Status = Protocol::InvokeBootProtocol(MenuEntry->ShortName, MenuEntry->Options);
            if(Status != STATUS_SUCCESS)
            {
                /* Failed to boot OS */
                Debug::Print(L"ERROR: Failed to boot '%S' (Status Code: 0x%zX)\n", MenuEntry->FullName, Status);
                DisplayErrorDialog(L"XTLDR", L"Failed to startup the selected Operating System.");
                RedrawEditMenu = TRUE;
            }

            /* Return to the edit menu */
            continue;
        }
        else if(Key.ScanCode == 0x17)
        {
            /* ESC key pressed, exit edit menu */
            break;
        }
    }
}

/**
 * Displays a red error dialog box with the specified caption and message.
 *
 * @param Caption
 *        Supplies a caption string put on the dialog box.
 *
 * @param Message
 *        Supplies a message string put on the dialog box.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTCDECL
VOID
TextUi::DisplayErrorDialog(IN PCWSTR Caption,
                           IN PCWSTR Message)
{
    XTBL_DIALOG_HANDLE Handle;
    EFI_INPUT_KEY Key;
    UINT_PTR Index;

    /* Set dialog window attributes */
    Handle.Attributes = XTBL_TUI_DIALOG_ERROR_BOX | XTBL_TUI_DIALOG_ACTIVE_BUTTON;

    /* Determine dialog window size and position */
    DetermineDialogBoxSize(&Handle, Message);

    /* Disable cursor and draw dialog box */
    Console::DisableCursor();
    DrawDialogBox(&Handle, Caption, Message);

    /* Draw active button */
    DrawButton(&Handle);

    /* Initialize key stroke */
    Key.ScanCode = 0;
    Key.UnicodeChar = 0;

    /* Wait until ENTER or ESC key is pressed */
    while(Key.ScanCode != 0x17 && Key.UnicodeChar != 0x0D)
    {
        /* Wait for key press and read key stroke */
        EfiUtils::WaitForEfiEvent(1, &(XtLoader::GetEfiSystemTable()->ConIn->WaitForKey), &Index);
        Console::ReadKeyStroke(&Key);
        Console::ResetInputBuffer();
    }

    /* Clear screen to remove dialog box */
    Console::SetAttributes(EFI_TEXT_BGCOLOR_BLACK | EFI_TEXT_FGCOLOR_LIGHTGRAY);
    Console::ClearScreen();
}

/**
 * Displays a blue informational dialog box with the specified caption and message.
 *
 * @param Caption
 *        Supplies a caption string put on the dialog box.
 *
 * @param Message
 *        Supplies a message string put on the dialog box.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTCDECL
VOID
TextUi::DisplayInfoDialog(IN PCWSTR Caption,
                          IN PCWSTR Message)
{
    XTBL_DIALOG_HANDLE Handle;
    EFI_INPUT_KEY Key;
    UINT_PTR Index;

    /* Set dialog window attributes */
    Handle.Attributes = XTBL_TUI_DIALOG_GENERIC_BOX | XTBL_TUI_DIALOG_ACTIVE_BUTTON;

    /* Determine dialog window size and position */
    DetermineDialogBoxSize(&Handle, Message);

    /* Disable cursor and draw dialog box */
    Console::DisableCursor();
    DrawDialogBox(&Handle, Caption, Message);

    /* Draw active button */
    DrawButton(&Handle);

    /* Initialize key stroke */
    Key.ScanCode = 0;
    Key.UnicodeChar = 0;

    /* Wait until ENTER or ESC key is pressed */
    while(Key.ScanCode != 0x17 && Key.UnicodeChar != 0x0D)
    {
        /* Wait for key press and read key stroke */
        EfiUtils::WaitForEfiEvent(1, &(XtLoader::GetEfiSystemTable()->ConIn->WaitForKey), &Index);
        Console::ReadKeyStroke(&Key);
        Console::ResetInputBuffer();
    }

    /* Clear screen to remove dialog box */
    Console::SetAttributes(EFI_TEXT_BGCOLOR_BLACK | EFI_TEXT_FGCOLOR_LIGHTGRAY);
    Console::ClearScreen();
}

/**
 * Displays a blue informational dialog box with the specified caption and message and an input field.
 *
 * @param Caption
 *        Specifies a caption string put on the dialog box.
 *
 * @param Message
 *        Specifies a message string put on the dialog box.
 *
 * @param InputFieldText
 *        Specifies a pointer to the input field text that will be edited.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTCDECL
VOID
TextUi::DisplayInputDialog(IN PCWSTR Caption,
                           IN PCWSTR Message,
                           IN OUT PWCHAR *InputFieldText)
{
    SIZE_T InputFieldLength, TextCursorPosition, TextIndex, TextPosition;
    XTBL_DIALOG_HANDLE Handle;
    PWCHAR InputFieldBuffer;
    EFI_INPUT_KEY Key;
    EFI_STATUS Status;
    UINT_PTR Index;

    /* Set dialog window attributes */
    Handle.Attributes = XTBL_TUI_DIALOG_GENERIC_BOX | XTBL_TUI_DIALOG_ACTIVE_INPUT | XTBL_TUI_DIALOG_INACTIVE_BUTTON;

    /* Determine dialog window size and position */
    DetermineDialogBoxSize(&Handle, Message);

    /* Disable cursor and draw dialog box */
    Console::DisableCursor();
    DrawDialogBox(&Handle, Caption, Message);

    /* Draw inactive button */
    DrawButton(&Handle);

    /* Draw active input field */
    DrawInputField(&Handle, *InputFieldText);

    /* Initialize key stroke */
    Key.ScanCode = 0;
    Key.UnicodeChar = 0;

    /* Determine input field length */
    InputFieldLength = RTL::WideString::WideStringLength(*InputFieldText, 0);

    /* Allocate a buffer for storing the input field text */
    Status = Memory::AllocatePool(2048 * sizeof(WCHAR), (PVOID *)&InputFieldBuffer);
    if(Status != STATUS_EFI_SUCCESS)
    {
        /* Memory allocation failure, print error message and return */
        Debug::Print(L"ERROR: Memory allocation failure (Status Code: 0x%zX)\n", Status);
        DisplayErrorDialog(L"XTLDR", L"Failed to allocate memory for input field buffer.");
        return;
    }

    /* Copy input text into edit buffer */
    RTL::Memory::CopyMemory(InputFieldBuffer, *InputFieldText, InputFieldLength * sizeof(WCHAR));
    InputFieldBuffer[InputFieldLength] = L'\0';

    /* Start at first character */
    TextPosition = 0;
    Console::SetCursorPosition(Handle.PosX + 4 + TextPosition, Handle.PosY + Handle.Height - 4);

    /* Wait until ENTER or ESC key is pressed */
    while(TRUE)
    {
        /* Wait for key press and read key stroke */
        EfiUtils::WaitForEfiEvent(1, &(XtLoader::GetEfiSystemTable()->ConIn->WaitForKey), &Index);
        Console::ReadKeyStroke(&Key);

        /* Check key press scan code */
        if(Key.ScanCode == 0x17)
        {
            /* ESC key pressed, return */
            break;
        }
        else if(Key.UnicodeChar == 0x09)
        {
            /* TAB key pressed, toggle input field and button */
            Handle.Attributes ^= (XTBL_TUI_DIALOG_ACTIVE_INPUT | XTBL_TUI_DIALOG_INACTIVE_INPUT);
            Handle.Attributes ^= (XTBL_TUI_DIALOG_ACTIVE_BUTTON | XTBL_TUI_DIALOG_INACTIVE_BUTTON);
        }
        else if(Key.ScanCode == 0x03)
        {
            /* RIGHT key pressed, move cursor forward */
            if(Handle.Attributes & XTBL_TUI_DIALOG_ACTIVE_INPUT && TextPosition < InputFieldLength)
            {
                TextPosition++;
            }
        }
        else if(Key.ScanCode == 0x04)
        {
            /* LEFT key pressed, move cursor back */
            if(Handle.Attributes & XTBL_TUI_DIALOG_ACTIVE_INPUT && TextPosition > 0)
            {
                TextPosition--;
            }
        }
        else if(Key.ScanCode == 0x05)
        {
            /* HOME key pressed, move cursor to the beginning */
            if(Handle.Attributes & XTBL_TUI_DIALOG_ACTIVE_INPUT)
            {
                TextPosition = 0;
            }
        }
        else if(Key.ScanCode == 0x06)
        {
            /* END key pressed, move cursor to the end */
            if(Handle.Attributes & XTBL_TUI_DIALOG_ACTIVE_INPUT)
            {
                TextPosition = InputFieldLength;
            }
        }
        else if(Key.ScanCode == 0x08)
        {
            /* DELETE key pressed, delete character */
            if(Handle.Attributes & XTBL_TUI_DIALOG_ACTIVE_INPUT)
            {
                /* Check if buffer is not empty */
                if(InputFieldLength > 0 && TextPosition < InputFieldLength)
                {
                    /* Delete character */
                    RTL::Memory::MoveMemory(InputFieldBuffer + TextPosition, InputFieldBuffer + TextPosition + 1,
                                  (InputFieldLength - TextPosition) * sizeof(WCHAR));

                    /* Decrement length and NULL terminate string */
                    InputFieldLength--;
                    InputFieldBuffer[InputFieldLength] = L'\0';
                }
            }
        }
        else if(Key.UnicodeChar == 0x08)
        {
            /* BACKSPACE key pressed, delete character */
            if(Handle.Attributes & XTBL_TUI_DIALOG_ACTIVE_INPUT)
            {
                /* Check if buffer is not empty */
                if(InputFieldLength > 0 && TextPosition > 0 && TextPosition <= InputFieldLength)
                {
                    /* Move memory to overwrite the character to the left of the cursor */
                    RTL::Memory::MoveMemory(InputFieldBuffer + TextPosition - 1, InputFieldBuffer + TextPosition,
                                  (InputFieldLength - TextPosition + 1) * sizeof(WCHAR));

                    /* Decrement length, position and NULL terminate string */
                    TextPosition--;
                    InputFieldLength--;
                    InputFieldBuffer[InputFieldLength] = L'\0';
                }
            }
        }
        else if(Key.UnicodeChar == 0x0D)
        {
            /* ENTER key pressed, update input buffer */
            *InputFieldText = InputFieldBuffer;
            break;
        }
        else
        {
            /* Other key pressed, add character to the buffer */
            if(Handle.Attributes & XTBL_TUI_DIALOG_ACTIVE_INPUT && Key.UnicodeChar != 0)
            {
                /* Check if buffer is full */
                if(InputFieldLength < 2047)
                {
                    /* Insert character at current position */
                    RTL::Memory::MoveMemory(InputFieldBuffer + TextPosition + 1, InputFieldBuffer + TextPosition,
                                  (InputFieldLength - TextPosition) * sizeof(WCHAR));
                    InputFieldBuffer[TextPosition] = Key.UnicodeChar;

                    /* Increment length, position and NULL terminate string */
                    TextPosition++;
                    InputFieldLength++;
                    InputFieldBuffer[InputFieldLength] = L'\0';
                }
            }
        }

        /* Calculate text index and cursor position */
        if(TextPosition > (Handle.Width - 9))
        {
            TextIndex = TextPosition - (Handle.Width - 9);
            TextCursorPosition = Handle.Width - 9;
        }
        else
        {
            TextIndex = 0;
            TextCursorPosition = TextPosition;
        }

        /* Redraw input field and button */
        DrawButton(&Handle);
        DrawInputField(&Handle, &InputFieldBuffer[TextIndex]);

        /* Set cursor position if input field is active */
        if(Handle.Attributes & XTBL_TUI_DIALOG_ACTIVE_INPUT)
        {
            Console::SetCursorPosition(Handle.PosX + 4 + TextCursorPosition, Handle.PosY + Handle.Height - 4);
        }
    }

    /* Clear screen to remove dialog box */
    Console::SetAttributes(EFI_TEXT_BGCOLOR_BLACK | EFI_TEXT_FGCOLOR_LIGHTGRAY);
    Console::ClearScreen();
}

/**
 * Displays a blue informational dialog box with the specified caption and message and a progress bar.
 *
 * @param Caption
 *        Supplies a caption string put on the dialog box.
 *
 * @param Message
 *        Supplies a message string put on the dialog box.
 *
 * @param Percentage
 *        Specifies the percentage progress of the progress bar.
 *
 * @return This routine returns a dialog box handle needed to update the progress bar.
 *
 * @since XT 1.0
 */
XTCDECL
XTBL_DIALOG_HANDLE
TextUi::DisplayProgressDialog(IN PCWSTR Caption,
                              IN PCWSTR Message,
                              IN UCHAR Percentage)
{
    XTBL_DIALOG_HANDLE Handle;

    /* Set dialog window attributes */
    Handle.Attributes = XTBL_TUI_DIALOG_GENERIC_BOX | XTBL_TUI_DIALOG_PROGRESS_BAR;

    /* Determine dialog window size and position */
    DetermineDialogBoxSize(&Handle, Message);

    /* Disable cursor and draw dialog box */
    Console::DisableCursor();
    DrawDialogBox(&Handle, Caption, Message);

    /* Draw active button */
    DrawProgressBar(&Handle, Percentage);

    /* Return dialog handle */
    return Handle;
}

/**
 * Draws a text UI-based boot menu.
 *
 * @param Handle
 *        Supplies a pointer to the boot menu handle.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTCDECL
VOID
TextUi::DrawBootMenu(OUT PXTBL_DIALOG_HANDLE Handle)
{
    /* Query console screen resolution */
    Console::QueryMode(&Handle->ResX, &Handle->ResY);

    /* Set boot menu parameters */
    Handle->Attributes = 0;
    Handle->DialogColor = EFI_TEXT_BGCOLOR_BLACK;
    Handle->TextColor = EFI_TEXT_FGCOLOR_LIGHTGRAY;
    Handle->PosX = 3;
    Handle->PosY = 3;
    Handle->Width = Handle->ResX - 6;
    Handle->Height = Handle->ResY - 10;

    /* Clear screen and disable cursor */
    Console::SetAttributes(Handle->DialogColor | Handle->TextColor);
    Console::ClearScreen();
    Console::DisableCursor();

    /* Check if debugging enabled */
    if(DEBUG)
    {
        /* Print debug version of XTLDR banner */
        Console::SetCursorPosition((Handle->ResX - 44) / 2, 1);
        Console::Print(L"XTLDR Boot Loader v%d.%d (%s-%s)\n",
                       XTLDR_VERSION_MAJOR, XTLDR_VERSION_MINOR, XTOS_VERSION_DATE, XTOS_VERSION_HASH);
    }
    else
    {
        /* Print standard XTLDR banner */
        Console::SetCursorPosition((Handle->ResX - 22) / 2, 1);
        Console::Print(L"XTLDR Boot Loader v%d.%d\n", XTLDR_VERSION_MAJOR, XTLDR_VERSION_MINOR);
    }

    /* Draw empty dialog box for boot menu */
    DrawDialogBox(Handle, NULLPTR, NULLPTR);

    /* Print help message below the boot menu */
    Console::SetCursorPosition(0, Handle->PosY + Handle->Height);
    Console::SetAttributes(EFI_TEXT_BGCOLOR_BLACK | EFI_TEXT_FGCOLOR_LIGHTGRAY);
    Console::Print(L"    Use cursors to change the selection. Press ENTER key to boot the chosen\n"
                   L"    Operating System, 'e' to edit it before booting or 's' for XTLDR shell.\n"
                   L"    Additional help available after pressing F1 key.");
}

/**
 * Draws boot menu entry at the specified position.
 *
 * @param Handle
 *        Supplies a pointer to the boot menu handle.
 *
 * @param MenuEntry
 *        Supplies a pointer to the buffer containing a menu entry name.
 *
 * @param Position
 *        Specifies entry position on the list in the boot menu.
 *
 * @param Highlighted
 *        Specifies whether this entry should be highlighted or not.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTCDECL
VOID
TextUi::DrawBootMenuEntry(IN PXTBL_DIALOG_HANDLE Handle,
                          IN PWCHAR MenuEntry,
                          IN UINT Position,
                          IN BOOLEAN Highlighted)
{
    UINT Index;

    /* Move cursor to the right position */
    Console::SetCursorPosition(5, 4 + Position);

    /* Check whether this entry should be highlighted */
    if(Highlighted)
    {
        /* Highlight this entry */
        Console::SetAttributes(EFI_TEXT_BGCOLOR_LIGHTGRAY | EFI_TEXT_FGCOLOR_BLACK);
    }
    else
    {
        /* Set default colors */
        Console::SetAttributes(EFI_TEXT_BGCOLOR_BLACK | EFI_TEXT_FGCOLOR_LIGHTGRAY);
    }

    /* Clear menu entry */
    for(Index = 0; Index < Handle->Width - 4; Index++)
    {
        Console::Print(L" ");
    }

    /* Print menu entry */
    Console::SetCursorPosition(5, 4 + Position);
    Console::Print(L"%S\n", MenuEntry);
}

/**
 * Draws dialog box with caption and message.
 *
 * @param Handle
 *        Supplies a pointer to the dialog box handle.
 *
 * @param Caption
 *        Specifies a caption string put on the dialog box.
 *
 * @param Message
 *        Specifies a message string put on the dialog box.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTCDECL
VOID
TextUi::DrawDialogBox(IN OUT PXTBL_DIALOG_HANDLE Handle,
                      IN PCWSTR Caption,
                      IN PCWSTR Message)
{
    WCHAR BoxLine[XTBL_TUI_MAX_DIALOG_WIDTH];
    SIZE_T CaptionLength;
    UINT_PTR PosX, PosY;

    /* Set dialog colors */
    if(Handle->Attributes & XTBL_TUI_DIALOG_ERROR_BOX)
    {
        /* Error dialog with red background and brown button */
        Handle->DialogColor = EFI_TEXT_BGCOLOR_RED;
        Handle->TextColor = EFI_TEXT_FGCOLOR_WHITE;
    }
    else if(Handle->Attributes & XTBL_TUI_DIALOG_GENERIC_BOX)
    {
        /* Generic dialog with blue background and cyan button */
        Handle->DialogColor = EFI_TEXT_BGCOLOR_BLUE;
        Handle->TextColor = EFI_TEXT_FGCOLOR_WHITE;
    }

    /* Set dialog box colors */
    Console::SetAttributes(Handle->DialogColor | 0x0F);

    /* Iterate through dialog box lines */
    for(PosY = Handle->PosY; PosY < Handle->PosY + Handle->Height; PosY++)
    {
        /* Set cursor position in the appropriate place */
        Console::SetCursorPosition(Handle->PosX, PosY);

        /* Draw dialog box */
        if(PosY == Handle->PosY)
        {
            /* Draw top line of the dialog box, starting from the left corner */
            BoxLine[0] = EFI_TEXT_BOX_DOWN_RIGHT;

            /* Check if there is a caption for this dialog */
            if(Caption != NULLPTR)
            {
                /* Get caption length */
                CaptionLength = RTL::WideString::WideStringLength(Caption, 0);

                /* Start caption area with vertical line */
                BoxLine[1] = EFI_TEXT_BOX_VERTICAL_LEFT;

                /* Fill caption area with spaces */
                for(PosX = 2; PosX < CaptionLength + 4; PosX++)
                {
                    BoxLine[PosX] = ' ';
                }

                /* End caption area with vertical line */
                BoxLine[CaptionLength + 4] = EFI_TEXT_BOX_VERTICAL_RIGHT;
            }
            else
            {
                /* No caption, -4 because of left and right vertical lines and corresponding spaces */
                CaptionLength = -4;
            }

            /* Draw bottom line */
            for(PosX = CaptionLength + 5; PosX < Handle->Width - 1; PosX++)
            {
                BoxLine[PosX] = EFI_TEXT_BOX_HORIZONTAL;
            }

            /* End with top right corner */
            BoxLine[Handle->Width - 1] = EFI_TEXT_BOX_DOWN_LEFT;
        }
        else if(PosY == Handle->PosY + Handle->Height - 1)
        {
            /* Draw bottom line of the dialog box, starting from the left corner */
            BoxLine[0] = EFI_TEXT_BOX_UP_LEFT;

            /* Fill bottom with horizontal line */
            for(PosX = 1; PosX < Handle->Width - 1; PosX++)
            {
                BoxLine[PosX] = EFI_TEXT_BOX_HORIZONTAL;
            }

            /* End with bottom right corner */
            BoxLine[Handle->Width - 1] = EFI_TEXT_BOX_UP_RIGHT;
        }
        else
        {
            /* Draw the middle of the dialog box */
            BoxLine[0] = EFI_TEXT_BOX_VERTICAL;

            /* Fill dialog box with spaces */
            for(PosX = 1; PosX < Handle->Width - 1; PosX++)
            {
                BoxLine[PosX] = ' ';
            }

            /* End with vertical line */
            BoxLine[Handle->Width - 1] = EFI_TEXT_BOX_VERTICAL;
        }

        /* Add NULL terminator to the end of the line */
        BoxLine[Handle->Width] = 0;

        /* Write the line to the console */
        Console::Write(BoxLine);
    }

    /* Make sure there is a caption to print */
    if(Caption != NULLPTR)
    {
    /* Write dialog box caption */
        Console::SetCursorPosition(Handle->PosX + 3, Handle->PosY);
        Console::Print(L"%S", Caption);
    }

    /* Make sure there is a message to print */
    if(Message != NULLPTR)
    {
        /* Write a message on the dialog box */
        DrawMessage(Handle, Message);
    }
}

/**
 * Draws an active or inactive button in the dialog box, depending on the attributes.
 *
 * @param Handle
 *        Supplies a pointer to the dialog box handle.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTCDECL
VOID
TextUi::DrawButton(IN PXTBL_DIALOG_HANDLE Handle)
{
    ULONG ButtonColor, TextColor;

    /* Set dialog button colors */
    if(Handle->Attributes & XTBL_TUI_DIALOG_ACTIVE_BUTTON)
    {
        /* This is an active button */
        if(Handle->Attributes & XTBL_TUI_DIALOG_ERROR_BOX)
        {
            /* This is an error dialog box */
            ButtonColor = EFI_TEXT_BGCOLOR_BROWN;
            TextColor = EFI_TEXT_FGCOLOR_WHITE;
        }
        else
        {
            /* This is a generic dialog box */
            ButtonColor = EFI_TEXT_BGCOLOR_CYAN;
            TextColor = EFI_TEXT_FGCOLOR_WHITE;
        }
    }
    else
    {
        /* This is an inactive button */
        ButtonColor = EFI_TEXT_BGCOLOR_LIGHTGRAY;
        TextColor = EFI_TEXT_FGCOLOR_BLACK;
    }

    /* Disable cursor and draw dialog button */
    Console::DisableCursor();
    Console::SetAttributes(ButtonColor | TextColor);
    Console::SetCursorPosition(Handle->ResX / 2 - 4, Handle->PosY + Handle->Height - 2);
    Console::Print(L"[  OK  ]");
}

/**
 * Draws an active or inactive input field in the dialog box, depending on the attributes.
 *
 * @param Handle
 *        Supplies a pointer to the dialog box handle.
 *
 * @param InputFieldText
 *        Supplies a pointer to the wide char string that will be displayed in the input field.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTCDECL
VOID
TextUi::DrawInputField(IN PXTBL_DIALOG_HANDLE Handle,
                       IN PWCHAR InputFieldText)
{
    WCHAR InputField[XTBL_TUI_MAX_DIALOG_WIDTH];
    ULONG InputColor, TextColor;
    UINT_PTR Index, Position;
    SIZE_T Length;

    /* Set dialog button colors */
    if(Handle->Attributes & XTBL_TUI_DIALOG_ACTIVE_INPUT)
    {
        /* This is an active input field */
        if(Handle->Attributes & XTBL_TUI_DIALOG_ERROR_BOX)
        {
            /* This is an error dialog box */
            InputColor = EFI_TEXT_BGCOLOR_BROWN;
            TextColor = EFI_TEXT_FGCOLOR_WHITE;
        }
        else
        {
            /* This is a generic dialog box */
            InputColor = EFI_TEXT_BGCOLOR_CYAN;
            TextColor = EFI_TEXT_FGCOLOR_WHITE;
        }
    }
    else
    {
        /* This is an inactive input field */
        InputColor = EFI_TEXT_BGCOLOR_LIGHTGRAY;
        TextColor = EFI_TEXT_FGCOLOR_BLACK;
    }

    /* Set progress bar color and position */
    Console::SetAttributes(InputColor | TextColor);
    Position = (Handle->Attributes & (XTBL_TUI_DIALOG_ACTIVE_BUTTON | XTBL_TUI_DIALOG_INACTIVE_BUTTON)) ? 4 : 3;
    Console::SetCursorPosition(Handle->PosX + 4, Handle->PosY + Handle->Height - Position);

    /* Draw input field */
    for(Index = 0; Index < Handle->Width - 8; Index++)
    {
        /* Fill input field with spaces */
        InputField[Index] = L' ';
    }

    /* Disable cursor and write input field to console */
    Console::DisableCursor();
    Console::Write(InputField);

    /* Check input field text length */
    Length = RTL::WideString::WideStringLength(InputFieldText, 0);
    if(Length > (Handle->Width - 9))
    {
        /* Text longer than input field width, display only part of it */
        Length = Handle->Width - 9;
    }

    /* Copy a part of input field text to input field */
    for(Index = 0; Index < Length; Index++)
    {
        /* Write input field text */
        InputField[Index] = InputFieldText[Index];
    }

    /* Add NULL terminator to the end of the line */
    InputField[Handle->Width] = 0;

    /* Write input field text */
    Console::SetCursorPosition(Handle->PosX + 4, Handle->PosY + Handle->Height - Position);
    Console::Write(InputField);

    /* Check if this is an active input field */
    if(Handle->Attributes & XTBL_TUI_DIALOG_ACTIVE_INPUT)
    {
        /* Enable cursor for active input field */
        Console::EnableCursor();
    }
}

/**
 * Draws a message on the dialog box specified by the handle.
 *
 * @param Handle
 *        Supplies a pointer to the dialog box handle.
 *
 * @param Message
 *        Supplies a message that will be displayed on the dialog box.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTCDECL
VOID
TextUi::DrawMessage(IN PXTBL_DIALOG_HANDLE Handle,
                    IN PCWSTR Message)
{
    PWCHAR Msg, MsgLine, LastMsgLine;
    SIZE_T Index, Length, LineLength;
    EFI_STATUS Status;
    ULONG Line;

    /* Allocate memory for dialog box message */
    Length = RTL::WideString::WideStringLength(Message, 0);
    Status = Memory::AllocatePool(Length * sizeof(WCHAR), (PVOID *)&Msg);
    if(Status != STATUS_EFI_SUCCESS)
    {
        /* Memory allocation failure, print debug message and return */
        Debug::Print(L"ERROR: Memory allocation failure (Status Code: 0x%zX)\n", Status);
        return;
    }

    /* Make a copy of dialog box message */
    RTL::Memory::CopyMemory(Msg, Message, Length * sizeof(WCHAR));
    Msg[Length] = 0;

    /* Tokenize dialog box message */
    MsgLine = RTL::WideString::TokenizeWideString(Msg, L"\n", &LastMsgLine);

    /* Iterate through message lines */
    Line = 0;
    while(MsgLine)
    {
        /* Determine line length */
        LineLength = RTL::WideString::WideStringLength(MsgLine, 0);

        /* Write line in the dialog box */
        Console::SetCursorPosition(Handle->PosX + 2, Handle->PosY + 2 + Line);
        Console::SetAttributes(Handle->DialogColor | Handle->TextColor);
        Console::Print(L"%S", MsgLine);

        /* Check if message line is shorter than the dialog box working area */
        if(LineLength < Handle->Width - 4)
        {
            /* Fill the rest of the line with spaces */
            for(Index = LineLength; Index < Handle->Width - 4; Index++)
            {
                Console::Print(L" ");
            }
        }

        /* Get next line */
        MsgLine = RTL::WideString::TokenizeWideString(NULLPTR, L"\n", &LastMsgLine);
        Line++;
    }
}

/**
 * Draws a progress bar component in the dialog box.
 *
 * @param Handle
 *        Supplies a pointer to the dialog box handle.
 *
 * @param Percentage
 *        Specifies the percentage progress of the progress bar.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTCDECL
VOID
TextUi::DrawProgressBar(IN PXTBL_DIALOG_HANDLE Handle,
                        IN UCHAR Percentage)
{
    UINT_PTR Index, ProgressLength, ProgressBarLength;
    WCHAR ProgressBar[XTBL_TUI_MAX_DIALOG_WIDTH];
    UINT_PTR Position;

    /* Determine progress bar length and calculate progress */
    ProgressBarLength = Handle->Width - 8;
    ProgressLength = (ProgressBarLength * Percentage) / 100;

    /* Set progress bar color and position */
    Console::SetAttributes(EFI_TEXT_FGCOLOR_YELLOW);
    Position = (Handle->Attributes & (XTBL_TUI_DIALOG_ACTIVE_BUTTON | XTBL_TUI_DIALOG_INACTIVE_BUTTON)) ? 4 : 3;
    Console::SetCursorPosition(Handle->PosX + 4, Handle->PosY + Handle->Height - Position);

    /* Draw progress bar */
    for(Index = 0; Index < ProgressBarLength; Index++)
    {
        /* Fill progress bar */
        if(Index < ProgressLength)
        {
            /* Fill with full block */
            ProgressBar[Index] = EFI_TEXT_BOX_FULL_BLOCK;
        }
        else
        {
            /* Fill with light block */
            ProgressBar[Index] = EFI_TEXT_BOX_LIGHT_BLOCK;
        }
    }

    /* Terminate progress bar string */
    ProgressBar[Index] = 0;

    /* Disable cursor and write progress bar to console */
    Console::DisableCursor();
    Console::Write(ProgressBar);
}

/**
 * Draws a text-based boot edition menu.
 *
 * @param Handle
 *        Supplies a pointer to the edition menu handle.
 *
 * @return This function does not return any value.
 *
 * @since XT 1.0
 */
XTCDECL
VOID
TextUi::DrawEditMenu(OUT PXTBL_DIALOG_HANDLE Handle)
{
    /* Query console screen resolution */
    Console::QueryMode(&Handle->ResX, &Handle->ResY);

    /* Set boot menu parameters */
    Handle->Attributes = 0;
    Handle->DialogColor = EFI_TEXT_BGCOLOR_BLACK;
    Handle->TextColor = EFI_TEXT_FGCOLOR_LIGHTGRAY;
    Handle->PosX = 3;
    Handle->PosY = 3;
    Handle->Width = Handle->ResX - 6;
    Handle->Height = Handle->ResY - 10;

    /* Clear screen and disable cursor */
    Console::SetAttributes(Handle->DialogColor | Handle->TextColor);
    Console::ClearScreen();
    Console::DisableCursor();

    /* Check if debugging enabled */
    if(DEBUG)
    {
        /* Print debug version of XTLDR banner */
        Console::SetCursorPosition((Handle->ResX - 44) / 2, 1);
        Console::Print(L"XTLDR Boot Loader v%d.%d (%s-%s)\n",
                       XTLDR_VERSION_MAJOR, XTLDR_VERSION_MINOR, XTOS_VERSION_DATE, XTOS_VERSION_HASH);
    }
    else
    {
        /* Print standard XTLDR banner */
        Console::SetCursorPosition((Handle->ResX - 22) / 2, 1);
        Console::Print(L"XTLDR Boot Loader v%d.%d\n", XTLDR_VERSION_MAJOR, XTLDR_VERSION_MINOR);
    }

    /* Draw empty dialog box for boot menu */
    DrawDialogBox(Handle, L"Edit Options", NULLPTR);

    /* Print help message below the edit menu */
    Console::SetCursorPosition(0, Handle->PosY + Handle->Height);
    Console::SetAttributes(EFI_TEXT_BGCOLOR_BLACK | EFI_TEXT_FGCOLOR_LIGHTGRAY);
    Console::Print(L"    Use cursors to change the selection. Press ENTER key to edit the chosen\n"
                   L"    option, ESC to return to the main boot menu or F10 to boot.\n");
}

/**
 * Draws edit menu entry at the specified position.
 *
 * @param Handle
 *        Supplies a pointer to the boot menu handle.
 *
 * @param OptionName
 *        Supplies a pointer to the buffer containing a part of the menu entry name (an option name).
 *
 * @param OptionValue
 *        Supplies a pointer to the buffer containing a part of the menu entry name (an option value).
 *
 * @param Position
 *        Specifies entry position on the list in the boot menu.
 *
 * @param Highlighted
 *        Specifies whether this entry should be highlighted or not.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTCDECL
EFI_STATUS
TextUi::DrawEditMenuEntry(IN PXTBL_DIALOG_HANDLE Handle,
                          IN PCWSTR OptionName,
                          IN PCWSTR OptionValue,
                          IN UINT Position,
                          IN BOOLEAN Highlighted)
{
    BOOLEAN Allocation;
    PCWSTR DisplayValue, ShortValue;
    UINT Index;
    ULONG OptionNameLength, OptionValueLength, OptionWidth;
    EFI_STATUS Status;

    /* Assume no allocation was made */
    Allocation = FALSE;

    /* Set display value depending on input */
    DisplayValue = (OptionValue != NULLPTR) ? OptionValue : L"";

    /* Determine lengths */
    OptionNameLength = RTL::WideString::WideStringLength(OptionName, 0);
    OptionValueLength = RTL::WideString::WideStringLength(DisplayValue, 0);
    OptionWidth = Handle->Width - 4 - (OptionNameLength + 2);

    /* Check if value needs to be truncated */
    if(OptionValueLength > OptionWidth)
    {
        /* Allocate buffer for new, shortened value */
        Status = Memory::AllocatePool((OptionWidth + 1) * sizeof(WCHAR), (PVOID *)&ShortValue); // This allocates PWCHAR
        if(Status != STATUS_EFI_SUCCESS)
        {
            /* Memory allocation failure, print debug message and return */
            Debug::Print(L"ERROR: Memory allocation failure (Status Code: 0x%zX)\n", Status);
            return Status;
        }

        /* Copy a desired value length into the allocated buffer and append "..." */
        RTL::Memory::CopyMemory((PWCHAR)ShortValue, DisplayValue, (OptionWidth - 3) * sizeof(WCHAR));
        RTL::Memory::CopyMemory((PWCHAR)ShortValue + OptionWidth - 3, L"...", 3 * sizeof(WCHAR));
        ((PWCHAR)ShortValue)[OptionWidth] = L'\0';

        /* Mark that allocation was made and set new display value */
        Allocation = TRUE;
        DisplayValue = ShortValue;
    }

    /* Move cursor to the right position */
    Console::SetCursorPosition(5, 4 + Position);

    /* Check whether this entry should be highlighted */
    if(Highlighted)
    {
        /* Highlight this entry */
        Console::SetAttributes(EFI_TEXT_BGCOLOR_LIGHTGRAY | EFI_TEXT_FGCOLOR_BLACK);
    }
    else
    {
        /* Set default colors */
        Console::SetAttributes(EFI_TEXT_BGCOLOR_BLACK | EFI_TEXT_FGCOLOR_LIGHTGRAY);
    }

    /* Clear menu entry */
    for(Index = 0; Index < Handle->Width - 4; Index++)
    {
        Console::Print(L" ");
    }

    /* Print menu entry */
    Console::SetCursorPosition(5, 4 + Position);
    Console::Print(L"%S: %S", OptionName, DisplayValue);

    /* Check if allocation was made */
    if(Allocation)
    {
        /* Free allocated memory */
        Memory::FreePool((PVOID)DisplayValue);
    }

    /* Return success */
    return STATUS_EFI_SUCCESS;
}

/**
 * Updates the progress bar on the dialog box.
 *
 * @param Handle
 *        Supplies a pointer to the dialog box handle.
 *
 * @param Message
 *        Supplies a new message that will be put on the dialog box, while updating the progress bar.
 *
 * @param Percentage
 *        Specifies the new percentage progress of the progress bar.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTCDECL
VOID
TextUi::UpdateProgressBar(IN PXTBL_DIALOG_HANDLE Handle,
                          IN PCWSTR Message,
                          IN UCHAR Percentage)
{
    /* Check if message needs an update */
    if(Message != NULLPTR)
    {
        /* Update a message on the dialog box */
        DrawMessage(Handle, Message);
    }

    /* Update progress bar */
    DrawProgressBar(Handle, Percentage);
}
