/** @file
  Builtin picker and password handler.

  Copyright (C) 2019, vit9696. All rights reserved.<BR>
  Copyright (C) 2021, Mike Beaton. All rights reserved.<BR>
  SPDX-License-Identifier: BSD-3-Clause
**/

#include "BootManagementInternal.h"

#include <Guid/AppleFile.h>
#include <Guid/AppleVariable.h>
#include <Guid/OcVariable.h>

#include <IndustryStandard/AppleCsrConfig.h>

#include <Protocol/AppleBootPolicy.h>
#include <Protocol/AppleKeyMapAggregator.h>
#include <Protocol/AppleBeepGen.h>
#include <Protocol/LoadedImage.h>
#include <Protocol/OcAudio.h>
#include <Protocol/SimpleTextOut.h>

#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/BaseOverflowLib.h>
#include <Library/OcConsoleLib.h>
#include <Library/OcCryptoLib.h>
#include <Library/OcDebugLogLib.h>
#include <Library/DevicePathLib.h>
#include <Library/OcTimerLib.h>
#include <Library/OcTypingLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/OcAppleKeyMapLib.h>
#include <Library/OcBootManagementLib.h>
#include <Library/OcDevicePathLib.h>
#include <Library/OcFileLib.h>
#include <Library/OcMiscLib.h>
#include <Library/OcRtcLib.h>
#include <Library/OcStringLib.h>
#include <Library/PrintLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>
#include <Library/UefiLib.h>
#include <Library/ResetSystemLib.h>

STATIC INT32  mStatusRow;
STATIC INT32  mStatusColumn;

STATIC INT32  mRunningColumn;

STATIC UINT64  mPreviousTick;

STATIC UINT64  mLoopDelayStart;
STATIC UINT64  mLoopDelayEnd;

typedef enum {
  TAB_PICKER,
  TAB_RESTART,
  TAB_SHUTDOWN,
 #if defined (BUILTIN_DEMONSTRATE_TYPING)
  TAB_TYPING_DEMO,
 #endif
  TAB_MAX
} _TAB_FOCUS;

typedef _TAB_FOCUS TAB_FOCUS;

STATIC TAB_FOCUS  mFocusList[] = {
  TAB_PICKER,
  TAB_SHUTDOWN,
  TAB_RESTART,
 #if defined (BUILTIN_DEMONSTRATE_TYPING)
  TAB_TYPING_DEMO
 #endif
};

STATIC TAB_FOCUS  mFocusListReversed[] = {
  TAB_PICKER,
  TAB_RESTART,
  TAB_SHUTDOWN,
 #if defined (BUILTIN_DEMONSTRATE_TYPING)
  TAB_TYPING_DEMO
 #endif
};

STATIC TAB_FOCUS  mFocusListMinimal[] = {
  TAB_PICKER,
 #if defined (BUILTIN_DEMONSTRATE_TYPING)
  TAB_TYPING_DEMO
 #endif
};

#define MENU_PREFIX_LENGTH  (5)

#define OC_KB_DBG_MAX_COLUMN           80
#define OC_KB_DBG_DELTA_SAMPLE_COLUMN  0 // 40

#if defined (BUILTIN_DEMONSTRATE_TYPING)
#define OC_KB_DBG_PRINT_ROW  4
#else
#define OC_KB_DBG_PRINT_ROW  2
#endif

#define OC_KB_DBG_DOWN_ROW       (OC_KB_DBG_PRINT_ROW + 4)
#define OC_KB_DBG_X_ROW          (OC_KB_DBG_PRINT_ROW + 5)
#define OC_KB_DBG_MODIFIERS_ROW  (OC_KB_DBG_PRINT_ROW + 6)

STATIC
VOID
InitKbDebugDisplay (
  VOID
  )
{
  mRunningColumn = 0;

  mLoopDelayStart = 0;
  mLoopDelayEnd   = 0;
}

STATIC
VOID
EFIAPI
InstrumentLoopDelay (
  UINT64  LoopDelayStart,
  UINT64  LoopDelayEnd
  )
{
  mLoopDelayStart = LoopDelayStart;
  mLoopDelayEnd   = LoopDelayEnd;
}

STATIC
VOID
EFIAPI
ShowKbDebugDisplay (
  UINTN               NumKeysDown,
  UINTN               NumKeysHeld,
  APPLE_MODIFIER_MAP  Modifiers
  )
{
  CONST CHAR16  *ClearSpace = L"      ";

  UINT64  CurrentTick;

  CHAR16  Code[3];         // includes flush-ahead space, to make progress visible

  Code[1] = L' ';
  Code[2] = L'\0';

  CurrentTick = AsmReadTsc ();

  if (mRunningColumn == OC_KB_DBG_DELTA_SAMPLE_COLUMN) {
    gST->ConOut->SetCursorPosition (gST->ConOut, 0, mStatusRow + OC_KB_DBG_PRINT_ROW + 1);

    Print (
      L"Called delta   = %,Lu%s\n",
      CurrentTick - mPreviousTick,
      ClearSpace
      );

    Print (
      L"Loop delta     = %,Lu (@ -%,Lu)%s%s\n",
      mLoopDelayEnd == 0 ? 0 : mLoopDelayEnd - mLoopDelayStart,
      mLoopDelayEnd == 0 ? 0 : CurrentTick - mLoopDelayEnd,
      ClearSpace,
      ClearSpace
      );
  }

  mPreviousTick = CurrentTick;

  //
  // Show Apple Event keys
  //
  gST->ConOut->SetCursorPosition (gST->ConOut, mRunningColumn, mStatusRow + OC_KB_DBG_DOWN_ROW);
  if (NumKeysDown > 0) {
    Code[0] = L'D';
  } else {
    Code[0] = L' ';
  }

  gST->ConOut->OutputString (gST->ConOut, Code);

  //
  // Show AKMA key held info
  //
  gST->ConOut->SetCursorPosition (gST->ConOut, mRunningColumn, mStatusRow + OC_KB_DBG_X_ROW);
  if (NumKeysHeld > 0) {
    Code[0] = L'X';
  } else {
    Code[0] = L'.';
  }

  gST->ConOut->OutputString (gST->ConOut, Code);

  //
  // Modifiers info
  //
  gST->ConOut->SetCursorPosition (gST->ConOut, mRunningColumn, mStatusRow + OC_KB_DBG_MODIFIERS_ROW);
  if (Modifiers == 0) {
    Code[0] = L' ';
    gST->ConOut->OutputString (gST->ConOut, Code);
  } else {
    Print (L"%X", Modifiers);
  }

  if (++mRunningColumn >= OC_KB_DBG_MAX_COLUMN) {
    mRunningColumn = 0;
  }

  gST->ConOut->SetCursorPosition (gST->ConOut, mStatusColumn, mStatusRow);
}

STATIC OC_KB_DEBUG_CALLBACKS  mSimplePickerKbDebug = {
  InstrumentLoopDelay,
  ShowKbDebugDisplay
};

STATIC
VOID
DisplaySystemMs (
  VOID
  )
{
  UINT64  CurrentMillis;

  CurrentMillis = DivU64x64Remainder (GetTimeInNanoSecond (GetPerformanceCounter ()), 1000000ULL, NULL);
  Print (L"%,Lu]", CurrentMillis);
}

STATIC
CHAR16
GetPickerEntryCursor (
  IN  OC_BOOT_CONTEXT  *BootContext,
  IN  UINT32           TimeOutSeconds,
  IN  INTN             ChosenEntry,
  IN  UINTN            Index,
  IN  OC_MODIFIER_MAP  OcModifiers
  )
{
  if ((TimeOutSeconds > 0) && (BootContext->DefaultEntry->EntryIndex - 1 == Index)) {
    return L'*';
  }

  if ((ChosenEntry >= 0) && ((UINTN)ChosenEntry == Index)) {
    return ((OcModifiers & OC_MODIFIERS_SET_DEFAULT) != 0) ? L'+' : L'>';
  }

  return L' ';
}

VOID
UpdateTabContext (
  IN  BOOLEAN          IsEntering,
  IN  OC_BOOT_CONTEXT  *BootContext,
  IN  OC_BOOT_ENTRY    **BootEntries,
  IN  TAB_FOCUS        TabFocus,
  IN  INTN             ChosenEntry,
  IN  CHAR16           OldEntryCursor,
#if defined (BUILTIN_DEMONSTRATE_TYPING)
  IN  INT32            TypingRow,
  IN  INT32            TypingColumn,
#endif
  IN  INT32            FirstIndexRow,
  IN  INT32            ShutdownRestartRow,
  IN  INT32            ShutdownColumn,
  IN  INT32            RestartColumn
  )
{
  CHAR16  Code[2];

  Code[1] = L'\0';

  if (TabFocus == TAB_PICKER) {
    if (ChosenEntry >= 0) {
      gST->ConOut->SetCursorPosition (gST->ConOut, 0, FirstIndexRow + ChosenEntry);
      Code[0] = IsEntering ? OldEntryCursor : L' ';
      gST->ConOut->OutputString (gST->ConOut, Code);
    }

    if (IsEntering) {
      if (ChosenEntry >= 0) {
        OcPlayAudioFile (BootContext->PickerContext, OC_VOICE_OVER_AUDIO_FILE_SELECTED, OC_VOICE_OVER_AUDIO_BASE_TYPE_OPEN_CORE, FALSE);
        OcPlayAudioEntry (BootContext->PickerContext, BootEntries[ChosenEntry]);
      } else {
        //
        // TODO: Sound for tabbing back to picker if no entry selected (cannot currently happen)
        //
      }
    }
  } else if ((TabFocus == TAB_SHUTDOWN) || (TabFocus == TAB_RESTART)) {
    if (TabFocus == TAB_SHUTDOWN) {
      gST->ConOut->SetCursorPosition (gST->ConOut, ShutdownColumn, ShutdownRestartRow);
    } else {
      gST->ConOut->SetCursorPosition (gST->ConOut, RestartColumn, ShutdownRestartRow);
    }

    Code[0] = IsEntering ? L'[' : '|';
    gST->ConOut->OutputString (gST->ConOut, Code);
    if (TabFocus == TAB_SHUTDOWN) {
      gST->ConOut->OutputString (gST->ConOut, L"Shutdown");
    } else {
      gST->ConOut->OutputString (gST->ConOut, L"Reset");
    }

    Code[0] = IsEntering ? L']' : '|';
    gST->ConOut->OutputString (gST->ConOut, Code);

    if (IsEntering) {
      if (TabFocus == TAB_SHUTDOWN) {
        OcPlayAudioFile (BootContext->PickerContext, OC_VOICE_OVER_AUDIO_FILE_SELECTED, OC_VOICE_OVER_AUDIO_BASE_TYPE_OPEN_CORE, FALSE);
        OcPlayAudioFile (BootContext->PickerContext, OC_VOICE_OVER_AUDIO_FILE_SHUT_DOWN, OC_VOICE_OVER_AUDIO_BASE_TYPE_OPEN_CORE, FALSE);
      } else {
        OcPlayAudioFile (BootContext->PickerContext, OC_VOICE_OVER_AUDIO_FILE_SELECTED, OC_VOICE_OVER_AUDIO_BASE_TYPE_OPEN_CORE, FALSE);
        OcPlayAudioFile (BootContext->PickerContext, OC_VOICE_OVER_AUDIO_FILE_RESTART, OC_VOICE_OVER_AUDIO_BASE_TYPE_OPEN_CORE, FALSE);
      }
    }
  }

 #if defined (BUILTIN_DEMONSTRATE_TYPING)
  else if (TabFocus == TAB_TYPING_DEMO) {
    gST->ConOut->SetCursorPosition (gST->ConOut, TypingColumn, TypingRow);
    Code[0] = IsEntering ? L'_' : ' ';
    gST->ConOut->OutputString (gST->ConOut, Code);
  }
 #endif
}

EFI_STATUS
EFIAPI
OcShowSimpleBootMenu (
  IN  OC_BOOT_CONTEXT  *BootContext,
  IN  OC_BOOT_ENTRY    **BootEntries,
  OUT OC_BOOT_ENTRY    **ChosenBootEntry
  )
{
  APPLE_KEY_MAP_AGGREGATOR_PROTOCOL  *KeyMap;
  UINTN                              Index;
  UINTN                              Length;
  OC_PICKER_KEY_INFO                 PickerKeyInfo;
  INTN                               ChosenEntry;
  INTN                               OldChosenEntry;
  INT32                              FirstIndexRow;
  INT32                              MillisColumn;
  CHAR16                             EntryCursor;
  CHAR16                             OldEntryCursor;
  CHAR16                             Code[2];
  UINT32                             TimeOutSeconds;
  UINT32                             Count;
  UINT64                             KeyEndTime;
  BOOLEAN                            PlayedOnce;
  BOOLEAN                            PlayChosen;
  BOOLEAN                            ModifiersChanged;
  UINTN                              Columns;
  UINTN                              Rows;
  UINTN                              SafeEntryLength;
  UINTN                              SuffixLength;

 #if defined (BUILTIN_DEMONSTRATE_TYPING)
  INT32  TypingRow;
  INT32  TypingColumn;
  INT32  TypingStartColumn;
 #endif
  INT32      ShutdownRestartRow;
  INT32      ShutdownColumn;
  INT32      RestartColumn;
  UINTN      FocusState;
  TAB_FOCUS  *FocusList;
  UINTN      NumFocusList;

  Code[1] = L'\0';

  TimeOutSeconds = BootContext->PickerContext->TimeoutSeconds;
  KeyEndTime     = 0;

  ASSERT (BootContext->DefaultEntry != NULL);
  ChosenEntry    = (INTN)(BootContext->DefaultEntry->EntryIndex - 1);
  OldChosenEntry = ChosenEntry;

  EntryCursor    = L'\0';
  OldEntryCursor = L'\0';

  FirstIndexRow = -1;

  FocusState = 0;
  if ((BootContext->PickerContext->PickerAttributes & OC_ATTR_USE_MINIMAL_UI) == 0) {
    STATIC_ASSERT (ARRAY_SIZE (mFocusList) == ARRAY_SIZE (mFocusListReversed), "Mismatched focus list sizes");
    FocusList = ((BootContext->PickerContext->PickerAttributes & OC_ATTR_USE_REVERSED_UI) == 0)
      ? mFocusList
      : mFocusListReversed;
    NumFocusList = ARRAY_SIZE (mFocusList);
  } else {
    FocusList    = mFocusListMinimal;
    NumFocusList = ARRAY_SIZE (mFocusListMinimal);
  }

  //
  //  Used to detect changes.
  //
  PickerKeyInfo.OcModifiers = OC_MODIFIERS_NONE;

  PlayedOnce = FALSE;
  PlayChosen = FALSE;

  DEBUG_CODE_BEGIN ();
  if ((BootContext->PickerContext->PickerAttributes & OC_ATTR_SHOW_DEBUG_DISPLAY) != 0) {
    DEBUG ((DEBUG_INFO, "OCB: Init builtin picker debug\n"));
    InitKbDebugDisplay ();
    BootContext->PickerContext->KbDebug = &mSimplePickerKbDebug;
  }

  DEBUG_CODE_END ();

  KeyMap = OcAppleKeyMapInstallProtocols (FALSE);
  if (KeyMap == NULL) {
    DEBUG ((DEBUG_ERROR, "OCB: Missing AppleKeyMapAggregator\n"));
    return EFI_UNSUPPORTED;
  }

  Count = (UINT32)BootContext->BootEntryCount;

  if (Count != MIN (Count, OC_INPUT_MAX)) {
    DEBUG ((DEBUG_WARN, "OCB: Cannot display all entries in the menu!\n"));
  }

  //
  // Fix overlong menu entries. For very small screens, below a minimum width we
  // overflow anyway, but other parts of menu will also have overflowed before this.
  //
  gST->ConOut->QueryMode (gST->ConOut, gST->ConOut->Mode->Mode, &Columns, &Rows);
  for (Index = 0; Index < Count; Index++) {
    SuffixLength = 0;
    if (BootEntries[Index]->IsExternal) {
      SuffixLength += L_STR_LEN (OC_MENU_EXTERNAL);
    }

    if (BootEntries[Index]->IsFolder) {
      SuffixLength += L_STR_LEN (OC_MENU_DISK_IMAGE);
    }

    if (Columns > MENU_PREFIX_LENGTH + SuffixLength + L_STR_LEN (L"...") + 1) {
      SafeEntryLength = Columns - MENU_PREFIX_LENGTH - SuffixLength;
    } else {
      SafeEntryLength = L_STR_LEN (L"...") + 1;
    }

    if (StrLen (BootEntries[Index]->Name) > SafeEntryLength) {
      StrCpyS (&BootEntries[Index]->Name[SafeEntryLength - L_STR_LEN (L"...")], L_STR_SIZE (L"..."), L"...");
    }
  }

  OcConsoleControlSetMode (EfiConsoleControlScreenText);
  gST->ConOut->EnableCursor (gST->ConOut, FALSE);

  if (BootContext->PickerContext->ConsoleAttributes != 0) {
    gST->ConOut->SetAttribute (gST->ConOut, BootContext->PickerContext->ConsoleAttributes & 0x7FU);
  }

  gST->ConOut->ClearScreen (gST->ConOut);

  while (TRUE) {
    if (FirstIndexRow != -1) {
      //
      // Incrementally update menu
      //
      if ((OldChosenEntry >= 0) && (OldChosenEntry != ChosenEntry)) {
        gST->ConOut->SetCursorPosition (gST->ConOut, 0, FirstIndexRow + OldChosenEntry);
        gST->ConOut->OutputString (gST->ConOut, L" ");
      }

      if (ChosenEntry >= 0) {
        EntryCursor = GetPickerEntryCursor (BootContext, TimeOutSeconds, ChosenEntry, ChosenEntry, PickerKeyInfo.OcModifiers);
      } else {
        EntryCursor = L'\0';
      }

      if ((OldChosenEntry != ChosenEntry) || (OldEntryCursor != EntryCursor)) {
        if (ChosenEntry >= 0) {
          gST->ConOut->SetCursorPosition (gST->ConOut, 0, FirstIndexRow + ChosenEntry);
          Code[0] = EntryCursor;
          gST->ConOut->OutputString (gST->ConOut, Code);
        }

        OldChosenEntry = ChosenEntry;
        OldEntryCursor = EntryCursor;

        gST->ConOut->SetCursorPosition (gST->ConOut, mStatusColumn, mStatusRow);
      }

      DEBUG_CODE_BEGIN ();
      if ((BootContext->PickerContext->PickerAttributes & OC_ATTR_SHOW_DEBUG_DISPLAY) != 0) {
        //
        // Varying part of milliseconds display
        //
        gST->ConOut->SetCursorPosition (gST->ConOut, MillisColumn, 0);
        DisplaySystemMs ();
        gST->ConOut->SetCursorPosition (gST->ConOut, mStatusColumn, mStatusRow);
      }

      DEBUG_CODE_END ();
    } else {
      //
      // Render initial menu
      //
      gST->ConOut->ClearScreen (gST->ConOut);
      //gST->ConOut->OutputString (gST->ConOut, OC_MENU_BOOT_MENU);

      if (BootContext->PickerContext->TitleSuffix != NULL) {
        Length = AsciiStrLen (BootContext->PickerContext->TitleSuffix);
        gST->ConOut->OutputString (gST->ConOut, L" (");
        for (Index = 0; Index < Length; ++Index) {
          Code[0] = BootContext->PickerContext->TitleSuffix[Index];
          gST->ConOut->OutputString (gST->ConOut, Code);
        }

        gST->ConOut->OutputString (gST->ConOut, L")");
      }

      DEBUG_CODE_BEGIN ();
      if ((BootContext->PickerContext->PickerAttributes & OC_ATTR_SHOW_DEBUG_DISPLAY) != 0) {
        //
        // Fixed part of milliseconds display
        //
        gST->ConOut->OutputString (gST->ConOut, L" [System uptime: ");
        MillisColumn = gST->ConOut->Mode->CursorColumn;
        DisplaySystemMs ();
      }

      DEBUG_CODE_END ();

      gST->ConOut->OutputString (gST->ConOut, L"\r\n");

      FirstIndexRow = gST->ConOut->Mode->CursorRow;

      for (Index = 0; Index < MIN (Count, OC_INPUT_MAX); ++Index) {
        EntryCursor = GetPickerEntryCursor (BootContext, TimeOutSeconds, ChosenEntry, Index, PickerKeyInfo.OcModifiers);

        if ((ChosenEntry >= 0) && ((UINTN)ChosenEntry == Index)) {
          OldEntryCursor = EntryCursor;
        }

        Code[0] = EntryCursor;
        gST->ConOut->OutputString (gST->ConOut, Code);
        gST->ConOut->OutputString (gST->ConOut, L" ");

        Code[0] = OC_INPUT_STR[Index];
        gST->ConOut->OutputString (gST->ConOut, Code);
        gST->ConOut->OutputString (gST->ConOut, L". ");
        gST->ConOut->OutputString (gST->ConOut, BootEntries[Index]->Name);
        if (BootEntries[Index]->IsExternal) {
          gST->ConOut->OutputString (gST->ConOut, OC_MENU_EXTERNAL);
        }

        if (BootEntries[Index]->IsFolder) {
          gST->ConOut->OutputString (gST->ConOut, OC_MENU_DISK_IMAGE);
        }

        //
        // Forcing cursor each row instead of using CRLF gives correct entry positioning
        // when we overflow horizontally (in very small modes) and enables writing entry
        // text (clipped or otherwise) up to the last column.
        //
        gST->ConOut->SetCursorPosition (gST->ConOut, 0, MIN (FirstIndexRow + Index + 1, Rows - 1));
      }

      if ((BootContext->PickerContext->PickerAttributes & OC_ATTR_USE_MINIMAL_UI) == 0) {
        gST->ConOut->OutputString (gST->ConOut, L"\r\n");

        ShutdownRestartRow = gST->ConOut->Mode->CursorRow;
        gST->ConOut->OutputString (gST->ConOut, L" ");

        if ((BootContext->PickerContext->PickerAttributes & OC_ATTR_USE_REVERSED_UI) == 0) {
          ShutdownColumn = gST->ConOut->Mode->CursorColumn;
          gST->ConOut->OutputString (gST->ConOut, L"|Shutdown|");
          gST->ConOut->OutputString (gST->ConOut, L"  ");
          RestartColumn = gST->ConOut->Mode->CursorColumn;
          gST->ConOut->OutputString (gST->ConOut, L"|Restart|");
        } else {
          RestartColumn = gST->ConOut->Mode->CursorColumn;
          gST->ConOut->OutputString (gST->ConOut, L"|Restart|");
          gST->ConOut->OutputString (gST->ConOut, L"  ");
          ShutdownColumn = gST->ConOut->Mode->CursorColumn;
          gST->ConOut->OutputString (gST->ConOut, L"|Shutdown|");
        }

        gST->ConOut->OutputString (gST->ConOut, L"\r\n");
      } else {
        //
        // prevent uninitialized err
        //
        ShutdownRestartRow = 0;
        RestartColumn      = 0;
        ShutdownColumn     = 0;
      }

      gST->ConOut->OutputString (gST->ConOut, L"\r\n");
      gST->ConOut->OutputString (gST->ConOut, OC_MENU_CHOOSE_OS);

      mStatusRow    = gST->ConOut->Mode->CursorRow;
      mStatusColumn = gST->ConOut->Mode->CursorColumn;

 #if defined (BUILTIN_DEMONSTRATE_TYPING)
      gST->ConOut->OutputString (gST->ConOut, L"\r\n\r\n");
      gST->ConOut->OutputString (gST->ConOut, L"Typing: ");
      TypingRow         = gST->ConOut->Mode->CursorRow;
      TypingColumn      = gST->ConOut->Mode->CursorColumn;
      TypingStartColumn = TypingColumn;
 #endif

      DEBUG_CODE_BEGIN ();
      if ((BootContext->PickerContext->PickerAttributes & OC_ATTR_SHOW_DEBUG_DISPLAY) != 0) {
        //
        // Parts of main debug display which do not need to reprint every frame
        // TODO: Could do more here
        //
        gST->ConOut->OutputString (gST->ConOut, L"\r\n\r\n");
        Print (
          L"mTscFrequency  = %,Lu\n",
          GetTscFrequency ()
          );
        gST->ConOut->SetCursorPosition (gST->ConOut, mStatusColumn, mStatusRow);
      }

      DEBUG_CODE_END ();
    }

    if (!PlayedOnce && BootContext->PickerContext->PickerAudioAssist) {
      OcPlayAudioFile (BootContext->PickerContext, OC_VOICE_OVER_AUDIO_FILE_CHOOSE_OS, OC_VOICE_OVER_AUDIO_BASE_TYPE_OPEN_CORE, FALSE);
      for (Index = 0; Index < Count; ++Index) {
        OcPlayAudioEntry (BootContext->PickerContext, BootEntries[Index]);
        if ((TimeOutSeconds > 0) && (BootContext->DefaultEntry->EntryIndex - 1 == Index)) {
          OcPlayAudioFile (BootContext->PickerContext, OC_VOICE_OVER_AUDIO_FILE_DEFAULT, OC_VOICE_OVER_AUDIO_BASE_TYPE_OPEN_CORE, FALSE);
        }
      }

      OcPlayAudioBeep (
        BootContext->PickerContext,
        OC_VOICE_OVER_SIGNALS_NORMAL,
        OC_VOICE_OVER_SIGNAL_NORMAL_MS,
        OC_VOICE_OVER_SILENCE_NORMAL_MS
        );
      PlayedOnce = TRUE;

      //
      // Avoid responding to possibly multiple keys pressed before menu is fully read out.
      //
      BootContext->PickerContext->HotKeyContext->FlushTypingBuffer (BootContext->PickerContext);
    }

    while (TRUE) {
      if (PlayChosen) {
        //
        // Pronounce entry name only after N ms of idleness.
        //
        KeyEndTime = BootContext->PickerContext->HotKeyContext->GetKeyWaitEndTime (OC_VOICE_OVER_IDLE_TIMEOUT_MS);
      } else if (TimeOutSeconds != 0) {
        if (KeyEndTime == 0) {
          KeyEndTime = BootContext->PickerContext->HotKeyContext->GetKeyWaitEndTime (TimeOutSeconds * 1000);
        }
      } else {
        KeyEndTime = 0;
      }

      ModifiersChanged = BootContext->PickerContext->HotKeyContext->WaitForKeyInfo (
                                                                      BootContext->PickerContext,
                                                                      KeyEndTime,
                                                                      (FocusList[FocusState] != TAB_PICKER)
          ? OC_PICKER_KEYS_FOR_TYPING
          : OC_PICKER_KEYS_FOR_PICKER,
                                                                      &PickerKeyInfo
                                                                      );

      if ((NumFocusList > 1) && (PickerKeyInfo.OcKeyCode == OC_INPUT_SWITCH_FOCUS)) {
        UpdateTabContext (
          FALSE,
          BootContext,
          BootEntries,
          FocusList[FocusState],
          ChosenEntry,
          OldEntryCursor,
 #if defined (BUILTIN_DEMONSTRATE_TYPING)
          TypingRow,
          TypingColumn,
 #endif
          FirstIndexRow,
          ShutdownRestartRow,
          ShutdownColumn,
          RestartColumn
          );

        //
        // On leaving picker the first time, any timeout gets cancelled (correctly), therefore text
        // cursor changes, therefore text cursor gets redrawn - unless we do this.
        //
        if ((FocusList[FocusState] == TAB_PICKER) && (TimeOutSeconds > 0)) {
          OldEntryCursor = GetPickerEntryCursor (BootContext, 0, ChosenEntry, ChosenEntry, PickerKeyInfo.OcModifiers);
        }

        if ((PickerKeyInfo.OcModifiers & OC_MODIFIERS_REVERSE_SWITCH_FOCUS) != 0) {
          if (FocusState == 0) {
            FocusState = NumFocusList;
          }

          FocusState--;
        } else {
          FocusState++;
          if (FocusState == NumFocusList) {
            FocusState = 0;
          }
        }

        UpdateTabContext (
          TRUE,
          BootContext,
          BootEntries,
          FocusList[FocusState],
          ChosenEntry,
          OldEntryCursor,
 #if defined (BUILTIN_DEMONSTRATE_TYPING)
          TypingRow,
          TypingColumn,
 #endif
          FirstIndexRow,
          ShutdownRestartRow,
          ShutdownColumn,
          RestartColumn
          );

        gST->ConOut->SetCursorPosition (gST->ConOut, mStatusColumn, mStatusRow);
      }

      if (FocusList[FocusState] == TAB_RESTART) {
        if (PickerKeyInfo.OcKeyCode == OC_INPUT_TYPING_CONFIRM) {
          gST->ConOut->OutputString (gST->ConOut, OC_MENU_RESTART);
          gST->ConOut->OutputString (gST->ConOut, L"\r\n");
          OcPlayAudioFile (BootContext->PickerContext, APPLE_VOICE_OVER_AUDIO_FILE_BEEP, OC_VOICE_OVER_AUDIO_BASE_TYPE_APPLE, FALSE);
          ResetWarm ();
          return EFI_SUCCESS;
        }
      } else if (FocusList[FocusState] == TAB_SHUTDOWN) {
        if (PickerKeyInfo.OcKeyCode == OC_INPUT_TYPING_CONFIRM) {
          gST->ConOut->OutputString (gST->ConOut, OC_MENU_SHUTDOWN);
          gST->ConOut->OutputString (gST->ConOut, L"\r\n");
          OcPlayAudioFile (BootContext->PickerContext, APPLE_VOICE_OVER_AUDIO_FILE_BEEP, OC_VOICE_OVER_AUDIO_BASE_TYPE_APPLE, FALSE);
          ResetShutdown ();
          return EFI_SUCCESS;
        }
      }

 #if defined (BUILTIN_DEMONSTRATE_TYPING)
      else if (FocusList[FocusState] == TAB_TYPING_DEMO) {
        if ((PickerKeyInfo.OcKeyCode == OC_INPUT_TYPING_BACKSPACE) && (TypingColumn > TypingStartColumn)) {
          //
          // Backspace and move cursor.
          //
          TypingColumn--;
          gST->ConOut->SetCursorPosition (gST->ConOut, TypingColumn, TypingRow);
          Code[0] = L'_';
          gST->ConOut->OutputString (gST->ConOut, Code);
          Code[0] = L' ';
          gST->ConOut->OutputString (gST->ConOut, Code);
          gST->ConOut->SetCursorPosition (gST->ConOut, mStatusColumn, mStatusRow);
        } else if ((PickerKeyInfo.UnicodeChar >= 32) && (PickerKeyInfo.UnicodeChar < 128)) {
          //
          // Type and move cursor.
          //
          gST->ConOut->SetCursorPosition (gST->ConOut, TypingColumn, TypingRow);
          Code[0] = PickerKeyInfo.UnicodeChar;
          gST->ConOut->OutputString (gST->ConOut, Code);
          Code[0] = L'_';
          gST->ConOut->OutputString (gST->ConOut, Code);
          gST->ConOut->SetCursorPosition (gST->ConOut, mStatusColumn, mStatusRow);
          TypingColumn++;
        }
      }
 #endif

      if (PlayChosen && (PickerKeyInfo.OcKeyCode == OC_INPUT_TIMEOUT)) {
        OcPlayAudioFile (BootContext->PickerContext, OC_VOICE_OVER_AUDIO_FILE_SELECTED, OC_VOICE_OVER_AUDIO_BASE_TYPE_OPEN_CORE, FALSE);
        OcPlayAudioEntry (BootContext->PickerContext, BootEntries[ChosenEntry]);
        PlayChosen = FALSE;
        continue;
      } else if (PickerKeyInfo.OcKeyCode == OC_INPUT_TIMEOUT) {
        *ChosenBootEntry = BootEntries[BootContext->DefaultEntry->EntryIndex - 1];
        gST->ConOut->OutputString (gST->ConOut, OC_MENU_TIMEOUT);
        gST->ConOut->OutputString (gST->ConOut, L"\r\n");
        OcPlayAudioFile (BootContext->PickerContext, OC_VOICE_OVER_AUDIO_FILE_TIMEOUT, OC_VOICE_OVER_AUDIO_BASE_TYPE_OPEN_CORE, FALSE);
        return EFI_SUCCESS;
      } else if (PickerKeyInfo.OcKeyCode == OC_INPUT_CONTINUE) {
        if (ChosenEntry >= 0) {
          *ChosenBootEntry = BootEntries[(UINTN)ChosenEntry];
        } else {
          *ChosenBootEntry = BootContext->DefaultEntry;
        }

        (*ChosenBootEntry)->SetDefault = ((PickerKeyInfo.OcModifiers & OC_MODIFIERS_SET_DEFAULT) != 0);
        gST->ConOut->OutputString (gST->ConOut, OC_MENU_OK);
        gST->ConOut->OutputString (gST->ConOut, L"\r\n");
        return EFI_SUCCESS;
      } else if (PickerKeyInfo.OcKeyCode == OC_INPUT_ABORTED) {
        gST->ConOut->OutputString (gST->ConOut, OC_MENU_RELOADING);
        gST->ConOut->OutputString (gST->ConOut, L"\r\n");
        OcPlayAudioFile (BootContext->PickerContext, OC_VOICE_OVER_AUDIO_FILE_RELOADING, OC_VOICE_OVER_AUDIO_BASE_TYPE_OPEN_CORE, FALSE);
        return EFI_ABORTED;
      } else if (PickerKeyInfo.OcKeyCode == OC_INPUT_MORE) {
        BootContext->PickerContext->HideAuxiliary = !BootContext->PickerContext->HideAuxiliary;
        return EFI_ABORTED;
        if (!BootContext->PickerContext->HideAuxiliary)
        {
        gST->ConOut->OutputString (gST->ConOut, OC_MENU_SHOW_AUXILIARY);
        gST->ConOut->OutputString (gST->ConOut, L"\r\n");
        OcPlayAudioFile (BootContext->PickerContext, OC_VOICE_OVER_AUDIO_FILE_SHOW_AUXILIARY, OC_VOICE_OVER_AUDIO_BASE_TYPE_OPEN_CORE, FALSE);
        BootContext->PickerContext->HideAuxiliary = FALSE;
        return EFI_ABORTED;}
      } else if (PickerKeyInfo.OcKeyCode == OC_INPUT_UP) {
        if (ChosenEntry < 0) {
          ChosenEntry = 0;
        } else if (ChosenEntry == 0) {
          ChosenEntry = (INTN)(MIN (Count, OC_INPUT_MAX) - 1);
        } else {
          --ChosenEntry;
        }

        TimeOutSeconds = 0;
        if (BootContext->PickerContext->PickerAudioAssist) {
          PlayChosen = TRUE;
        }

        break;
      } else if (PickerKeyInfo.OcKeyCode == OC_INPUT_DOWN) {
        if (ChosenEntry < 0) {
          ChosenEntry = 0;
        } else if (ChosenEntry == (INTN)(MIN (Count, OC_INPUT_MAX) - 1)) {
          ChosenEntry = 0;
        } else {
          ++ChosenEntry;
        }

        TimeOutSeconds = 0;
        if (BootContext->PickerContext->PickerAudioAssist) {
          PlayChosen = TRUE;
        }

        break;
      } else if ((PickerKeyInfo.OcKeyCode == OC_INPUT_TOP) || (PickerKeyInfo.OcKeyCode == OC_INPUT_LEFT)) {
        ChosenEntry    = 0;
        TimeOutSeconds = 0;
        if (BootContext->PickerContext->PickerAudioAssist) {
          PlayChosen = TRUE;
        }

        break;
      } else if ((PickerKeyInfo.OcKeyCode == OC_INPUT_BOTTOM) || (PickerKeyInfo.OcKeyCode == OC_INPUT_RIGHT)) {
        ChosenEntry    = (INTN)(MIN (Count, OC_INPUT_MAX) - 1);
        TimeOutSeconds = 0;
        if (BootContext->PickerContext->PickerAudioAssist) {
          PlayChosen = TRUE;
        }

        break;
      } else if (PickerKeyInfo.OcKeyCode == OC_INPUT_VOICE_OVER) {
        OcToggleVoiceOver (BootContext->PickerContext, NULL, NULL);
        break;
      } else if ((PickerKeyInfo.OcKeyCode != OC_INPUT_NO_ACTION) && (PickerKeyInfo.OcKeyCode >= 0) && ((UINTN)PickerKeyInfo.OcKeyCode < Count)) {
        *ChosenBootEntry               = BootEntries[PickerKeyInfo.OcKeyCode];
        (*ChosenBootEntry)->SetDefault = ((PickerKeyInfo.OcModifiers & OC_MODIFIERS_SET_DEFAULT) != 0);
        Code[0]                        = OC_INPUT_STR[PickerKeyInfo.OcKeyCode];
        gST->ConOut->OutputString (gST->ConOut, Code);
        gST->ConOut->OutputString (gST->ConOut, L"\r\n");
        return EFI_SUCCESS;
      }

      if ((ModifiersChanged || (PickerKeyInfo.OcKeyCode != OC_INPUT_NO_ACTION)) && (TimeOutSeconds > 0)) {
        OcPlayAudioFile (BootContext->PickerContext, OC_VOICE_OVER_AUDIO_FILE_ABORT_TIMEOUT, OC_VOICE_OVER_AUDIO_BASE_TYPE_OPEN_CORE, FALSE);
        TimeOutSeconds = 0;
        break;
      }

      if (ModifiersChanged || (PickerKeyInfo.UnicodeChar != CHAR_NULL)) {
        break;
      }
    }
  }

  ASSERT (FALSE);
}

EFI_STATUS
EFIAPI
OcShowSimplePasswordRequest (
  IN OC_PICKER_CONTEXT   *Context,
  IN OC_PRIVILEGE_LEVEL  Level
  )
{
  BOOLEAN  Result;

  UINT8   Password[OC_PASSWORD_MAX_LEN];
  UINT32  PwIndex;

  UINT8               Index;
  OC_PICKER_KEY_INFO  PickerKeyInfo;
  UINT8               SpaceIndex;

  OcConsoleControlSetMode (EfiConsoleControlScreenText);
  gST->ConOut->EnableCursor (gST->ConOut, FALSE);
  gST->ConOut->ClearScreen (gST->ConOut);

  for (Index = 0; Index < OC_PASSWORD_MAX_RETRIES; ++Index) {
    PwIndex = 0;

    Context->HotKeyContext->FlushTypingBuffer (Context);

    gST->ConOut->OutputString (gST->ConOut, OC_MENU_PASSWORD_REQUEST);
    OcPlayAudioFile (Context, OC_VOICE_OVER_AUDIO_FILE_ENTER_PASSWORD, OC_VOICE_OVER_AUDIO_BASE_TYPE_OPEN_CORE, TRUE);

    while (TRUE) {
      Context->HotKeyContext->WaitForKeyInfo (
                                Context,
                                0,
                                OC_PICKER_KEYS_FOR_TYPING,
                                &PickerKeyInfo
                                );

      if (PickerKeyInfo.OcKeyCode == OC_INPUT_VOICE_OVER) {
        OcToggleVoiceOver (Context, OC_VOICE_OVER_AUDIO_FILE_ENTER_PASSWORD, OC_VOICE_OVER_AUDIO_BASE_TYPE_OPEN_CORE);
        continue;
      }

      if (PickerKeyInfo.UnicodeChar == CHAR_CARRIAGE_RETURN) {
        //
        // RETURN finalizes the input.
        //
        break;
      }

      if (PickerKeyInfo.UnicodeChar == CHAR_BACKSPACE) {
        //
        // Delete the last entered character, if such exists.
        //
        if (PwIndex > 0) {
          --PwIndex;
          Password[PwIndex] = 0;
          //
          // Overwrite current character with a space.
          //
          gST->ConOut->SetCursorPosition (
                         gST->ConOut,
                         gST->ConOut->Mode->CursorColumn - 1,
                         gST->ConOut->Mode->CursorRow
                         );
          gST->ConOut->OutputString (gST->ConOut, L" ");
          gST->ConOut->SetCursorPosition (
                         gST->ConOut,
                         gST->ConOut->Mode->CursorColumn - 1,
                         gST->ConOut->Mode->CursorRow
                         );
        }

        OcPlayAudioFile (Context, APPLE_VOICE_OVER_AUDIO_FILE_BEEP, OC_VOICE_OVER_AUDIO_BASE_TYPE_APPLE, TRUE);
        continue;
      }

      if (  (PickerKeyInfo.UnicodeChar != CHAR_NULL)
         && (PickerKeyInfo.UnicodeChar == (CHAR8)PickerKeyInfo.UnicodeChar)
         && (PwIndex < ARRAY_SIZE (Password)))
      {
        gST->ConOut->OutputString (gST->ConOut, L"*");
        Password[PwIndex] = (UINT8)PickerKeyInfo.UnicodeChar;
        OcPlayAudioFile (Context, APPLE_VOICE_OVER_AUDIO_FILE_BEEP, OC_VOICE_OVER_AUDIO_BASE_TYPE_APPLE, TRUE);
        ++PwIndex;
        continue;
      }

      //
      // Only ASCII characters are supported.
      //
      OcPlayAudioBeep (
        Context,
        OC_VOICE_OVER_SIGNALS_ERROR,
        OC_VOICE_OVER_SIGNAL_ERROR_MS,
        OC_VOICE_OVER_SILENCE_ERROR_MS
        );
    }

    //
    // Output password processing status.
    //
    gST->ConOut->SetCursorPosition (
                   gST->ConOut,
                   0,
                   gST->ConOut->Mode->CursorRow
                   );
    gST->ConOut->OutputString (gST->ConOut, OC_MENU_PASSWORD_PROCESSING);
    //
    // Clear remaining password prompt status.
    //
    for (
         SpaceIndex = L_STR_LEN (OC_MENU_PASSWORD_PROCESSING);
         SpaceIndex < L_STR_LEN (OC_MENU_PASSWORD_REQUEST) + PwIndex;
         ++SpaceIndex
         )
    {
      gST->ConOut->OutputString (gST->ConOut, L" ");
    }

    Result = Context->VerifyPassword (
                        Password,
                        PwIndex,
                        Context->PrivilegeContext
                        );

    SecureZeroMem (Password, PwIndex);
    //
    // Clear password processing status.
    //
    gST->ConOut->SetCursorPosition (
                   gST->ConOut,
                   0,
                   gST->ConOut->Mode->CursorRow
                   );
    for (SpaceIndex = 0; SpaceIndex < L_STR_LEN (OC_MENU_PASSWORD_PROCESSING); ++SpaceIndex) {
      gST->ConOut->OutputString (gST->ConOut, L" ");
    }

    gST->ConOut->SetCursorPosition (
                   gST->ConOut,
                   0,
                   gST->ConOut->Mode->CursorRow
                   );

    if (Result) {
      OcPlayAudioFile (Context, OC_VOICE_OVER_AUDIO_FILE_PASSWORD_ACCEPTED, OC_VOICE_OVER_AUDIO_BASE_TYPE_OPEN_CORE, TRUE);
      return EFI_SUCCESS;
    }

    OcPlayAudioFile (Context, OC_VOICE_OVER_AUDIO_FILE_PASSWORD_INCORRECT, OC_VOICE_OVER_AUDIO_BASE_TYPE_OPEN_CORE, TRUE);
  }

  gST->ConOut->OutputString (gST->ConOut, OC_MENU_PASSWORD_RETRY_LIMIT L"\r\n");
  OcPlayAudioFile (Context, OC_VOICE_OVER_AUDIO_FILE_PASSWORD_RETRY_LIMIT, OC_VOICE_OVER_AUDIO_BASE_TYPE_OPEN_CORE, TRUE);
  DEBUG ((DEBUG_WARN, "OCB: User failed to verify password %d times running\n", OC_PASSWORD_MAX_RETRIES));

  gBS->Stall (SECONDS_TO_MICROSECONDS (5));
  ResetWarm ();

  return EFI_ACCESS_DENIED;
}
