/** @file
Implementation for handling user input from the User Interfaces.

Copyright (c) 2004 - 2018, Intel Corporation. All rights reserved.<BR>
Copyright (C) 2024 Advanced Micro Devices, Inc. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include "FormDisplay.h"

/**
  Get maximum and minimum info from this opcode.

  @param  OpCode            Pointer to the current input opcode.
  @param  Minimum           The minimum size info for this opcode.
  @param  Maximum           The maximum size info for this opcode.

**/
VOID
GetFieldFromOp (
  IN   EFI_IFR_OP_HEADER  *OpCode,
  OUT  UINTN              *Minimum,
  OUT  UINTN              *Maximum
  )
{
  EFI_IFR_STRING    *StringOp;
  EFI_IFR_PASSWORD  *PasswordOp;

  if (OpCode->OpCode == EFI_IFR_STRING_OP) {
    StringOp = (EFI_IFR_STRING *)OpCode;
    *Minimum = StringOp->MinSize;
    *Maximum = StringOp->MaxSize;
  } else if (OpCode->OpCode == EFI_IFR_PASSWORD_OP) {
    PasswordOp = (EFI_IFR_PASSWORD *)OpCode;
    *Minimum   = PasswordOp->MinSize;
    *Maximum   = PasswordOp->MaxSize;
  } else {
    *Minimum = 0;
    *Maximum = 0;
  }
}

/**
  Get string or password input from user.

  @param  MenuOption        Pointer to the current input menu.
  @param  Prompt            The prompt string shown on popup window.
  @param  StringPtr         Old user input and destination for use input string.

  @retval EFI_SUCCESS       If string input is read successfully
  @retval EFI_DEVICE_ERROR  If operation fails

**/
EFI_STATUS
ReadString (
  IN     UI_MENU_OPTION  *MenuOption,
  IN     CHAR16          *Prompt,
  IN OUT CHAR16          *StringPtr
  )
{
  EFI_STATUS                     Status;
  EFI_INPUT_KEY                  Key;
  CHAR16                         NullCharacter;
  UINTN                          ScreenSize;
  CHAR16                         Space[2];
  CHAR16                         KeyPad[2];
  CHAR16                         *TempString;
  CHAR16                         *BufferedString;
  UINTN                          Index;
  UINTN                          Index2;
  UINTN                          Count;
  UINTN                          Start;
  UINTN                          Top;
  UINTN                          DimensionsWidth;
  UINTN                          DimensionsHeight;
  UINTN                          CurrentCursor;
  BOOLEAN                        CursorVisible;
  UINTN                          Minimum;
  UINTN                          Maximum;
  FORM_DISPLAY_ENGINE_STATEMENT  *Question;
  BOOLEAN                        IsPassword;
  UINTN                          MaxLen;

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

  NullCharacter = CHAR_NULL;
  ScreenSize    = GetStringWidth (Prompt) / sizeof (CHAR16);
  Space[0]      = L' ';
  Space[1]      = CHAR_NULL;

  Question = MenuOption->ThisTag;
  GetFieldFromOp (Question->OpCode, &Minimum, &Maximum);

  if (Question->OpCode->OpCode == EFI_IFR_PASSWORD_OP) {
    IsPassword = TRUE;
  } else {
    IsPassword = FALSE;
  }

  MaxLen     = Maximum + 1;
  TempString = AllocateZeroPool (MaxLen * sizeof (CHAR16));
  ASSERT (TempString);

  if (ScreenSize < (Maximum + 1)) {
    ScreenSize = Maximum + 1;
  }

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

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

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

  //
  // Display prompt for string
  //
  // CreateDialog (NULL, "", Prompt, Space, "", NULL);
  CreateMultiStringPopUp (ScreenSize, 4, &NullCharacter, Prompt, Space, &NullCharacter);
  gST->ConOut->SetAttribute (gST->ConOut, EFI_TEXT_ATTR (EFI_BLACK, EFI_LIGHTGRAY));

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

  CurrentCursor = GetStringWidth (StringPtr) / 2 - 1;
  if (CurrentCursor != 0) {
    //
    // Show the string which has beed saved before.
    //
    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;
    }

    if (IsPassword) {
      gST->ConOut->SetCursorPosition (gST->ConOut, Start + 1, Top + 3);
    }

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

      if (IsPassword) {
        PrintCharAt ((UINTN)-1, (UINTN)-1, L'*');
      }
    }

    if (!IsPassword) {
      PrintStringAt (Start + 1, Top + 3, BufferedString);
    }

    gST->ConOut->SetAttribute (gST->ConOut, EFI_TEXT_ATTR (EFI_LIGHTGRAY, EFI_BLACK));
    gST->ConOut->SetCursorPosition (gST->ConOut, Start + GetStringWidth (StringPtr) / 2, Top + 3);
  }

  do {
    Status = WaitForKeyStroke (&Key);
    ASSERT_EFI_ERROR (Status);

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

            break;

          case SCAN_RIGHT:
            if (CurrentCursor < (GetStringWidth (StringPtr) / 2 - 1)) {
              CurrentCursor++;
            }

            break;

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

          case SCAN_DELETE:
            for (Index = CurrentCursor; StringPtr[Index] != CHAR_NULL; Index++) {
              StringPtr[Index] = StringPtr[Index + 1];
              PrintCharAt (Start + Index + 1, Top + 3, IsPassword && StringPtr[Index] != CHAR_NULL ? L'*' : StringPtr[Index]);
            }

            break;

          default:
            break;
        }

        break;

      case CHAR_CARRIAGE_RETURN:
        if (GetStringWidth (StringPtr) >= ((Minimum + 1) * sizeof (CHAR16))) {
          FreePool (TempString);
          FreePool (BufferedString);
          gST->ConOut->SetAttribute (gST->ConOut, EFI_TEXT_ATTR (EFI_LIGHTGRAY, EFI_BLACK));
          gST->ConOut->EnableCursor (gST->ConOut, CursorVisible);
          return EFI_SUCCESS;
        } else {
          //
          // 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 {
            CreateDialog (&Key, &NullCharacter, gMiniString, gPressEnter, &NullCharacter, NULL);
          } while (Key.UnicodeChar != CHAR_CARRIAGE_RETURN);

          FreePool (TempString);
          FreePool (BufferedString);
          gST->ConOut->SetAttribute (gST->ConOut, EFI_TEXT_ATTR (EFI_LIGHTGRAY, EFI_BLACK));
          gST->ConOut->EnableCursor (gST->ConOut, CursorVisible);
          return EFI_DEVICE_ERROR;
        }

      case CHAR_BACKSPACE:
        if ((StringPtr[0] != CHAR_NULL) && (CurrentCursor != 0)) {
          for (Index = 0; Index < CurrentCursor - 1; Index++) {
            TempString[Index] = StringPtr[Index];
          }

          Count = GetStringWidth (StringPtr) / 2 - 1;
          if (Count >= CurrentCursor) {
            for (Index = CurrentCursor - 1, Index2 = CurrentCursor; Index2 < Count; Index++, Index2++) {
              TempString[Index] = StringPtr[Index2];
            }

            TempString[Index] = CHAR_NULL;
          }

          //
          // Effectively truncate string by 1 character
          //
          StrCpyS (StringPtr, MaxLen, TempString);
          CurrentCursor--;
        }

      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)) {
          StrnCpyS (StringPtr, MaxLen, &Key.UnicodeChar, 1);
          CurrentCursor++;
        } else if ((GetStringWidth (StringPtr) < ((Maximum + 1) * sizeof (CHAR16))) && (Key.UnicodeChar != CHAR_BACKSPACE)) {
          KeyPad[0] = Key.UnicodeChar;
          KeyPad[1] = CHAR_NULL;
          Count     = GetStringWidth (StringPtr) / 2 - 1;
          if (CurrentCursor < Count) {
            for (Index = 0; Index < CurrentCursor; Index++) {
              TempString[Index] = StringPtr[Index];
            }

            TempString[Index] = CHAR_NULL;
            StrCatS (TempString, MaxLen, KeyPad);
            StrCatS (TempString, MaxLen, StringPtr + CurrentCursor);
            StrCpyS (StringPtr, MaxLen, TempString);
          } else {
            StrCatS (StringPtr, MaxLen, KeyPad);
          }

          CurrentCursor++;
        }

        //
        // 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;
        }

        if (IsPassword) {
          gST->ConOut->SetCursorPosition (gST->ConOut, Start + 1, Top + 3);
        }

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

          if (IsPassword) {
            PrintCharAt ((UINTN)-1, (UINTN)-1, L'*');
          }
        }

        if (!IsPassword) {
          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 + CurrentCursor + 1, Top + 3);
  } while (TRUE);
}

/**
  Adjust the value to the correct one. Rules follow the sample:
  like:  Year change:  2012.02.29 -> 2013.02.29 -> 2013.02.01
         Month change: 2013.03.29 -> 2013.02.29 -> 2013.02.28

  @param  QuestionValue     Pointer to current question.
  @param  Sequence          The sequence of the field in the question.
**/
VOID
AdjustQuestionValue (
  IN  EFI_HII_VALUE  *QuestionValue,
  IN  UINT8          Sequence
  )
{
  UINT8   Month;
  UINT16  Year;
  UINT8   Maximum;
  UINT8   Minimum;

  Month   = QuestionValue->Value.date.Month;
  Year    = QuestionValue->Value.date.Year;
  Minimum = 1;

  switch (Month) {
    case 2:
      if (((Year % 4) == 0) && (((Year % 100) != 0) || ((Year % 400) == 0))) {
        Maximum = 29;
      } else {
        Maximum = 28;
      }

      break;
    case 4:
    case 6:
    case 9:
    case 11:
      Maximum = 30;
      break;
    default:
      Maximum = 31;
      break;
  }

  //
  // Change the month area.
  //
  if (Sequence == 0) {
    if (QuestionValue->Value.date.Day > Maximum) {
      QuestionValue->Value.date.Day = Maximum;
    }
  }

  //
  // Change the Year area.
  //
  if (Sequence == 2) {
    if (QuestionValue->Value.date.Day > Maximum) {
      QuestionValue->Value.date.Day = Minimum;
    }
  }
}

/**
  Get field info from numeric opcode.

  @param  OpCode            Pointer to the current input opcode.
  @param  IntInput          Whether question shows with EFI_IFR_DISPLAY_INT_DEC type.
  @param  QuestionValue     Input question value, with EFI_HII_VALUE type.
  @param  Value             Return question value, always return UINT64 type.
  @param  Minimum           The minimum size info for this opcode.
  @param  Maximum           The maximum size info for this opcode.
  @param  Step              The step size info for this opcode.
  @param  StorageWidth      The storage width info for this opcode.

**/
VOID
GetValueFromNum (
  IN  EFI_IFR_OP_HEADER  *OpCode,
  IN  BOOLEAN            IntInput,
  IN  EFI_HII_VALUE      *QuestionValue,
  OUT UINT64             *Value,
  OUT UINT64             *Minimum,
  OUT UINT64             *Maximum,
  OUT UINT64             *Step,
  OUT UINT16             *StorageWidth
  )
{
  EFI_IFR_NUMERIC  *NumericOp;

  NumericOp = (EFI_IFR_NUMERIC *)OpCode;

  switch (NumericOp->Flags & EFI_IFR_NUMERIC_SIZE) {
    case EFI_IFR_NUMERIC_SIZE_1:
      if (IntInput) {
        *Minimum = (INT64)(INT8)NumericOp->data.u8.MinValue;
        *Maximum = (INT64)(INT8)NumericOp->data.u8.MaxValue;
        *Value   = (INT64)(INT8)QuestionValue->Value.u8;
      } else {
        *Minimum = NumericOp->data.u8.MinValue;
        *Maximum = NumericOp->data.u8.MaxValue;
        *Value   = QuestionValue->Value.u8;
      }

      *Step         = NumericOp->data.u8.Step;
      *StorageWidth = (UINT16)sizeof (UINT8);
      break;

    case EFI_IFR_NUMERIC_SIZE_2:
      if (IntInput) {
        *Minimum = (INT64)(INT16)NumericOp->data.u16.MinValue;
        *Maximum = (INT64)(INT16)NumericOp->data.u16.MaxValue;
        *Value   = (INT64)(INT16)QuestionValue->Value.u16;
      } else {
        *Minimum = NumericOp->data.u16.MinValue;
        *Maximum = NumericOp->data.u16.MaxValue;
        *Value   = QuestionValue->Value.u16;
      }

      *Step         = NumericOp->data.u16.Step;
      *StorageWidth = (UINT16)sizeof (UINT16);
      break;

    case EFI_IFR_NUMERIC_SIZE_4:
      if (IntInput) {
        *Minimum = (INT64)(INT32)NumericOp->data.u32.MinValue;
        *Maximum = (INT64)(INT32)NumericOp->data.u32.MaxValue;
        *Value   = (INT64)(INT32)QuestionValue->Value.u32;
      } else {
        *Minimum = NumericOp->data.u32.MinValue;
        *Maximum = NumericOp->data.u32.MaxValue;
        *Value   = QuestionValue->Value.u32;
      }

      *Step         = NumericOp->data.u32.Step;
      *StorageWidth = (UINT16)sizeof (UINT32);
      break;

    case EFI_IFR_NUMERIC_SIZE_8:
      if (IntInput) {
        *Minimum = (INT64)NumericOp->data.u64.MinValue;
        *Maximum = (INT64)NumericOp->data.u64.MaxValue;
        *Value   = (INT64)QuestionValue->Value.u64;
      } else {
        *Minimum = NumericOp->data.u64.MinValue;
        *Maximum = NumericOp->data.u64.MaxValue;
        *Value   = QuestionValue->Value.u64;
      }

      *Step         = NumericOp->data.u64.Step;
      *StorageWidth = (UINT16)sizeof (UINT64);
      break;

    default:
      break;
  }

  if (*Maximum == 0) {
    *Maximum = (UINT64)-1;
  }
}

/**
  This routine reads a numeric value from the user input.

  @param  MenuOption        Pointer to the current input menu.

  @retval EFI_SUCCESS       If numerical input is read successfully
  @retval EFI_DEVICE_ERROR  If operation fails

**/
EFI_STATUS
GetNumericInput (
  IN  UI_MENU_OPTION  *MenuOption
  )
{
  UINTN                          Column;
  UINTN                          Row;
  CHAR16                         InputText[MAX_NUMERIC_INPUT_WIDTH];
  CHAR16                         FormattedNumber[MAX_NUMERIC_INPUT_WIDTH - 1];
  UINT64                         PreviousNumber[MAX_NUMERIC_INPUT_WIDTH - 3];
  UINTN                          Count;
  UINTN                          Loop;
  BOOLEAN                        ManualInput;
  BOOLEAN                        HexInput;
  BOOLEAN                        IntInput;
  BOOLEAN                        Negative;
  BOOLEAN                        ValidateFail;
  BOOLEAN                        DateOrTime;
  UINTN                          InputWidth;
  UINT64                         EditValue;
  UINT64                         Step;
  UINT64                         Minimum;
  UINT64                         Maximum;
  UINTN                          EraseLen;
  UINT8                          Digital;
  EFI_INPUT_KEY                  Key;
  EFI_HII_VALUE                  *QuestionValue;
  FORM_DISPLAY_ENGINE_STATEMENT  *Question;
  EFI_IFR_NUMERIC                *NumericOp;
  UINT16                         StorageWidth;

  Column            = MenuOption->OptCol;
  Row               = MenuOption->Row;
  PreviousNumber[0] = 0;
  Count             = 0;
  InputWidth        = 0;
  Digital           = 0;
  StorageWidth      = 0;
  Minimum           = 0;
  Maximum           = 0;
  NumericOp         = NULL;
  IntInput          = FALSE;
  HexInput          = FALSE;
  Negative          = FALSE;
  ValidateFail      = FALSE;

  Question      = MenuOption->ThisTag;
  QuestionValue = &Question->CurrentValue;
  ZeroMem (InputText, MAX_NUMERIC_INPUT_WIDTH * sizeof (CHAR16));

  //
  // Only two case, user can enter to this function: Enter and +/- case.
  // In Enter case, gDirection = 0; in +/- case, gDirection = SCAN_LEFT/SCAN_WRIGHT
  //
  ManualInput = (BOOLEAN)(gDirection == 0 ? TRUE : FALSE);

  if ((Question->OpCode->OpCode == EFI_IFR_DATE_OP) || (Question->OpCode->OpCode == EFI_IFR_TIME_OP)) {
    DateOrTime = TRUE;
  } else {
    DateOrTime = FALSE;
  }

  //
  // Prepare Value to be edit
  //
  EraseLen  = 0;
  EditValue = 0;
  if (Question->OpCode->OpCode == EFI_IFR_DATE_OP) {
    Step    = 1;
    Minimum = 1;

    switch (MenuOption->Sequence) {
      case 0:
        Maximum   = 12;
        EraseLen  = 4;
        EditValue = QuestionValue->Value.date.Month;
        break;

      case 1:
        switch (QuestionValue->Value.date.Month) {
          case 2:
            if (((QuestionValue->Value.date.Year % 4) == 0) &&
                (((QuestionValue->Value.date.Year % 100) != 0) ||
                 ((QuestionValue->Value.date.Year % 400) == 0)))
            {
              Maximum = 29;
            } else {
              Maximum = 28;
            }

            break;
          case 4:
          case 6:
          case 9:
          case 11:
            Maximum = 30;
            break;
          default:
            Maximum = 31;
            break;
        }

        EraseLen  = 3;
        EditValue = QuestionValue->Value.date.Day;
        break;

      case 2:
        Maximum   = 0xffff;
        EraseLen  = 5;
        EditValue = QuestionValue->Value.date.Year;
        break;

      default:
        break;
    }
  } else if (Question->OpCode->OpCode == EFI_IFR_TIME_OP) {
    Step    = 1;
    Minimum = 0;

    switch (MenuOption->Sequence) {
      case 0:
        Maximum   = 23;
        EraseLen  = 4;
        EditValue = QuestionValue->Value.time.Hour;
        break;

      case 1:
        Maximum   = 59;
        EraseLen  = 3;
        EditValue = QuestionValue->Value.time.Minute;
        break;

      case 2:
        Maximum   = 59;
        EraseLen  = 3;
        EditValue = QuestionValue->Value.time.Second;
        break;

      default:
        break;
    }
  } else {
    ASSERT (Question->OpCode->OpCode == EFI_IFR_NUMERIC_OP);
    NumericOp = (EFI_IFR_NUMERIC *)Question->OpCode;
    GetValueFromNum (Question->OpCode, (NumericOp->Flags & EFI_IFR_DISPLAY) == 0, QuestionValue, &EditValue, &Minimum, &Maximum, &Step, &StorageWidth);
    EraseLen = gOptionBlockWidth;
  }

  if ((Question->OpCode->OpCode == EFI_IFR_NUMERIC_OP) && (NumericOp != NULL)) {
    if ((NumericOp->Flags & EFI_IFR_DISPLAY) == EFI_IFR_DISPLAY_UINT_HEX) {
      HexInput = TRUE;
    } else if ((NumericOp->Flags & EFI_IFR_DISPLAY) == 0) {
      //
      // Display with EFI_IFR_DISPLAY_INT_DEC type. Support negative number.
      //
      IntInput = TRUE;
    }
  }

  //
  // Enter from "Enter" input, clear the old word showing.
  //
  if (ManualInput) {
    if (Question->OpCode->OpCode == EFI_IFR_NUMERIC_OP) {
      if (HexInput) {
        InputWidth = StorageWidth * 2;
      } else {
        switch (StorageWidth) {
          case 1:
            InputWidth = 3;
            break;

          case 2:
            InputWidth = 5;
            break;

          case 4:
            InputWidth = 10;
            break;

          case 8:
            InputWidth = 20;
            break;

          default:
            InputWidth = 0;
            break;
        }

        if (IntInput) {
          //
          // Support an extra '-' for negative number.
          //
          InputWidth += 1;
        }
      }

      InputText[0] = LEFT_NUMERIC_DELIMITER;
      SetUnicodeMem (InputText + 1, InputWidth, L' ');
      ASSERT (InputWidth + 2 < MAX_NUMERIC_INPUT_WIDTH);
      InputText[InputWidth + 1] = RIGHT_NUMERIC_DELIMITER;
      InputText[InputWidth + 2] = L'\0';

      PrintStringAt (Column, Row, InputText);
      Column++;
    }

    if (Question->OpCode->OpCode == EFI_IFR_DATE_OP) {
      if (MenuOption->Sequence == 2) {
        InputWidth = 4;
      } else {
        InputWidth = 2;
      }

      if (MenuOption->Sequence == 0) {
        InputText[0] = LEFT_NUMERIC_DELIMITER;
        SetUnicodeMem (InputText + 1, InputWidth, L' ');
        InputText[InputWidth + 1] = DATE_SEPARATOR;
        InputText[InputWidth + 2] = L'\0';
      } else if (MenuOption->Sequence == 1) {
        SetUnicodeMem (InputText, InputWidth, L' ');
        InputText[InputWidth]     = DATE_SEPARATOR;
        InputText[InputWidth + 1] = L'\0';
      } else {
        SetUnicodeMem (InputText, InputWidth, L' ');
        InputText[InputWidth]     = RIGHT_NUMERIC_DELIMITER;
        InputText[InputWidth + 1] = L'\0';
      }

      PrintStringAt (Column, Row, InputText);
      if (MenuOption->Sequence == 0) {
        Column++;
      }
    }

    if (Question->OpCode->OpCode == EFI_IFR_TIME_OP) {
      InputWidth = 2;

      if (MenuOption->Sequence == 0) {
        InputText[0] = LEFT_NUMERIC_DELIMITER;
        SetUnicodeMem (InputText + 1, InputWidth, L' ');
        InputText[InputWidth + 1] = TIME_SEPARATOR;
        InputText[InputWidth + 2] = L'\0';
      } else if (MenuOption->Sequence == 1) {
        SetUnicodeMem (InputText, InputWidth, L' ');
        InputText[InputWidth]     = TIME_SEPARATOR;
        InputText[InputWidth + 1] = L'\0';
      } else {
        SetUnicodeMem (InputText, InputWidth, L' ');
        InputText[InputWidth]     = RIGHT_NUMERIC_DELIMITER;
        InputText[InputWidth + 1] = L'\0';
      }

      PrintStringAt (Column, Row, InputText);
      if (MenuOption->Sequence == 0) {
        Column++;
      }
    }
  }

  //
  // 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 (ManualInput && IntInput) {
          //
          // In Manual input mode, check whether input the negative flag.
          //
          if (Key.UnicodeChar == '-') {
            if (Negative) {
              break;
            }

            Negative = TRUE;
            PrintCharAt (Column++, Row, Key.UnicodeChar);
          }
        } else {
          if (Key.UnicodeChar == '+') {
            Key.ScanCode = SCAN_RIGHT;
          } else {
            Key.ScanCode = SCAN_LEFT;
          }

          Key.UnicodeChar = CHAR_NULL;
          goto TheKey2;
        }

        break;

      case CHAR_NULL:
        switch (Key.ScanCode) {
          case SCAN_LEFT:
          case SCAN_RIGHT:
            if (DateOrTime && !ManualInput) {
              //
              // 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 ((Step != 0) && !ManualInput) {
              if (Key.ScanCode == SCAN_LEFT) {
                if (IntInput) {
                  if ((INT64)EditValue >= (INT64)Minimum + (INT64)Step) {
                    EditValue = EditValue - Step;
                  } else if ((INT64)EditValue > (INT64)Minimum) {
                    EditValue = Minimum;
                  } else {
                    EditValue = Maximum;
                  }
                } else {
                  if (EditValue >= Minimum + Step) {
                    EditValue = EditValue - Step;
                  } else if (EditValue > Minimum) {
                    EditValue = Minimum;
                  } else {
                    EditValue = Maximum;
                  }
                }
              } else if (Key.ScanCode == SCAN_RIGHT) {
                if (IntInput) {
                  if ((INT64)EditValue + (INT64)Step <= (INT64)Maximum) {
                    EditValue = EditValue + Step;
                  } else if ((INT64)EditValue < (INT64)Maximum) {
                    EditValue = Maximum;
                  } else {
                    EditValue = Minimum;
                  }
                } else {
                  if (EditValue + Step <= Maximum) {
                    EditValue = EditValue + Step;
                  } else if (EditValue < Maximum) {
                    EditValue = Maximum;
                  } else {
                    EditValue = Minimum;
                  }
                }
              }

              ZeroMem (FormattedNumber, 21 * sizeof (CHAR16));
              if (Question->OpCode->OpCode == EFI_IFR_DATE_OP) {
                if (MenuOption->Sequence == 2) {
                  //
                  // Year
                  //
                  UnicodeSPrint (FormattedNumber, 21 * sizeof (CHAR16), L"%04d", (UINT16)EditValue);
                } else {
                  //
                  // Month/Day
                  //
                  UnicodeSPrint (FormattedNumber, 21 * sizeof (CHAR16), L"%02d", (UINT8)EditValue);
                }

                if (MenuOption->Sequence == 0) {
                  ASSERT (EraseLen >= 2);
                  FormattedNumber[EraseLen - 2] = DATE_SEPARATOR;
                } else if (MenuOption->Sequence == 1) {
                  ASSERT (EraseLen >= 1);
                  FormattedNumber[EraseLen - 1] = DATE_SEPARATOR;
                }
              } else if (Question->OpCode->OpCode == EFI_IFR_TIME_OP) {
                UnicodeSPrint (FormattedNumber, 21 * sizeof (CHAR16), L"%02d", (UINT8)EditValue);

                if (MenuOption->Sequence == 0) {
                  ASSERT (EraseLen >= 2);
                  FormattedNumber[EraseLen - 2] = TIME_SEPARATOR;
                } else if (MenuOption->Sequence == 1) {
                  ASSERT (EraseLen >= 1);
                  FormattedNumber[EraseLen - 1] = TIME_SEPARATOR;
                }
              } else {
                QuestionValue->Value.u64 = EditValue;
                PrintFormattedNumber (Question, FormattedNumber, 21 * sizeof (CHAR16));
              }

              gST->ConOut->SetAttribute (gST->ConOut, GetFieldTextColor ());
              for (Loop = 0; Loop < EraseLen; Loop++) {
                PrintStringAt (MenuOption->OptCol + Loop, MenuOption->Row, L" ");
              }

              gST->ConOut->SetAttribute (gST->ConOut, GetHighlightTextColor ());

              if (MenuOption->Sequence == 0) {
                PrintCharAt (MenuOption->OptCol, Row, LEFT_NUMERIC_DELIMITER);
                Column = MenuOption->OptCol + 1;
              }

              PrintStringAt (Column, Row, FormattedNumber);

              if (!DateOrTime || (MenuOption->Sequence == 2)) {
                PrintCharAt ((UINTN)-1, (UINTN)-1, RIGHT_NUMERIC_DELIMITER);
              }
            }

            goto EnterCarriageReturn;

          case SCAN_UP:
          case SCAN_DOWN:
            goto EnterCarriageReturn;

          case SCAN_ESC:
            return EFI_DEVICE_ERROR;

          default:
            break;
        }

        break;

EnterCarriageReturn:

      case CHAR_CARRIAGE_RETURN:
        //
        // Validate input value with Minimum value.
        //
        ValidateFail = FALSE;
        if (IntInput) {
          //
          // After user input Enter, need to check whether the input value.
          // If input a negative value, should compare with maximum value.
          // else compare with the minimum value.
          //
          if (Negative) {
            ValidateFail = (INT64)EditValue > (INT64)Maximum ? TRUE : FALSE;
          } else {
            ValidateFail = (INT64)EditValue < (INT64)Minimum ? TRUE : FALSE;
          }

          if (ValidateFail) {
            UpdateStatusBar (INPUT_ERROR, TRUE);
            break;
          }
        } else if (EditValue < Minimum) {
          UpdateStatusBar (INPUT_ERROR, TRUE);
          break;
        }

        UpdateStatusBar (INPUT_ERROR, FALSE);
        CopyMem (&gUserInput->InputValue, &Question->CurrentValue, sizeof (EFI_HII_VALUE));
        QuestionValue = &gUserInput->InputValue;
        //
        // Store Edit value back to Question
        //
        if (Question->OpCode->OpCode == EFI_IFR_DATE_OP) {
          switch (MenuOption->Sequence) {
            case 0:
              QuestionValue->Value.date.Month = (UINT8)EditValue;
              break;

            case 1:
              QuestionValue->Value.date.Day = (UINT8)EditValue;
              break;

            case 2:
              QuestionValue->Value.date.Year = (UINT16)EditValue;
              break;

            default:
              break;
          }
        } else if (Question->OpCode->OpCode  == EFI_IFR_TIME_OP) {
          switch (MenuOption->Sequence) {
            case 0:
              QuestionValue->Value.time.Hour = (UINT8)EditValue;
              break;

            case 1:
              QuestionValue->Value.time.Minute = (UINT8)EditValue;
              break;

            case 2:
              QuestionValue->Value.time.Second = (UINT8)EditValue;
              break;

            default:
              break;
          }
        } else {
          //
          // Numeric
          //
          QuestionValue->Value.u64 = EditValue;
        }

        //
        // Adjust the value to the correct one.
        // Sample like: 2012.02.29 -> 2013.02.29 -> 2013.02.01
        //              2013.03.29 -> 2013.02.29 -> 2013.02.28
        //
        if ((Question->OpCode->OpCode  == EFI_IFR_DATE_OP) &&
            ((MenuOption->Sequence == 0) || (MenuOption->Sequence == 2)))
        {
          AdjustQuestionValue (QuestionValue, (UINT8)MenuOption->Sequence);
        }

        return EFI_SUCCESS;

      case CHAR_BACKSPACE:
        if (ManualInput) {
          if (Count == 0) {
            if (Negative) {
              Negative = FALSE;
              Column--;
              PrintStringAt (Column, Row, L" ");
            }

            break;
          }

          //
          // Remove a character
          //
          EditValue = PreviousNumber[Count - 1];
          UpdateStatusBar (INPUT_ERROR, FALSE);
          Count--;
          Column--;
          PrintStringAt (Column, Row, L" ");
        }

        break;

      default:
        if (ManualInput) {
          if (HexInput) {
            if ((Key.UnicodeChar >= L'0') && (Key.UnicodeChar <= L'9')) {
              Digital = (UINT8)(Key.UnicodeChar - L'0');
            } else if ((Key.UnicodeChar >= L'A') && (Key.UnicodeChar <= L'F')) {
              Digital = (UINT8)(Key.UnicodeChar - L'A' + 0x0A);
            } else if ((Key.UnicodeChar >= L'a') && (Key.UnicodeChar <= L'f')) {
              Digital = (UINT8)(Key.UnicodeChar - L'a' + 0x0A);
            } else {
              UpdateStatusBar (INPUT_ERROR, TRUE);
              break;
            }
          } else {
            if ((Key.UnicodeChar > L'9') || (Key.UnicodeChar < L'0')) {
              UpdateStatusBar (INPUT_ERROR, TRUE);
              break;
            }
          }

          //
          // If Count exceed input width, there is no way more is valid
          //
          if (Count >= InputWidth) {
            break;
          }

          //
          // Someone typed something valid!
          //
          if (Count != 0) {
            if (HexInput) {
              EditValue = LShiftU64 (EditValue, 4) + Digital;
            } else if (IntInput && Negative) {
              //
              // Save the negative number.
              //
              EditValue = ~(MultU64x32 (~(EditValue - 1), 10) + (Key.UnicodeChar - L'0')) + 1;
            } else {
              EditValue = MultU64x32 (EditValue, 10) + (Key.UnicodeChar - L'0');
            }
          } else {
            if (HexInput) {
              EditValue = Digital;
            } else if (IntInput && Negative) {
              //
              // Save the negative number.
              //
              EditValue = ~(Key.UnicodeChar - L'0') + 1;
            } else {
              EditValue = Key.UnicodeChar - L'0';
            }
          }

          if (IntInput) {
            ValidateFail = FALSE;
            //
            // When user input a new value, should check the current value.
            // If user input a negative value, should compare it with minimum
            // value, else compare it with maximum value.
            //
            if (Negative) {
              ValidateFail = (INT64)EditValue < (INT64)Minimum ? TRUE : FALSE;
            } else {
              ValidateFail = (INT64)EditValue > (INT64)Maximum ? TRUE : FALSE;
            }

            if (ValidateFail) {
              UpdateStatusBar (INPUT_ERROR, TRUE);
              ASSERT (Count < ARRAY_SIZE (PreviousNumber));
              EditValue = PreviousNumber[Count];
              break;
            }
          } else {
            if (EditValue > Maximum) {
              UpdateStatusBar (INPUT_ERROR, TRUE);
              ASSERT (Count < ARRAY_SIZE (PreviousNumber));
              EditValue = PreviousNumber[Count];
              break;
            }
          }

          UpdateStatusBar (INPUT_ERROR, FALSE);

          Count++;
          ASSERT (Count < (ARRAY_SIZE (PreviousNumber)));
          PreviousNumber[Count] = EditValue;

          gST->ConOut->SetAttribute (gST->ConOut, GetHighlightTextColor ());
          PrintCharAt (Column, Row, Key.UnicodeChar);
          Column++;
        }

        break;
    }
  } while (TRUE);
}

/**
  Adjust option order base on the question value.

  @param  Question           Pointer to current question.
  @param  PopUpMenuLines     The line number of the pop up menu.

  @retval EFI_SUCCESS       If Option input is processed successfully
  @retval EFI_DEVICE_ERROR  If operation fails

**/
EFI_STATUS
AdjustOptionOrder (
  IN  FORM_DISPLAY_ENGINE_STATEMENT  *Question,
  OUT UINTN                          *PopUpMenuLines
  )
{
  UINTN                    Index;
  EFI_IFR_ORDERED_LIST     *OrderList;
  UINT8                    *ValueArray;
  UINT8                    ValueType;
  LIST_ENTRY               *Link;
  DISPLAY_QUESTION_OPTION  *OneOfOption;
  EFI_HII_VALUE            *HiiValueArray;

  Link        = GetFirstNode (&Question->OptionListHead);
  OneOfOption = DISPLAY_QUESTION_OPTION_FROM_LINK (Link);
  ValueArray  = Question->CurrentValue.Buffer;
  ValueType   =  OneOfOption->OptionOpCode->Type;
  OrderList   = (EFI_IFR_ORDERED_LIST *)Question->OpCode;

  for (Index = 0; Index < OrderList->MaxContainers; Index++) {
    if (GetArrayData (ValueArray, ValueType, Index) == 0) {
      break;
    }
  }

  *PopUpMenuLines = Index;

  //
  // Prepare HiiValue array
  //
  HiiValueArray = AllocateZeroPool (*PopUpMenuLines * sizeof (EFI_HII_VALUE));
  ASSERT (HiiValueArray != NULL);

  for (Index = 0; Index < *PopUpMenuLines; Index++) {
    HiiValueArray[Index].Type      = ValueType;
    HiiValueArray[Index].Value.u64 = GetArrayData (ValueArray, ValueType, Index);
  }

  for (Index = 0; Index < *PopUpMenuLines; Index++) {
    OneOfOption = ValueToOption (Question, &HiiValueArray[*PopUpMenuLines - Index - 1]);
    if (OneOfOption == NULL) {
      return EFI_NOT_FOUND;
    }

    RemoveEntryList (&OneOfOption->Link);

    //
    // Insert to head.
    //
    InsertHeadList (&Question->OptionListHead, &OneOfOption->Link);
  }

  FreePool (HiiValueArray);

  return EFI_SUCCESS;
}

/**
  Base on the type to compare the value.

  @param  Value1                The first value need to compare.
  @param  Value2                The second value need to compare.
  @param  Type                  The value type for above two values.

  @retval TRUE                  The two value are same.
  @retval FALSE                 The two value are different.

**/
BOOLEAN
IsValuesEqual (
  IN EFI_IFR_TYPE_VALUE  *Value1,
  IN EFI_IFR_TYPE_VALUE  *Value2,
  IN UINT8               Type
  )
{
  switch (Type) {
    case EFI_IFR_TYPE_BOOLEAN:
    case EFI_IFR_TYPE_NUM_SIZE_8:
      return (BOOLEAN)(Value1->u8 == Value2->u8);

    case EFI_IFR_TYPE_NUM_SIZE_16:
      return (BOOLEAN)(Value1->u16 == Value2->u16);

    case EFI_IFR_TYPE_NUM_SIZE_32:
      return (BOOLEAN)(Value1->u32 == Value2->u32);

    case EFI_IFR_TYPE_NUM_SIZE_64:
      return (BOOLEAN)(Value1->u64 == Value2->u64);

    default:
      ASSERT (FALSE);
      return FALSE;
  }
}

/**
  Base on the type to set the value.

  @param  Dest                  The dest value.
  @param  Source                The source value.
  @param  Type                  The value type for above two values.

**/
VOID
SetValuesByType (
  OUT EFI_IFR_TYPE_VALUE  *Dest,
  IN  EFI_IFR_TYPE_VALUE  *Source,
  IN  UINT8               Type
  )
{
  switch (Type) {
    case EFI_IFR_TYPE_BOOLEAN:
      Dest->b = Source->b;
      break;

    case EFI_IFR_TYPE_NUM_SIZE_8:
      Dest->u8 = Source->u8;
      break;

    case EFI_IFR_TYPE_NUM_SIZE_16:
      Dest->u16 = Source->u16;
      break;

    case EFI_IFR_TYPE_NUM_SIZE_32:
      Dest->u32 = Source->u32;
      break;

    case EFI_IFR_TYPE_NUM_SIZE_64:
      Dest->u64 = Source->u64;
      break;

    default:
      ASSERT (FALSE);
      break;
  }
}

/**
  Get selection for OneOf and OrderedList (Left/Right will be ignored).

  @param  MenuOption        Pointer to the current input menu.

  @retval EFI_SUCCESS       If Option input is processed successfully
  @retval EFI_DEVICE_ERROR  If operation fails

**/
EFI_STATUS
GetSelectionInputPopUp (
  IN  UI_MENU_OPTION  *MenuOption
  )
{
  EFI_INPUT_KEY                  Key;
  UINTN                          Index;
  CHAR16                         *StringPtr;
  CHAR16                         *TempStringPtr;
  UINTN                          Index2;
  UINTN                          TopOptionIndex;
  UINTN                          HighlightOptionIndex;
  UINTN                          Start;
  UINTN                          End;
  UINTN                          Top;
  UINTN                          Bottom;
  UINTN                          PopUpMenuLines;
  UINTN                          MenuLinesInView;
  UINTN                          PopUpWidth;
  CHAR16                         Character;
  INT32                          SavedAttribute;
  BOOLEAN                        ShowDownArrow;
  BOOLEAN                        ShowUpArrow;
  UINTN                          DimensionsWidth;
  LIST_ENTRY                     *Link;
  BOOLEAN                        OrderedList;
  UINT8                          *ValueArray;
  UINT8                          *ReturnValue;
  UINT8                          ValueType;
  EFI_HII_VALUE                  HiiValue;
  DISPLAY_QUESTION_OPTION        *OneOfOption;
  DISPLAY_QUESTION_OPTION        *CurrentOption;
  FORM_DISPLAY_ENGINE_STATEMENT  *Question;
  INTN                           Result;
  EFI_IFR_ORDERED_LIST           *OrderList;

  DimensionsWidth = gStatementDimensions.RightColumn - gStatementDimensions.LeftColumn;

  ValueArray    = NULL;
  ValueType     = 0;
  CurrentOption = NULL;
  ShowDownArrow = FALSE;
  ShowUpArrow   = FALSE;

  ZeroMem (&HiiValue, sizeof (EFI_HII_VALUE));

  Question = MenuOption->ThisTag;
  if (Question->OpCode->OpCode == EFI_IFR_ORDERED_LIST_OP) {
    Link        = GetFirstNode (&Question->OptionListHead);
    OneOfOption = DISPLAY_QUESTION_OPTION_FROM_LINK (Link);
    ValueArray  = Question->CurrentValue.Buffer;
    ValueType   =  OneOfOption->OptionOpCode->Type;
    OrderedList = TRUE;
    OrderList   = (EFI_IFR_ORDERED_LIST *)Question->OpCode;
  } else {
    OrderedList = FALSE;
    OrderList   = NULL;
  }

  //
  // Calculate Option count
  //
  PopUpMenuLines = 0;
  if (OrderedList) {
    AdjustOptionOrder (Question, &PopUpMenuLines);
  } else {
    Link = GetFirstNode (&Question->OptionListHead);
    while (!IsNull (&Question->OptionListHead, Link)) {
      OneOfOption = DISPLAY_QUESTION_OPTION_FROM_LINK (Link);
      PopUpMenuLines++;
      Link = GetNextNode (&Question->OptionListHead, Link);
    }
  }

  //
  // Get the number of one of options present and its size
  //
  PopUpWidth           = 0;
  HighlightOptionIndex = 0;
  Link                 = GetFirstNode (&Question->OptionListHead);
  for (Index = 0; Index < PopUpMenuLines; Index++) {
    OneOfOption = DISPLAY_QUESTION_OPTION_FROM_LINK (Link);

    StringPtr = GetToken (OneOfOption->OptionOpCode->Option, gFormData->HiiHandle);
    if (StrLen (StringPtr) > PopUpWidth) {
      PopUpWidth = StrLen (StringPtr);
    }

    FreePool (StringPtr);
    HiiValue.Type = OneOfOption->OptionOpCode->Type;
    SetValuesByType (&HiiValue.Value, &OneOfOption->OptionOpCode->Value, HiiValue.Type);
    if (!OrderedList && (CompareHiiValue (&Question->CurrentValue, &HiiValue, &Result, NULL) == EFI_SUCCESS) && (Result == 0)) {
      //
      // Find current selected Option for OneOf
      //
      HighlightOptionIndex = Index;
    }

    Link = GetNextNode (&Question->OptionListHead, Link);
  }

  //
  // Perform popup menu initialization.
  //
  PopUpWidth = PopUpWidth + POPUP_PAD_SPACE_COUNT;

  SavedAttribute = gST->ConOut->Mode->Attribute;
  gST->ConOut->SetAttribute (gST->ConOut, GetPopupColor ());

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

  Start  = (DimensionsWidth - PopUpWidth - POPUP_FRAME_WIDTH) / 2 + gStatementDimensions.LeftColumn;
  End    = Start + PopUpWidth + POPUP_FRAME_WIDTH;
  Top    = gStatementDimensions.TopRow;
  Bottom = gStatementDimensions.BottomRow - 1;

  MenuLinesInView = Bottom - Top - 1;
  if (MenuLinesInView >= PopUpMenuLines) {
    Top    = Top + (MenuLinesInView - PopUpMenuLines) / 2;
    Bottom = Top + PopUpMenuLines + 1;
  } else {
    ShowDownArrow = TRUE;
  }

  if (HighlightOptionIndex > (MenuLinesInView - 1)) {
    TopOptionIndex = HighlightOptionIndex - MenuLinesInView + 1;
  } else {
    TopOptionIndex = 0;
  }

  do {
    //
    // Clear that portion of the screen
    //
    ClearLines (Start, End, Top, Bottom, GetPopupColor ());

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

      PrintCharAt ((UINTN)-1, (UINTN)-1, Character);
    }

    Character = BOXDRAW_DOWN_LEFT;
    PrintCharAt ((UINTN)-1, (UINTN)-1, Character);
    Character = BOXDRAW_VERTICAL;
    for (Index = Top + 1; Index < Bottom; Index++) {
      PrintCharAt (Start, Index, Character);
      PrintCharAt (End - 1, Index, Character);
    }

    //
    // Move to top Option
    //
    Link = GetFirstNode (&Question->OptionListHead);
    for (Index = 0; Index < TopOptionIndex; Index++) {
      Link = GetNextNode (&Question->OptionListHead, Link);
    }

    //
    // Display the One of options
    //
    Index2 = Top + 1;
    for (Index = TopOptionIndex; (Index < PopUpMenuLines) && (Index2 < Bottom); Index++) {
      OneOfOption = DISPLAY_QUESTION_OPTION_FROM_LINK (Link);
      Link        = GetNextNode (&Question->OptionListHead, Link);

      StringPtr = GetToken (OneOfOption->OptionOpCode->Option, gFormData->HiiHandle);
      ASSERT (StringPtr != NULL);
      //
      // 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)));
        FreePool (StringPtr);
        StringPtr = TempStringPtr;
        StrCatS (StringPtr, PopUpWidth - 1, L"...");
      }

      if (Index == HighlightOptionIndex) {
        //
        // Highlight the selected one
        //
        CurrentOption = OneOfOption;

        gST->ConOut->SetAttribute (gST->ConOut, GetPickListColor ());
        PrintStringAt (Start + 2, Index2, StringPtr);
        gST->ConOut->SetAttribute (gST->ConOut, GetPopupColor ());
      } else {
        gST->ConOut->SetAttribute (gST->ConOut, GetPopupColor ());
        PrintStringAt (Start + 2, Index2, StringPtr);
      }

      Index2++;
      FreePool (StringPtr);
    }

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

      PrintCharAt ((UINTN)-1, (UINTN)-1, Character);
    }

    Character = BOXDRAW_UP_LEFT;
    PrintCharAt ((UINTN)-1, (UINTN)-1, Character);

    //
    // Get User selection
    //
    Key.UnicodeChar = CHAR_NULL;
    if ((gDirection == SCAN_UP) || (gDirection == SCAN_DOWN)) {
      Key.ScanCode = gDirection;
      gDirection   = 0;
      goto TheKey;
    }

    WaitForKeyStroke (&Key);

TheKey:
    switch (Key.UnicodeChar) {
      case '+':
        if (OrderedList) {
          if ((TopOptionIndex > 0) && (TopOptionIndex == HighlightOptionIndex)) {
            //
            // Highlight reaches the top of the popup window, scroll one menu item.
            //
            TopOptionIndex--;
            ShowDownArrow = TRUE;
          }

          if (TopOptionIndex == 0) {
            ShowUpArrow = FALSE;
          }

          if (HighlightOptionIndex > 0) {
            HighlightOptionIndex--;

            ASSERT (CurrentOption != NULL);
            SwapListEntries (CurrentOption->Link.BackLink, &CurrentOption->Link);
          }
        }

        break;

      case '-':
        //
        // If an ordered list op-code, we will allow for a popup of +/- keys
        // to create an ordered list of items
        //
        if (OrderedList) {
          if (((TopOptionIndex + MenuLinesInView) < PopUpMenuLines) &&
              (HighlightOptionIndex == (TopOptionIndex + MenuLinesInView - 1)))
          {
            //
            // Highlight reaches the bottom of the popup window, scroll one menu item.
            //
            TopOptionIndex++;
            ShowUpArrow = TRUE;
          }

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

          if (HighlightOptionIndex < (PopUpMenuLines - 1)) {
            HighlightOptionIndex++;

            ASSERT (CurrentOption != NULL);
            SwapListEntries (&CurrentOption->Link, CurrentOption->Link.ForwardLink);
          }
        }

        break;

      case '^':
        if ((TopOptionIndex > 0) && (TopOptionIndex == HighlightOptionIndex)) {
          //
          // Highlight reaches the top of the popup window, scroll one menu item.
          //
          TopOptionIndex--;
          ShowDownArrow = TRUE;
        }

        if (TopOptionIndex == 0) {
          ShowUpArrow = FALSE;
        }

        if (HighlightOptionIndex > 0) {
          HighlightOptionIndex--;
        }

        break;

      case 'V':
      case 'v':
        if (((TopOptionIndex + MenuLinesInView) < PopUpMenuLines) &&
            (HighlightOptionIndex == (TopOptionIndex + MenuLinesInView - 1)))
        {
          //
          // Highlight reaches the bottom of the popup window, scroll one menu item.
          //
          TopOptionIndex++;
          ShowUpArrow = TRUE;
        }

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

        if (HighlightOptionIndex < (PopUpMenuLines - 1)) {
          HighlightOptionIndex++;
        }

        break;

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

              if (TopOptionIndex == 0) {
                ShowUpArrow = FALSE;
              }

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

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

              if (HighlightOptionIndex < (PopUpMenuLines - 1)) {
                HighlightOptionIndex++;
              }
            }

            break;

          case SCAN_ESC:
            gST->ConOut->SetAttribute (gST->ConOut, SavedAttribute);

            //
            // Restore link list order for orderedlist
            //
            if (OrderedList) {
              HiiValue.Type      = ValueType;
              HiiValue.Value.u64 = 0;
              for (Index = 0; Index < OrderList->MaxContainers; Index++) {
                HiiValue.Value.u64 = GetArrayData (ValueArray, ValueType, Index);
                if (HiiValue.Value.u64 == 0) {
                  break;
                }

                OneOfOption = ValueToOption (Question, &HiiValue);
                if (OneOfOption == NULL) {
                  return EFI_NOT_FOUND;
                }

                RemoveEntryList (&OneOfOption->Link);
                InsertTailList (&Question->OptionListHead, &OneOfOption->Link);
              }
            }

            return EFI_DEVICE_ERROR;

          default:
            break;
        }

        break;

      case CHAR_CARRIAGE_RETURN:
        //
        // return the current selection
        //
        if (OrderedList) {
          ReturnValue = AllocateZeroPool (Question->CurrentValue.BufferLen);
          ASSERT (ReturnValue != NULL);
          Index = 0;
          Link  = GetFirstNode (&Question->OptionListHead);
          while (!IsNull (&Question->OptionListHead, Link)) {
            OneOfOption = DISPLAY_QUESTION_OPTION_FROM_LINK (Link);
            Link        = GetNextNode (&Question->OptionListHead, Link);

            SetArrayData (ReturnValue, ValueType, Index, OneOfOption->OptionOpCode->Value.u64);

            Index++;
            if (Index > OrderList->MaxContainers) {
              break;
            }
          }

          if (CompareMem (ReturnValue, ValueArray, Question->CurrentValue.BufferLen) == 0) {
            FreePool (ReturnValue);
            return EFI_DEVICE_ERROR;
          } else {
            gUserInput->InputValue.Buffer    = ReturnValue;
            gUserInput->InputValue.BufferLen = Question->CurrentValue.BufferLen;
          }
        } else {
          ASSERT (CurrentOption != NULL);
          gUserInput->InputValue.Type = CurrentOption->OptionOpCode->Type;
          if (IsValuesEqual (&Question->CurrentValue.Value, &CurrentOption->OptionOpCode->Value, gUserInput->InputValue.Type)) {
            return EFI_DEVICE_ERROR;
          } else {
            SetValuesByType (&gUserInput->InputValue.Value, &CurrentOption->OptionOpCode->Value, gUserInput->InputValue.Type);
          }
        }

        gST->ConOut->SetAttribute (gST->ConOut, SavedAttribute);

        return EFI_SUCCESS;

      default:
        break;
    }
  } while (TRUE);
}
