/*++

Copyright (c) 2006, Intel Corporation                                                         
All rights reserved. This program and the accompanying materials                          
are licensed and made available under the terms and conditions of the BSD License         
which accompanies this distribution.  The full text of the license may be found at        
http://opensource.org/licenses/bsd-license.php                                            
                                                                                          
THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,                     
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.             

Module Name:

  InputHandler.C

Abstract:
  
  Implementation for handling user input from the User Interface

Revision History

--*/

#include "Setup.h"
#include "Ui.h"
#include "Colors.h"

#ifndef EFI_MAX
#define EFI_MAX(_a, _b) ((_a) > (_b) ? (_a) : (_b))
#endif

EFI_STATUS
ReadString (
  IN  UI_MENU_OPTION              *MenuOption,
  OUT CHAR16                      *StringPtr
  )
{
  EFI_STATUS    Status;
  EFI_INPUT_KEY Key;
  CHAR16        NullCharacter;
  UINTN         ScreenSize;
  EFI_TAG       *Tag;
  CHAR16        Space[2];
  CHAR16        KeyPad[2];
  BOOLEAN       SelectionComplete;
  CHAR16        *TempString;
  CHAR16        *BufferedString;
  UINTN         Index;
  UINTN         Count;
  UINTN         Start;
  UINTN         Top;
  CHAR16        *PromptForDataString;
  UINTN         DimensionsWidth;
  UINTN         DimensionsHeight;
  BOOLEAN       CursorVisible;

  DimensionsWidth     = gScreenDimensions.RightColumn - gScreenDimensions.LeftColumn;
  DimensionsHeight    = gScreenDimensions.BottomRow - gScreenDimensions.TopRow;

  PromptForDataString = GetToken (STRING_TOKEN (PROMPT_FOR_DATA), gHiiHandle);

  NullCharacter       = CHAR_NULL;
  ScreenSize          = GetStringWidth (PromptForDataString) / 2;
  Tag                 = MenuOption->ThisTag;
  Space[0]            = L' ';
  Space[1]            = CHAR_NULL;
  SelectionComplete   = FALSE;

  TempString          = AllocateZeroPool (MenuOption->ThisTag->Maximum * 2);
  ASSERT (TempString);

  if (ScreenSize < (Tag->Maximum / (UINTN) 2)) {
    ScreenSize = Tag->Maximum / 2;
  }

  if ((ScreenSize + 2) > DimensionsWidth) {
    ScreenSize = DimensionsWidth - 2;
  }

  BufferedString = AllocateZeroPool (ScreenSize * 2);
  ASSERT (BufferedString);

  Start = (DimensionsWidth - ScreenSize - 2) / 2 + gScreenDimensions.LeftColumn + 1;
  Top   = ((DimensionsHeight - 6) / 2) + gScreenDimensions.TopRow - 1;

  //
  // Display prompt for string
  //
  CreatePopUp (ScreenSize, 4, &NullCharacter, PromptForDataString, Space, &NullCharacter);

  gBS->FreePool (PromptForDataString);

  gST->ConOut->SetAttribute (gST->ConOut, EFI_TEXT_ATTR (EFI_BLACK, EFI_LIGHTGRAY));

  CursorVisible = gST->ConOut->Mode->CursorVisible;
  gST->ConOut->EnableCursor (gST->ConOut, TRUE);

  do {
    Status = WaitForKeyStroke (&Key);

    gST->ConOut->SetAttribute (gST->ConOut, EFI_TEXT_ATTR (EFI_BLACK, EFI_LIGHTGRAY));
    switch (Key.UnicodeChar) {
    case CHAR_NULL:
      switch (Key.ScanCode) {
      case SCAN_LEFT:
        break;

      case SCAN_RIGHT:
        break;

      case SCAN_ESC:
        gBS->FreePool (TempString);
        gBS->FreePool (BufferedString);
        gST->ConOut->SetAttribute (gST->ConOut, EFI_TEXT_ATTR (EFI_LIGHTGRAY, EFI_BLACK));
        gST->ConOut->EnableCursor (gST->ConOut, CursorVisible);
        return EFI_DEVICE_ERROR;

      default:
        break;
      }

      break;

    case CHAR_CARRIAGE_RETURN:
      if (GetStringWidth (StringPtr) >= MenuOption->ThisTag->Minimum) {
        SelectionComplete = TRUE;
        gBS->FreePool (TempString);
        gBS->FreePool (BufferedString);
        gST->ConOut->SetAttribute (gST->ConOut, EFI_TEXT_ATTR (EFI_LIGHTGRAY, EFI_BLACK));
        gST->ConOut->EnableCursor (gST->ConOut, CursorVisible);
        return EFI_SUCCESS;
      } else {
        ScreenSize = GetStringWidth (gMiniString) / 2;
        CreatePopUp (ScreenSize, 4, &NullCharacter, gMiniString, gPressEnter, &NullCharacter);
        //
        // Simply create a popup to tell the user that they had typed in too few characters.
        // To save code space, we can then treat this as an error and return back to the menu.
        //
        do {
          Status = WaitForKeyStroke (&Key);
        } while (Key.UnicodeChar != CHAR_CARRIAGE_RETURN);
        gBS->FreePool (TempString);
        gBS->FreePool (BufferedString);
        gST->ConOut->SetAttribute (gST->ConOut, EFI_TEXT_ATTR (EFI_LIGHTGRAY, EFI_BLACK));
        gST->ConOut->EnableCursor (gST->ConOut, CursorVisible);
        return EFI_DEVICE_ERROR;
      }

      break;

    case CHAR_BACKSPACE:
      if (StringPtr[0] != CHAR_NULL) {
        for (Index = 0; StringPtr[Index] != CHAR_NULL; Index++) {
          TempString[Index] = StringPtr[Index];
        }
        //
        // Effectively truncate string by 1 character
        //
        TempString[Index - 1] = CHAR_NULL;
        StrCpy (StringPtr, TempString);
      }

    default:
      //
      // If it is the beginning of the string, don't worry about checking maximum limits
      //
      if ((StringPtr[0] == CHAR_NULL) && (Key.UnicodeChar != CHAR_BACKSPACE)) {
        StrnCpy (StringPtr, &Key.UnicodeChar, 1);
        StrnCpy (TempString, &Key.UnicodeChar, 1);
      } else if ((GetStringWidth (StringPtr) < MenuOption->ThisTag->Maximum) && (Key.UnicodeChar != CHAR_BACKSPACE)) {
        KeyPad[0] = Key.UnicodeChar;
        KeyPad[1] = CHAR_NULL;
        StrCat (StringPtr, KeyPad);
        StrCat (TempString, KeyPad);
      }
      //
      // If the width of the input string is now larger than the screen, we nee to
      // adjust the index to start printing portions of the string
      //
      SetUnicodeMem (BufferedString, ScreenSize - 1, L' ');

      PrintStringAt (Start + 1, Top + 3, BufferedString);

      if ((GetStringWidth (StringPtr) / 2) > (DimensionsWidth - 2)) {
        Index = (GetStringWidth (StringPtr) / 2) - DimensionsWidth + 2;
      } else {
        Index = 0;
      }

      for (Count = 0; Index + 1 < GetStringWidth (StringPtr) / 2; Index++, Count++) {
        BufferedString[Count] = StringPtr[Index];
      }

      PrintStringAt (Start + 1, Top + 3, BufferedString);
      break;
    }

    gST->ConOut->SetAttribute (gST->ConOut, EFI_TEXT_ATTR (EFI_LIGHTGRAY, EFI_BLACK));
    gST->ConOut->SetCursorPosition (gST->ConOut, Start + GetStringWidth (StringPtr) / 2, Top + 3);
  } while (!SelectionComplete);
  gST->ConOut->SetAttribute (gST->ConOut, EFI_TEXT_ATTR (EFI_LIGHTGRAY, EFI_BLACK));
  gST->ConOut->EnableCursor (gST->ConOut, CursorVisible);
  return Status;
}

EFI_STATUS
ReadPassword (
  IN  UI_MENU_OPTION              *MenuOption,
  IN  BOOLEAN                     PromptForPassword,
  IN  EFI_TAG                     *Tag,
  IN  EFI_IFR_DATA_ARRAY          *PageData,
  IN  BOOLEAN                     SecondEntry,
  IN  EFI_FILE_FORM_TAGS          *FileFormTags,
  OUT CHAR16                      *StringPtr
  )
{
  EFI_STATUS                  Status;
  UINTN                       ScreenSize;
  CHAR16                      NullCharacter;
  CHAR16                      Space[2];
  EFI_INPUT_KEY               Key;
  CHAR16                      KeyPad[2];
  UINTN                       Index;
  UINTN                       Start;
  UINTN                       Top;
  CHAR16                      *TempString;
  CHAR16                      *TempString2;
  BOOLEAN                     Confirmation;
  BOOLEAN                     ConfirmationComplete;
  EFI_HII_CALLBACK_PACKET     *Packet;
  EFI_FORM_CALLBACK_PROTOCOL  *FormCallback;
  EFI_VARIABLE_DEFINITION     *VariableDefinition;
  UINTN                       DimensionsWidth;
  UINTN                       DimensionsHeight;
  EFI_IFR_DATA_ENTRY          *DataEntry;
  UINTN                       WidthOfString;

  DimensionsWidth       = gScreenDimensions.RightColumn - gScreenDimensions.LeftColumn;
  DimensionsHeight      = gScreenDimensions.BottomRow - gScreenDimensions.TopRow;

  VariableDefinition    = NULL;
  NullCharacter         = CHAR_NULL;
  Space[0]              = L' ';
  Space[1]              = CHAR_NULL;
  Confirmation          = FALSE;
  ConfirmationComplete  = FALSE;
  Status                = EFI_SUCCESS;
  FormCallback          = NULL;
  Packet                = NULL;

  //
  // Remember that dynamic pages in an environment where all pages are not
  // dynamic require us to call back to the user to give them an opportunity
  // to register fresh information in the HII database so that we can extract it.
  //
  Status = gBS->HandleProtocol (
                  (VOID *) (UINTN) MenuOption->Tags[0].CallbackHandle,
                  &gEfiFormCallbackProtocolGuid,
                  (VOID **) &FormCallback
                  );

  TempString  = AllocateZeroPool (MenuOption->ThisTag->Maximum * 2);
  TempString2 = AllocateZeroPool (MenuOption->ThisTag->Maximum * 2);

  ASSERT (TempString);
  ASSERT (TempString2);

  if (Tag->Flags & EFI_IFR_FLAG_INTERACTIVE) {
    //
    // Password requires a callback to determine if a password exists
    //
    DataEntry = (EFI_IFR_DATA_ENTRY *) (PageData + 1);
    DataEntry->OpCode  = EFI_IFR_PASSWORD_OP;
    DataEntry->Length  = 3;

    ExtractRequestedNvMap (FileFormTags, Tag->VariableNumber, &VariableDefinition);

    //
    // The user is about to be prompted with a password field, Data = 0 (Return Status determines the type of prompt)
    //
    DataEntry->Data  = (VOID *) (UINTN) (UINT8) (0 + SecondEntry * 2);
    PageData->NvRamMap    = VariableDefinition->NvRamMap;

    if ((FormCallback != NULL) && (FormCallback->Callback != NULL)) {
      Status = FormCallback->Callback (
                              FormCallback,
                              Tag->Key,
                              PageData,
                              &Packet
                              );
    }
    //
    // If error on return, continue with the reading of a typed in password to verify user knows password
    // If no error, there is no password set, so prompt for new password
    // if the previous callback was to verify the user knew password, and user typed it correctly - should return no error
    //
    if (!EFI_ERROR (Status)) {
      PromptForPassword = FALSE;

      //
      // Simulate this as the second entry into this routine for an interactive behavior
      //
      SecondEntry = TRUE;
    } else if (Status == EFI_NOT_READY) {
Error:
      if (Packet != NULL) {
        //
        // Upon error, we will likely receive a string to print out
        // Display error popup
        //
        WidthOfString = GetStringWidth (Packet->String);
        ScreenSize = EFI_MAX(WidthOfString, GetStringWidth (gPressEnter)) / 2;
        CreatePopUp (ScreenSize, 4, &NullCharacter, Packet->String, gPressEnter, &NullCharacter);
        gBS->FreePool (Packet);

        do {
          Status = WaitForKeyStroke (&Key);
        } while (Key.UnicodeChar != CHAR_CARRIAGE_RETURN);
      }

      Status = EFI_NOT_READY;
      goto Done;
    }
  }

  do {
    //
    // Display PopUp Screen
    //
    ScreenSize = GetStringWidth (gPromptForNewPassword) / 2;
    if (GetStringWidth (gConfirmPassword) / 2 > ScreenSize) {
      ScreenSize = GetStringWidth (gConfirmPassword) / 2;
    }

    Start = (DimensionsWidth - ScreenSize - 4) / 2 + gScreenDimensions.LeftColumn + 2;
    Top   = ((DimensionsHeight - 6) / 2) + gScreenDimensions.TopRow - 1;

    if (!Confirmation) {
      if (PromptForPassword) {
        CreatePopUp (ScreenSize, 4, &NullCharacter, gPromptForPassword, Space, &NullCharacter);
      } else {
        CreatePopUp (ScreenSize, 4, &NullCharacter, gPromptForNewPassword, Space, &NullCharacter);
      }
    } else {
      CreatePopUp (ScreenSize, 4, &NullCharacter, gConfirmPassword, Space, &NullCharacter);
      StringPtr[0] = CHAR_NULL;
    }

    do {
      Status = WaitForKeyStroke (&Key);

      switch (Key.UnicodeChar) {
      case CHAR_NULL:
        if (Key.ScanCode == SCAN_ESC) {
          return EFI_NOT_READY;
        }

        ConfirmationComplete = FALSE;
        break;

      case CHAR_CARRIAGE_RETURN:
        if (Tag->Flags & EFI_IFR_FLAG_INTERACTIVE) {
          //
          // User just typed a string in
          //
          DataEntry = (EFI_IFR_DATA_ENTRY *) (PageData + 1);
          DataEntry->OpCode = EFI_IFR_PASSWORD_OP;

          //
          // If the user just typed in a password, Data = 1
          // If the user just typed in a password to confirm the previous password, Data = 2
          //
          if (!Confirmation) {
            DataEntry->Length  = 3;
            DataEntry->Data    = (VOID *) (UINTN) (UINT8) (1 + SecondEntry * 2);

            if ((FormCallback != NULL) && (FormCallback->Callback != NULL)) {
              Status = FormCallback->Callback (
                                      FormCallback,
                                      Tag->Key,
                                      PageData,
                                      &Packet
                                      );
            }

            DataEntry->Length  = sizeof (EFI_IFR_DATA_ENTRY);
            DataEntry->Data    = (VOID *) TempString;
          } else {
            DataEntry->Length  = 3;
            DataEntry->Data    = (VOID *) (UINTN) (UINT8) (2 + SecondEntry * 2);

            if ((FormCallback != NULL) && (FormCallback->Callback != NULL)) {
              Status = FormCallback->Callback (
                                      FormCallback,
                                      Tag->Key,
                                      PageData,
                                      &Packet
                                      );
            }

            DataEntry->Length  = sizeof (EFI_IFR_DATA_ENTRY);
            DataEntry->Data    = (VOID *) TempString2;
          }

          if ((FormCallback != NULL) && (FormCallback->Callback != NULL)) {
            Status = FormCallback->Callback (
                                    FormCallback,
                                    Tag->Key,
                                    PageData,
                                    &Packet
                                    );
          }
          //
          // If this was the confirmation round of callbacks
          // and an error comes back, display an error
          //
          if (Confirmation) {
            if (EFI_ERROR (Status)) {
              if (Packet->String == NULL) {
                WidthOfString = GetStringWidth (gConfirmError);
                ScreenSize = EFI_MAX (WidthOfString, GetStringWidth (gPressEnter)) / 2;
                CreatePopUp (ScreenSize, 4, &NullCharacter, gConfirmError, gPressEnter, &NullCharacter);
              } else {
                WidthOfString = GetStringWidth (Packet->String);
                ScreenSize = EFI_MAX (WidthOfString, GetStringWidth (gPressEnter)) / 2;
                CreatePopUp (ScreenSize, 4, &NullCharacter, Packet->String, gPressEnter, &NullCharacter);
                gBS->FreePool (Packet);
              }

              StringPtr[0] = CHAR_NULL;
              do {
                Status = WaitForKeyStroke (&Key);

                if (Key.UnicodeChar == CHAR_CARRIAGE_RETURN) {
                  Status = EFI_NOT_READY;
                  goto Done;
                }
              } while (1);
            } else {
                Status = EFI_NOT_READY;
                goto Done;
            }
          } else {
            //
            // User typed a string in and it wasn't valid somehow from the callback
            // For instance, callback may have said that some invalid characters were contained in the string
            //
            if (Status == EFI_NOT_READY) {
              goto Error;
            }

            if (PromptForPassword && EFI_ERROR (Status)) {
              Status = EFI_DEVICE_ERROR;
              goto Done;
            }
          }
        }

        if (Confirmation) {
          //
          // Compare tempstring and tempstring2, if the same, return with StringPtr success
          // Otherwise, kick and error box, and return an error
          //
          if (StrCmp (TempString, TempString2) == 0) {
            Status = EFI_SUCCESS;
            goto Done;
          } else {
            WidthOfString = GetStringWidth (gConfirmError);
            ScreenSize = EFI_MAX (WidthOfString, GetStringWidth (gPressEnter)) / 2;
            CreatePopUp (ScreenSize, 4, &NullCharacter, gConfirmError, gPressEnter, &NullCharacter);
            StringPtr[0] = CHAR_NULL;
            do {
              Status = WaitForKeyStroke (&Key);
              if (Key.UnicodeChar == CHAR_CARRIAGE_RETURN) {
                Status = EFI_DEVICE_ERROR;
                goto Done;
              }
            } while (1);
          }
        }

        if (PromptForPassword) {
          //
          // I was asked for a password, return it back in StringPtr
          //
          Status = EFI_SUCCESS;
          goto Done;
        } else {
          //
          // If the two passwords were not the same kick an error popup
          //
          Confirmation          = TRUE;
          ConfirmationComplete  = TRUE;
          break;
        }

      case CHAR_BACKSPACE:
        if (StringPtr[0] != CHAR_NULL) {
          if (!Confirmation) {
            for (Index = 0; StringPtr[Index] != CHAR_NULL; Index++) {
              TempString[Index] = StringPtr[Index];
            }
            //
            // Effectively truncate string by 1 character
            //
            TempString[Index - 1] = CHAR_NULL;
            StrCpy (StringPtr, TempString);
          } else {
            for (Index = 0; StringPtr[Index] != CHAR_NULL; Index++) {
              TempString2[Index] = StringPtr[Index];
            }
            //
            // Effectively truncate string by 1 character
            //
            TempString2[Index - 1] = CHAR_NULL;
            StrCpy (StringPtr, TempString2);
          }

          ConfirmationComplete = FALSE;
        } else {
          ConfirmationComplete = FALSE;
        }

      //
      // Must be a character we are interested in!
      //
      default:
        if ((StringPtr[0] == CHAR_NULL) && (Key.UnicodeChar != CHAR_BACKSPACE)) {
          if (!Confirmation) {
            StrnCpy (StringPtr, &Key.UnicodeChar, 1);
            StrnCpy (TempString, &Key.UnicodeChar, 1);
          } else {
            StrnCpy (StringPtr, &Key.UnicodeChar, 1);
            StrnCpy (TempString2, &Key.UnicodeChar, 1);
            ConfirmationComplete = FALSE;
          }
        } else if ((GetStringWidth (StringPtr) / 2 <= (UINTN) (MenuOption->ThisTag->Maximum - 1) / 2) &&
                 (Key.UnicodeChar != CHAR_BACKSPACE)
                ) {
          KeyPad[0] = Key.UnicodeChar;
          KeyPad[1] = CHAR_NULL;
          if (!Confirmation) {
            StrCat (StringPtr, KeyPad);
            StrCat (TempString, KeyPad);
          } else {
            StrCat (StringPtr, KeyPad);
            StrCat (TempString2, KeyPad);
          }
        }

        gST->ConOut->SetAttribute (gST->ConOut, EFI_TEXT_ATTR (EFI_BLACK, EFI_LIGHTGRAY));
        for (Index = 1; Index < ScreenSize; Index++) {
          PrintCharAt (Start + Index, Top + 3, L' ');
        }

        gST->ConOut->SetCursorPosition (
                      gST->ConOut,
                      (DimensionsWidth - GetStringWidth (StringPtr) / 2) / 2 + gScreenDimensions.LeftColumn,
                      Top + 3
                      );
        for (Index = 0; Index + 1 < GetStringWidth (StringPtr) / 2; Index++) {
          PrintChar (L'*');
        }

        gST->ConOut->SetAttribute (gST->ConOut, EFI_TEXT_ATTR (EFI_LIGHTGRAY, EFI_BLACK));
        break;
      }
      //
      // end switch
      //
    } while (!ConfirmationComplete);

  } while (1);

Done:
  gBS->FreePool (TempString);
  gBS->FreePool (TempString2);
  return Status;
}

VOID
EncodePassword (
  IN  CHAR16                      *Password,
  IN  UINT8                       MaxSize
  )
{
  UINTN   Index;
  UINTN   Loop;
  CHAR16  *Buffer;
  CHAR16  *Key;

  Key     = (CHAR16 *) L"MAR10648567";
  Buffer  = AllocateZeroPool (MaxSize);

  ASSERT (Buffer);

  for (Index = 0; Key[Index] != 0; Index++) {
    for (Loop = 0; Loop < (UINT8) (MaxSize / 2); Loop++) {
      Buffer[Loop] = (CHAR16) (Password[Loop] ^ Key[Index]);
    }
  }

  CopyMem (Password, Buffer, MaxSize);

  gBS->FreePool (Buffer);
  return ;
}

EFI_STATUS
GetNumericInput (
  IN  UI_MENU_OPTION              *MenuOption,
  IN  EFI_FILE_FORM_TAGS          *FileFormTagsHead,
  IN  BOOLEAN                     ManualInput,
  IN  EFI_TAG                     *Tag,
  IN  UINTN                       NumericType,
  OUT UINT16                      *Value
  )
/*++

Routine Description:

  This routine reads a numeric value from the user input.  

Arguments:

  MenuOption       -  Pointer to the current input menu.

  FileFormTagsHead -  Pointer to the root of formset.

  ManualInput      -  If the input is manual or not.

  Tag              -  Pointer to all the attributes and values associated with a tag.
                 
  Value            -  Pointer to the numeric value that is going to be read.

Returns: 

  EFI_SUCCESS       - If numerical input is read successfully
  EFI_DEVICE_ERROR  - If operation fails
  
--*/
{
  EFI_INPUT_KEY           Key;
  BOOLEAN                 SelectionComplete;
  UINTN                   Column;
  UINTN                   Row;
  CHAR16                  FormattedNumber[6];
  UINTN                   PreviousNumber[6];
  INTN                    Number;
  UINTN                   Count;
  UINT16                  BackupValue;
  STRING_REF              PopUp;
  CHAR16                  NullCharacter;
  CHAR16                  *StringPtr;
  EFI_FILE_FORM_TAGS      *FileFormTags;
  EFI_VARIABLE_DEFINITION *VariableDefinition;
  UINTN                   Loop;

  NullCharacter     = CHAR_NULL;
  StringPtr         = NULL;
  Column            = MenuOption->OptCol;
  Row               = MenuOption->Row;
  Number            = 0;
  PreviousNumber[0] = 0;
  Count             = 0;
  SelectionComplete = FALSE;
  BackupValue       = Tag->Value;
  FileFormTags      = FileFormTagsHead;

  if (ManualInput) {
    PrintAt (Column, Row, (CHAR16 *) L"[     ]");
    Column++;
    if (Tag->Operand != EFI_IFR_TIME_OP) {
      *Value = BackupValue;
    }
  }
  //
  // First time we enter this handler, we need to check to see if
  // we were passed an increment or decrement directive
  //
  do {
    Key.UnicodeChar = CHAR_NULL;
    if (gDirection != 0) {
      Key.ScanCode  = gDirection;
      gDirection    = 0;
      goto TheKey2;
    }

    WaitForKeyStroke (&Key);

TheKey2:
    switch (Key.UnicodeChar) {
    case '+':
    case '-':
      if ((Tag->Operand == EFI_IFR_DATE_OP) || (Tag->Operand == EFI_IFR_TIME_OP)) {
        Key.UnicodeChar = CHAR_NULL;
        if (Key.UnicodeChar == '+') {
          Key.ScanCode = SCAN_RIGHT;
        } else {
          Key.ScanCode = SCAN_LEFT;
        }

        goto TheKey2;
      }
      break;

    case CHAR_NULL:
      switch (Key.ScanCode) {
      case SCAN_LEFT:
      case SCAN_RIGHT:
        if ((Tag->Operand == EFI_IFR_DATE_OP) || (Tag->Operand == EFI_IFR_TIME_OP)) {
          //
          // By setting this value, we will return back to the caller.
          // We need to do this since an auto-refresh will destroy the adjustment
          // based on what the real-time-clock is showing.  So we always commit
          // upon changing the value.
          //
          gDirection = SCAN_DOWN;
        }

        if (!ManualInput) {
          Tag->Value = *Value;
          if (Key.ScanCode == SCAN_LEFT) {
            Number = *Value - Tag->Step;
            if (Number < Tag->Minimum) {
              Number = Tag->Minimum;
            }
          } else if (Key.ScanCode == SCAN_RIGHT) {
            Number = *Value + Tag->Step;
            if (Number > Tag->Maximum) {
              Number = Tag->Maximum;
            }
          }

          Tag->Value  = (UINT16) Number;
          *Value      = (UINT16) Number;
          UnicodeValueToString (
            FormattedNumber, 
            FALSE, 
            (UINTN) Number, 
            (sizeof (FormattedNumber) / sizeof (FormattedNumber[0]))
            );
          Number = (UINT16) GetStringWidth (FormattedNumber);

          gST->ConOut->SetAttribute (gST->ConOut, FIELD_TEXT | FIELD_BACKGROUND);
          if ((Tag->Operand == EFI_IFR_DATE_OP) || (Tag->Operand == EFI_IFR_TIME_OP)) {
            for (Loop = 0; Loop < (UINTN) ((Number >= 8) ? 4 : 2); Loop++) {
              PrintAt (MenuOption->OptCol + Loop, MenuOption->Row, (CHAR16 *) L" ");
            }
          } else {
            for (Loop = 0; Loop < gOptionBlockWidth; Loop++) {
              PrintAt (MenuOption->OptCol + Loop, MenuOption->Row, (CHAR16 *) L" ");
            }
          }

          gST->ConOut->SetAttribute (gST->ConOut, FIELD_TEXT_HIGHLIGHT | FIELD_BACKGROUND_HIGHLIGHT);

          if ((MenuOption->Col + gPromptBlockWidth + 1) == MenuOption->OptCol) {
            PrintCharAt (MenuOption->OptCol, Row, LEFT_NUMERIC_DELIMITER);
            Column = MenuOption->OptCol + 1;
          }
          //
          // If Number looks like "3", convert it to "03/"
          //
          if (Number == 4 && (NumericType == DATE_NUMERIC)) {
            FormattedNumber[3]  = FormattedNumber[1];
            FormattedNumber[2]  = DATE_SEPARATOR;
            FormattedNumber[1]  = FormattedNumber[0];
            FormattedNumber[0]  = L'0';
            Number              = 8;
          }
          //
          // If Number looks like "13", convert it to "13/"
          //
          if (Number == 6 && (NumericType == DATE_NUMERIC)) {
            FormattedNumber[3]  = FormattedNumber[2];
            FormattedNumber[2]  = DATE_SEPARATOR;
            Number              = 8;
          }

          if (Number == 4 &&
              (NumericType == TIME_NUMERIC) &&
              (MenuOption->Col + gPromptBlockWidth + 8) != MenuOption->OptCol
              ) {
            FormattedNumber[3]  = FormattedNumber[1];
            FormattedNumber[2]  = TIME_SEPARATOR;
            FormattedNumber[1]  = FormattedNumber[0];
            FormattedNumber[0]  = L'0';
            Number              = 8;
          }

          if (Number == 4 &&
              (NumericType == TIME_NUMERIC) &&
              (MenuOption->Col + gPromptBlockWidth + 8) == MenuOption->OptCol
              ) {
            FormattedNumber[3]  = FormattedNumber[1];
            FormattedNumber[2]  = RIGHT_NUMERIC_DELIMITER;
            FormattedNumber[1]  = FormattedNumber[0];
            FormattedNumber[0]  = L'0';
            Number              = 8;
          }

          PrintStringAt (Column, Row, FormattedNumber);
          if (Number == 10 && (NumericType == DATE_NUMERIC)) {
            PrintChar (RIGHT_NUMERIC_DELIMITER);
          }

          if (NumericType == REGULAR_NUMERIC) {
            PrintChar (RIGHT_NUMERIC_DELIMITER);
          }
        }
        break;

      case SCAN_UP:
      case SCAN_DOWN:
        goto EnterCarriageReturn;

      case SCAN_ESC:
        return EFI_DEVICE_ERROR;

      default:
        break;
      }

      break;

EnterCarriageReturn:

    case CHAR_CARRIAGE_RETURN:
      //
      // Check to see if the Value is something reasonable against consistency limitations.
      // If not, let's kick the error specified.
      //
      //
      // This gives us visibility to the FileFormTags->NvRamMap to check things
      // ActiveIfr is a global maintained by the menuing code to ensure that we
      // are pointing to the correct formset's file data.
      //
      for (Count = 0; Count < gActiveIfr; Count++) {
        FileFormTags = FileFormTags->NextFile;
      }

      ExtractRequestedNvMap (FileFormTags, Tag->VariableNumber, &VariableDefinition);

      CopyMem (&VariableDefinition->NvRamMap[Tag->StorageStart], &Tag->Value, Tag->StorageWidth);

      //
      // Data associated with a NULL device (in the fake NV storage)
      //
      if (Tag->StorageWidth == (UINT16) 0) {
        CopyMem (&VariableDefinition->FakeNvRamMap[Tag->StorageStart], &Tag->Value, 2);
      }
      //
      // If a late check is required save off the information.  This is used when consistency checks
      // are required, but certain values might be bound by an impossible consistency check such as
      // if two questions are bound by consistency checks and each only has two possible choices, there
      // would be no way for a user to switch the values.  Thus we require late checking.
      //
      if (Tag->Flags & EFI_IFR_FLAG_LATE_CHECK) {
        CopyMem (&Tag->OldValue, &BackupValue, Tag->StorageWidth);
      } else {
        //
        // In theory, passing the value and the Id are sufficient to determine what needs
        // to be done.  The Id is the key to look for the entry needed in the Inconsistency
        // database.  That will yields operand and ID data - and since the ID's correspond
        // to the NV storage, we can determine the values for other IDs there.
        //
        if (ValueIsNotValid (TRUE, 0, Tag, FileFormTags, &PopUp)) {
          if (PopUp == 0x0000) {
            SelectionComplete = TRUE;
            break;
          }

          StringPtr = GetToken (PopUp, MenuOption->Handle);

          CreatePopUp (GetStringWidth (StringPtr) / 2, 3, &NullCharacter, StringPtr, &NullCharacter);

          do {
            WaitForKeyStroke (&Key);

            switch (Key.UnicodeChar) {

            case CHAR_CARRIAGE_RETURN:
              SelectionComplete = TRUE;
              gBS->FreePool (StringPtr);
              break;

            default:
              break;
            }
          } while (!SelectionComplete);

          Tag->Value  = BackupValue;
          *Value      = BackupValue;

          CopyMem (&VariableDefinition->NvRamMap[Tag->StorageStart], &Tag->Value, Tag->StorageWidth);

          //
          // Data associated with a NULL device (in the fake NV storage)
          //
          if (Tag->StorageWidth == (UINT16) 0) {
            CopyMem (&VariableDefinition->FakeNvRamMap[Tag->StorageStart], &Tag->Value, 2);
          }

          return EFI_DEVICE_ERROR;
        }
      }

      return EFI_SUCCESS;
      break;

    case CHAR_BACKSPACE:
      if (ManualInput) {
        if (Count == 0) {
          break;
        }
        //
        // Remove a character
        //
        Number  = PreviousNumber[Count - 1];
        *Value  = (UINT16) Number;
        UpdateStatusBar (INPUT_ERROR, Tag->Flags, FALSE);
        Count--;
        Column--;
        PrintAt (Column, Row, (CHAR16 *) L" ");
      }
      break;

    default:
      if (ManualInput) {
        if (Key.UnicodeChar > L'9' || Key.UnicodeChar < L'0') {
          UpdateStatusBar (INPUT_ERROR, Tag->Flags, TRUE);
          break;
        }
        //
        // If Count 0-4 is complete, there is no way more is valid
        //
        if (Count > 4) {
          break;
        }
        //
        // Someone typed something valid!
        //
        if (Count != 0) {
          Number = Number * 10 + (Key.UnicodeChar - L'0');
        } else {
          Number = Key.UnicodeChar - L'0';
        }

        if (Number > Tag->Maximum) {
          UpdateStatusBar (INPUT_ERROR, Tag->Flags, TRUE);
          Number = PreviousNumber[Count];
          break;
        } else {
          UpdateStatusBar (INPUT_ERROR, Tag->Flags, FALSE);
        }

        Count++;

        PreviousNumber[Count] = Number;
        *Value                = (UINT16) Number;
        Tag->Value            = (UINT16) Number;

        PrintCharAt (Column, Row, Key.UnicodeChar);
        Column++;
      }
      break;
    }
  } while (!SelectionComplete);
  return EFI_SUCCESS;
}
//
// Notice that this is at least needed for the ordered list manipulation.
// Left/Right doesn't make sense for this op-code
//
EFI_STATUS
GetSelectionInputPopUp (
  IN  UI_MENU_OPTION              *MenuOption,
  IN  EFI_TAG                     *Tag,
  IN  UINTN                       ValueCount,
  OUT UINT16                      *Value,
  OUT UINT16                      *KeyValue
  )
{
  EFI_INPUT_KEY Key;
  UINTN         Index;
  UINTN         TempIndex;
  CHAR16        *StringPtr;
  CHAR16        *TempStringPtr;
  UINT16        Token;
  UINTN         Index2;
  UINTN         TopOptionIndex;
  UINTN         HighlightPosition;
  UINTN         Start;
  UINTN         End;
  UINTN         Top;
  UINTN         Bottom;
  UINT16        TempValue;
  UINTN         Count;
  UINTN         PopUpMenuLines;
  UINTN         MenuLinesInView;
  UINTN         PopUpWidth;
  CHAR16        Character;
  BOOLEAN       FirstOptionFoundFlag;
  INT32         SavedAttribute;
  EFI_TAG       TagBackup;
  UINT8         *ValueArray;
  UINT8         *ValueArrayBackup;
  UINT8         ValueBackup;
  BOOLEAN       Initialized;
  BOOLEAN       KeyInitialized;
  BOOLEAN       ShowDownArrow;
  BOOLEAN       ShowUpArrow;
  UINTN         DimensionsWidth;

  DimensionsWidth   = gScreenDimensions.RightColumn - gScreenDimensions.LeftColumn;

  TempValue         = 0;
  TempIndex         = 0;
  ValueArray        = (UINT8 *) Value;
  ValueArrayBackup  = NULL;
  Initialized       = FALSE;
  KeyInitialized    = FALSE;
  ShowDownArrow     = FALSE;
  ShowUpArrow       = FALSE;

  if (Tag->Operand == EFI_IFR_ORDERED_LIST_OP) {
    ValueArrayBackup = AllocateZeroPool (Tag->StorageWidth);
    ASSERT (ValueArrayBackup != NULL);
    CopyMem (ValueArrayBackup, ValueArray, ValueCount);
    TempValue = *(UINT8 *) (ValueArray);
    if (ValueArray[0] != 0x00) {
      Initialized = TRUE;
    }

    for (Index = 0; ValueArray[Index] != 0x00; Index++)
      ;
    ValueCount = Index;
  } else {
    TempValue = *Value;
  }

  Count                 = 0;
  PopUpWidth            = 0;

  FirstOptionFoundFlag  = FALSE;

  StringPtr             = AllocateZeroPool ((gOptionBlockWidth + 1) * 2);
  ASSERT (StringPtr);

  //
  // Initialization for "One of" pop-up menu
  //
  //
  // Get the number of one of options present and its size
  //
  for (Index = MenuOption->TagIndex; MenuOption->Tags[Index].Operand != EFI_IFR_END_ONE_OF_OP; Index++) {
    if (MenuOption->Tags[Index].Operand == EFI_IFR_ONE_OF_OPTION_OP &&
        !MenuOption->Tags[Index].Suppress) {
      if (!FirstOptionFoundFlag) {
        FirstOptionFoundFlag  = TRUE;
      }

      Count++;
      Token = MenuOption->Tags[Index].Text;

      //
      // If this is an ordered list that is initialized
      //
      if (Initialized) {
        for (ValueBackup = (UINT8) MenuOption->TagIndex;
             MenuOption->Tags[ValueBackup].Operand != EFI_IFR_END_OP;
             ValueBackup++
            ) {
          if (MenuOption->Tags[ValueBackup].Value == ((UINT8 *) ValueArrayBackup)[Index - MenuOption->TagIndex - 1]) {
            StringPtr = GetToken (MenuOption->Tags[ValueBackup].Text, MenuOption->Handle);
            break;
          }
        }
      } else {
        StringPtr = GetToken (Token, MenuOption->Handle);
      }

      if (StrLen (StringPtr) > PopUpWidth) {
        PopUpWidth = StrLen (StringPtr);
      }

      gBS->FreePool (StringPtr);
    }
  }
  //
  // Perform popup menu initialization.
  //
  PopUpMenuLines  = Count;
  PopUpWidth      = PopUpWidth + POPUP_PAD_SPACE_COUNT;

  SavedAttribute  = gST->ConOut->Mode->Attribute;
  gST->ConOut->SetAttribute (gST->ConOut, POPUP_TEXT | POPUP_BACKGROUND);

  if ((PopUpWidth + POPUP_FRAME_WIDTH) > DimensionsWidth) {
    PopUpWidth = DimensionsWidth - POPUP_FRAME_WIDTH;
  }

  Start           = (DimensionsWidth - PopUpWidth - POPUP_FRAME_WIDTH) / 2 + gScreenDimensions.LeftColumn;
  End             = Start + PopUpWidth + POPUP_FRAME_WIDTH;
  Top             = gScreenDimensions.TopRow + NONE_FRONT_PAGE_HEADER_HEIGHT;
  Bottom          = gScreenDimensions.BottomRow - STATUS_BAR_HEIGHT - FOOTER_HEIGHT;

  MenuLinesInView = Bottom - Top - 1;
  if (MenuLinesInView >= PopUpMenuLines) {
    Top     = Top + (MenuLinesInView - PopUpMenuLines) / 2;
    Bottom  = Top + PopUpMenuLines + 1;
  } else {
    TempValue     = MenuOption->Tags[MenuOption->TagIndex + 1].Value;
    ShowDownArrow = TRUE;
  }

  TopOptionIndex    = 1;
  HighlightPosition = 0;
  do {
    if (Initialized) {
      for (Index = MenuOption->TagIndex, Index2 = 0; Index2 < ValueCount; Index++, Index2++) {
        //
        // Set the value for the item we are looking for
        //
        Count = ValueArrayBackup[Index2];

        //
        // If we hit the end of the Array, we are complete
        //
        if (Count == 0) {
          break;
        }

        if (MenuOption->Tags[Index].Operand == EFI_IFR_ONE_OF_OPTION_OP) {
          for (ValueBackup = (UINT8) MenuOption->TagIndex;
               MenuOption->Tags[ValueBackup].Operand != EFI_IFR_END_ONE_OF_OP;
               ValueBackup++
              ) {
            //
            // We just found what we are looking for
            //
            if (MenuOption->Tags[ValueBackup].Value == Count) {
              //
              // As long as the two indexes aren't the same, we have
              // two different op-codes we need to swap internally
              //
              if (Index != ValueBackup) {
                //
                // Backup destination tag, then copy source to destination, then copy backup to source location
                //
                CopyMem (&TagBackup, &MenuOption->Tags[Index], sizeof (EFI_TAG));
                CopyMem (&MenuOption->Tags[Index], &MenuOption->Tags[ValueBackup], sizeof (EFI_TAG));
                CopyMem (&MenuOption->Tags[ValueBackup], &TagBackup, sizeof (EFI_TAG));
              } else {
                //
                // If the indexes are the same, then the op-code is where he belongs
                //
              }
            }
          }
        } else {
          //
          // Since this wasn't an option op-code (likely the ordered list op-code) decerement Index2
          //
          Index2--;
        }
      }
    }
    //
    // Clear that portion of the screen
    //
    ClearLines (Start, End, Top, Bottom, POPUP_TEXT | POPUP_BACKGROUND);

    //
    // Draw "One of" pop-up menu
    //
    Character = (CHAR16) BOXDRAW_DOWN_RIGHT;
    PrintCharAt (Start, Top, Character);
    for (Index = Start; Index + 2 < End; Index++) {
      if ((ShowUpArrow) && ((Index + 1) == (Start + End) / 2)) {
        Character = (CHAR16) GEOMETRICSHAPE_UP_TRIANGLE;
      } else {
        Character = (CHAR16) BOXDRAW_HORIZONTAL;
      }

      PrintChar (Character);
    }

    Character = (CHAR16) BOXDRAW_DOWN_LEFT;
    PrintChar (Character);
    Character = (CHAR16) BOXDRAW_VERTICAL;
    for (Index = Top + 1; Index < Bottom; Index++) {
      PrintCharAt (Start, Index, Character);
      PrintCharAt (End - 1, Index, Character);
    }
    //
    // Display the One of options
    //
    Index2 = Top + 1;
    for (Index = MenuOption->TagIndex + TopOptionIndex;
         (MenuOption->Tags[Index].Operand != EFI_IFR_END_ONE_OF_OP) && (Index2 < Bottom);
         Index++
        ) {
      if (MenuOption->Tags[Index].Operand == EFI_IFR_ONE_OF_OPTION_OP) {
        Token = MenuOption->Tags[Index].Text;
        if (Initialized) {
          for (ValueBackup = (UINT8) MenuOption->TagIndex;
               MenuOption->Tags[ValueBackup].Operand != EFI_IFR_END_ONE_OF_OP;
               ValueBackup++
              ) {
            if (MenuOption->Tags[ValueBackup].Value == ((UINT8 *) ValueArrayBackup)[Index - MenuOption->TagIndex - 1]) {
              StringPtr = GetToken (MenuOption->Tags[ValueBackup].Text, MenuOption->Handle);
              break;
            }
          }
        } else {
          ValueBackup = (UINT8) Index;
          StringPtr   = GetToken (Token, MenuOption->Handle);
        }
        //
        // If the string occupies multiple lines, truncate it to fit in one line,
        // and append a "..." for indication.
        //
        if (StrLen (StringPtr) > (PopUpWidth - 1)) {
          TempStringPtr = AllocateZeroPool (sizeof (CHAR16) * (PopUpWidth - 1));
          ASSERT (TempStringPtr != NULL);
          CopyMem (TempStringPtr, StringPtr, (sizeof (CHAR16) * (PopUpWidth - 5)));
          gBS->FreePool (StringPtr);
          StringPtr = TempStringPtr;
          StrCat (StringPtr, (CHAR16 *) L"...");
        }
        //
        // Code to display the text should go here. Follwed by the [*]
        //
        if (MenuOption->Tags[ValueBackup].Suppress == TRUE) {
          //
          // Don't show the one, so decrease the Index2 for balance
          //
          Index2--;
        } else if (MenuOption->Tags[ValueBackup].GrayOut == TRUE) {
          //
          // Gray Out the one
          //
          gST->ConOut->SetAttribute (gST->ConOut, FIELD_TEXT_GRAYED | POPUP_BACKGROUND);
          PrintStringAt (Start + 2, Index2, StringPtr);
          gST->ConOut->SetAttribute (gST->ConOut, POPUP_TEXT | POPUP_BACKGROUND);
        } else if (MenuOption->Tags[ValueBackup].Value == TempValue) {
          //
          // Highlight the selected one
          //
          gST->ConOut->SetAttribute (gST->ConOut, PICKLIST_HIGHLIGHT_TEXT | PICKLIST_HIGHLIGHT_BACKGROUND);
          PrintStringAt (Start + 2, Index2, StringPtr);
          gST->ConOut->SetAttribute (gST->ConOut, POPUP_TEXT | POPUP_BACKGROUND);
          HighlightPosition = Index2;
        } else {
          gST->ConOut->SetAttribute (gST->ConOut, POPUP_TEXT | POPUP_BACKGROUND);
          PrintStringAt (Start + 2, Index2, StringPtr);
        }

        gBS->FreePool (StringPtr);
        Index2 = Index2 + 1;
      }
    }

    Character = (CHAR16) BOXDRAW_UP_RIGHT;
    PrintCharAt (Start, Bottom, Character);
    for (Index = Start; Index + 2 < End; Index++) {
      if ((ShowDownArrow) && ((Index + 1) == (Start + End) / 2)) {
        Character = (CHAR16) GEOMETRICSHAPE_DOWN_TRIANGLE;
      } else {
        Character = (CHAR16) BOXDRAW_HORIZONTAL;
      }

      PrintChar (Character);
    }

    Character = (CHAR16) BOXDRAW_UP_LEFT;
    PrintChar (Character);
    //
    // Get User selection and change TempValue if necessary
    //
    //
    // Stop: One of pop-up menu
    //
    Key.UnicodeChar = CHAR_NULL;
    if ((gDirection == SCAN_UP) || (gDirection == SCAN_DOWN)) {
      Key.ScanCode  = gDirection;
      gDirection    = 0;
      goto TheKey;
    }

    if (!KeyInitialized) {
      if (MenuOption->ThisTag->Operand == EFI_IFR_ONE_OF_OP) {
        *KeyValue = MenuOption->Tags[MenuOption->TagIndex + 1].Key;
      } else {
        *KeyValue = MenuOption->ThisTag->Key;
      }

      KeyInitialized = TRUE;
    }

    WaitForKeyStroke (&Key);

TheKey:
    switch (Key.UnicodeChar) {
    case '+':
    case '-':
      //
      // If an ordered list op-code, we will allow for a popup of +/- keys
      // to create an ordered list of items
      //
      if (Tag->Operand == EFI_IFR_ORDERED_LIST_OP) {
        if (Key.UnicodeChar == '+') {
          if ((TopOptionIndex > 1) && (HighlightPosition == (Top + 1))) {
            //
            // Highlight reaches the top of the popup window, scroll one menu item.
            //
            TopOptionIndex--;
            ShowDownArrow = TRUE;
          }

          if (TopOptionIndex == 1) {
            ShowUpArrow = FALSE;
          }
        } else {
          if (((TopOptionIndex + MenuLinesInView) <= PopUpMenuLines) && (HighlightPosition == (Bottom - 1))) {
            //
            // Highlight reaches the bottom of the popup window, scroll one menu item.
            //
            TopOptionIndex++;
            ShowUpArrow = TRUE;
          }

          if ((TopOptionIndex + MenuLinesInView) == (PopUpMenuLines + 1)) {
            ShowDownArrow = FALSE;
          }
        }

        for (Index = MenuOption->TagIndex + TopOptionIndex;
             MenuOption->Tags[Index].Operand != EFI_IFR_END_ONE_OF_OP;
             Index++
            ) {
          if (MenuOption->Tags[Index].Operand == EFI_IFR_ORDERED_LIST_OP) {
            continue;
          }

          if (Key.UnicodeChar == '+') {
            TempIndex = Index - 1;
          } else {
            TempIndex = Index + 1;
          }
          //
          // Is this the current tag we are on?
          //
          if (MenuOption->Tags[Index].Value == TempValue) {
            //
            // Is this prior tag a valid choice?  If not, bail out
            //
            if (MenuOption->Tags[TempIndex].Operand == EFI_IFR_ONE_OF_OPTION_OP) {
              //
              // Copy the destination tag to the local variable
              //
              CopyMem (&TagBackup, &MenuOption->Tags[TempIndex], sizeof (EFI_TAG));
              //
              // Copy the current tag to the tag location before us
              //
              CopyMem (&MenuOption->Tags[TempIndex], &MenuOption->Tags[Index], sizeof (EFI_TAG));
              //
              // Copy the backed up tag to the current location
              //
              CopyMem (&MenuOption->Tags[Index], &TagBackup, sizeof (EFI_TAG));

              //
              // Adjust the array of values
              //
              for (Index = 0; Index < ValueCount; Index++) {
                if (ValueArrayBackup[Index] == (UINT8) TempValue) {
                  if (Key.UnicodeChar == '+') {
                    if (Index == 0) {
                      //
                      // It is the top of the array already
                      //
                      break;
                    }

                    TempIndex = Index - 1;
                  } else {
                    if ((Index + 1) == ValueCount) {
                      //
                      // It is the bottom of the array already
                      //
                      break;
                    }

                    TempIndex = Index + 1;
                  }

                  ValueBackup                 = ValueArrayBackup[TempIndex];
                  ValueArrayBackup[TempIndex] = ValueArrayBackup[Index];
                  ValueArrayBackup[Index]     = ValueBackup;
                  Initialized                 = TRUE;
                  break;
                }
              }
              break;
            } else {
              break;
            }
          }
        }
      }
      break;

    case CHAR_NULL:
      switch (Key.ScanCode) {
      case SCAN_UP:
      case SCAN_DOWN:
        if (Key.ScanCode == SCAN_UP) {
          if ((TopOptionIndex > 1) && (HighlightPosition == (Top + 1))) {
            //
            // Highlight reaches the top of the popup window, scroll one menu item.
            //
            TopOptionIndex--;
            ShowDownArrow = TRUE;
          }

          if (TopOptionIndex == 1) {
            ShowUpArrow = FALSE;
          }
        } else {
          if (((TopOptionIndex + MenuLinesInView) <= PopUpMenuLines) && (HighlightPosition == (Bottom - 1))) {
            //
            // Highlight reaches the bottom of the popup window, scroll one menu item.
            //
            TopOptionIndex++;
            ShowUpArrow = TRUE;
          }

          if ((TopOptionIndex + MenuLinesInView) == (PopUpMenuLines + 1)) {
            ShowDownArrow = FALSE;
          }
        }

        for (Index = MenuOption->TagIndex + TopOptionIndex;
             MenuOption->Tags[Index].Operand != EFI_IFR_END_ONE_OF_OP;
             Index++
            ) {
          if (MenuOption->Tags[Index].Operand == EFI_IFR_ONE_OF_OPTION_OP) {
            if (Initialized) {
              for (Index = 0; (ValueArrayBackup[Index] != TempValue) && (Index < ValueCount); Index++)
                ;

              //
              // Did we hit the end of the array?  Either get the first TempValue or the next one
              //
              if (Key.ScanCode == SCAN_UP) {
                if (Index == 0) {
                  TempValue = ValueArrayBackup[0];
                } else {
                  TempValue = ValueArrayBackup[Index - 1];
                }
              } else {
                if ((Index + 1) == ValueCount) {
                  TempValue = ValueArrayBackup[Index];
                } else {
                  TempValue = ValueArrayBackup[Index + 1];
                }
              }
              break;
            } else {
              if (Key.ScanCode == SCAN_UP) {
                TempIndex = Index - 1;
                
                //
                // Keep going until meets meaningful tag.
                //
                while ((MenuOption->Tags[TempIndex].Operand != EFI_IFR_ONE_OF_OPTION_OP  &&
                         MenuOption->Tags[TempIndex].Operand != EFI_IFR_ONE_OF_OP        &&
                         MenuOption->Tags[TempIndex].Operand != EFI_IFR_END_ONE_OF_OP)
                       ||
                       (MenuOption->Tags[TempIndex].Operand == EFI_IFR_ONE_OF_OPTION_OP  &&
                         (MenuOption->Tags[TempIndex].Suppress || MenuOption->Tags[TempIndex].GrayOut))) {
                  TempIndex--;
                }
              } else {
                TempIndex = Index + 1;

                //
                // Keep going until meets meaningful tag.
                //
                while ((MenuOption->Tags[TempIndex].Operand != EFI_IFR_ONE_OF_OPTION_OP  &&
                         MenuOption->Tags[TempIndex].Operand != EFI_IFR_ONE_OF_OP        &&
                         MenuOption->Tags[TempIndex].Operand != EFI_IFR_END_ONE_OF_OP)
                       ||
                       (MenuOption->Tags[TempIndex].Operand == EFI_IFR_ONE_OF_OPTION_OP  &&
                         (MenuOption->Tags[TempIndex].Suppress || MenuOption->Tags[TempIndex].GrayOut))) {
                  TempIndex++;
                }
              }
              //
              // The option value is the same as what is stored in NV store.  This is where we take action
              //
              if (MenuOption->Tags[Index].Value == TempValue) {
                //
                // Only if the previous op-code is an option can we select it, otherwise we are at the left-most option
                //
                if (MenuOption->Tags[TempIndex].Operand == EFI_IFR_ONE_OF_OPTION_OP) {
                  TempValue = MenuOption->Tags[TempIndex].Value;
                  *KeyValue = MenuOption->Tags[TempIndex].Key;
                } else {
                  TempValue = MenuOption->Tags[Index].Value;
                  *KeyValue = MenuOption->Tags[Index].Key;
                }
                break;
              }
            }
          }
        }
        break;

      case SCAN_ESC:
        gST->ConOut->SetAttribute (gST->ConOut, SavedAttribute);
        if (ValueArrayBackup != NULL) {
          gBS->FreePool (ValueArrayBackup);
        }

        return EFI_DEVICE_ERROR;

      default:
        break;
      }

      break;

    case CHAR_CARRIAGE_RETURN:
      //
      // return the current selection
      //
      if (Tag->Operand == EFI_IFR_ORDERED_LIST_OP) {
        CopyMem (ValueArray, ValueArrayBackup, ValueCount);
        gBS->FreePool (ValueArrayBackup);
      } else {
        *Value = TempValue;
      }

      goto Done;

    default:
      break;
    }
  } while (1);

Done:
  gST->ConOut->SetAttribute (gST->ConOut, SavedAttribute);
  return EFI_SUCCESS;
}

EFI_STATUS
WaitForKeyStroke (
  OUT  EFI_INPUT_KEY           *Key
  )
{
  EFI_STATUS  Status;

  do {
    UiWaitForSingleEvent (gST->ConIn->WaitForKey, 0);
    Status = gST->ConIn->ReadKeyStroke (gST->ConIn, Key);
  } while (EFI_ERROR(Status));

  return Status;
}
