/** @file
Entry and initialization module for the browser.

Copyright (c) 2007 - 2018, Intel Corporation. All rights reserved.<BR>
Copyright (c) 2014, Hewlett-Packard Development Company, L.P.<BR>
SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include "FormDisplay.h"

//
// Search table for UiDisplayMenu()
//
SCAN_CODE_TO_SCREEN_OPERATION  gScanCodeToOperation[] = {
  {
    SCAN_UP,
    UiUp,
  },
  {
    SCAN_DOWN,
    UiDown,
  },
  {
    SCAN_PAGE_UP,
    UiPageUp,
  },
  {
    SCAN_PAGE_DOWN,
    UiPageDown,
  },
  {
    SCAN_ESC,
    UiReset,
  },
  {
    SCAN_LEFT,
    UiLeft,
  },
  {
    SCAN_RIGHT,
    UiRight,
  }
};

UINTN  mScanCodeNumber = ARRAY_SIZE (gScanCodeToOperation);

SCREEN_OPERATION_T0_CONTROL_FLAG  gScreenOperationToControlFlag[] = {
  {
    UiNoOperation,
    CfUiNoOperation,
  },
  {
    UiSelect,
    CfUiSelect,
  },
  {
    UiUp,
    CfUiUp,
  },
  {
    UiDown,
    CfUiDown,
  },
  {
    UiLeft,
    CfUiLeft,
  },
  {
    UiRight,
    CfUiRight,
  },
  {
    UiReset,
    CfUiReset,
  },
  {
    UiPageUp,
    CfUiPageUp,
  },
  {
    UiPageDown,
    CfUiPageDown
  },
  {
    UiHotKey,
    CfUiHotKey
  }
};

EFI_GUID  gDisplayEngineGuid = {
  0xE38C1029, 0xE38F, 0x45b9, { 0x8F, 0x0D, 0xE2, 0xE6, 0x0B, 0xC9, 0xB2, 0x62 }
};

BOOLEAN                      gMisMatch;
EFI_SCREEN_DESCRIPTOR        gStatementDimensions;
BOOLEAN                      mStatementLayoutIsChanged = TRUE;
USER_INPUT                   *gUserInput;
FORM_DISPLAY_ENGINE_FORM     *gFormData;
EFI_HII_HANDLE               gHiiHandle;
UINT16                       gDirection;
LIST_ENTRY                   gMenuOption;
DISPLAY_HIGHLIGHT_MENU_INFO  gHighligthMenuInfo = { 0 };
BOOLEAN                      mIsFirstForm       = TRUE;
FORM_ENTRY_INFO              gOldFormEntry      = { 0 };

//
// Browser Global Strings
//
CHAR16  *gReconnectConfirmChanges;
CHAR16  *gReconnectFail;
CHAR16  *gReconnectRequired;
CHAR16  *gChangesOpt;
CHAR16  *gFormNotFound;
CHAR16  *gNoSubmitIf;
CHAR16  *gBrowserError;
CHAR16  *gSaveFailed;
CHAR16  *gNoSubmitIfFailed;
CHAR16  *gSaveProcess;
CHAR16  *gSaveNoSubmitProcess;
CHAR16  *gDiscardChange;
CHAR16  *gJumpToFormSet;
CHAR16  *gCheckError;
CHAR16  *gPromptForData;
CHAR16  *gPromptForPassword;
CHAR16  *gPromptForNewPassword;
CHAR16  *gConfirmPassword;
CHAR16  *gConfirmError;
CHAR16  *gPassowordInvalid;
CHAR16  *gPressEnter;
CHAR16  *gEmptyString;
CHAR16  *gMiniString;
CHAR16  *gOptionMismatch;
CHAR16  *gFormSuppress;
CHAR16  *gProtocolNotFound;
CHAR16  *gConfirmDefaultMsg;
CHAR16  *gConfirmSubmitMsg;
CHAR16  *gConfirmDiscardMsg;
CHAR16  *gConfirmResetMsg;
CHAR16  *gConfirmExitMsg;
CHAR16  *gConfirmSubmitMsg2nd;
CHAR16  *gConfirmDefaultMsg2nd;
CHAR16  *gConfirmResetMsg2nd;
CHAR16  *gConfirmExitMsg2nd;
CHAR16  *gConfirmOpt;
CHAR16  *gConfirmOptYes;
CHAR16  *gConfirmOptNo;
CHAR16  *gConfirmOptOk;
CHAR16  *gConfirmOptCancel;
CHAR16  *gYesOption;
CHAR16  *gNoOption;
CHAR16  *gOkOption;
CHAR16  *gCancelOption;
CHAR16  *gErrorPopup;
CHAR16  *gWarningPopup;
CHAR16  *gInfoPopup;
CHAR16  *gConfirmMsgConnect;
CHAR16  *gConfirmMsgEnd;
CHAR16  *gPasswordUnsupported;
CHAR16  gModalSkipColumn;
CHAR16  gPromptBlockWidth;
CHAR16  gOptionBlockWidth;
CHAR16  gHelpBlockWidth;
CHAR16  *mUnknownString;

FORM_DISPLAY_DRIVER_PRIVATE_DATA  mPrivateData = {
  FORM_DISPLAY_DRIVER_SIGNATURE,
  NULL,
  {
    FormDisplay,
    DriverClearDisplayPage,
    ConfirmDataChange
  },
  {
    EFI_HII_POPUP_PROTOCOL_REVISION,
    CreatePopup
  }
};

/**
  Get the string based on the StringId and HII Package List Handle.

  @param  Token                  The String's ID.
  @param  HiiHandle              The package list in the HII database to search for
                                 the specified string.

  @return The output string.

**/
CHAR16 *
GetToken (
  IN  EFI_STRING_ID   Token,
  IN  EFI_HII_HANDLE  HiiHandle
  )
{
  EFI_STRING  String;

  String = HiiGetString (HiiHandle, Token, NULL);
  if (String == NULL) {
    String = AllocateCopyPool (StrSize (mUnknownString), mUnknownString);
    ASSERT (String != NULL);
  }

  return (CHAR16 *)String;
}

/**
  Initialize the HII String Token to the correct values.

**/
VOID
InitializeDisplayStrings (
  VOID
  )
{
  gReconnectConfirmChanges = GetToken (STRING_TOKEN (RECONNECT_CONFIRM_CHANGES), gHiiHandle);
  mUnknownString           = GetToken (STRING_TOKEN (UNKNOWN_STRING), gHiiHandle);
  gSaveFailed              = GetToken (STRING_TOKEN (SAVE_FAILED), gHiiHandle);
  gNoSubmitIfFailed        = GetToken (STRING_TOKEN (NO_SUBMIT_IF_CHECK_FAILED), gHiiHandle);
  gReconnectFail           = GetToken (STRING_TOKEN (RECONNECT_FAILED), gHiiHandle);
  gReconnectRequired       = GetToken (STRING_TOKEN (RECONNECT_REQUIRED), gHiiHandle);
  gChangesOpt              = GetToken (STRING_TOKEN (RECONNECT_CHANGES_OPTIONS), gHiiHandle);
  gSaveProcess             = GetToken (STRING_TOKEN (DISCARD_OR_JUMP), gHiiHandle);
  gSaveNoSubmitProcess     = GetToken (STRING_TOKEN (DISCARD_OR_CHECK), gHiiHandle);
  gDiscardChange           = GetToken (STRING_TOKEN (DISCARD_OR_JUMP_DISCARD), gHiiHandle);
  gJumpToFormSet           = GetToken (STRING_TOKEN (DISCARD_OR_JUMP_JUMP), gHiiHandle);
  gCheckError              = GetToken (STRING_TOKEN (DISCARD_OR_CHECK_CHECK), gHiiHandle);
  gPromptForData           = GetToken (STRING_TOKEN (PROMPT_FOR_DATA), gHiiHandle);
  gPromptForPassword       = GetToken (STRING_TOKEN (PROMPT_FOR_PASSWORD), gHiiHandle);
  gPromptForNewPassword    = GetToken (STRING_TOKEN (PROMPT_FOR_NEW_PASSWORD), gHiiHandle);
  gConfirmPassword         = GetToken (STRING_TOKEN (CONFIRM_PASSWORD), gHiiHandle);
  gConfirmError            = GetToken (STRING_TOKEN (CONFIRM_ERROR), gHiiHandle);
  gPassowordInvalid        = GetToken (STRING_TOKEN (PASSWORD_INVALID), gHiiHandle);
  gPressEnter              = GetToken (STRING_TOKEN (PRESS_ENTER), gHiiHandle);
  gEmptyString             = GetToken (STRING_TOKEN (EMPTY_STRING), gHiiHandle);
  gMiniString              = GetToken (STRING_TOKEN (MINI_STRING), gHiiHandle);
  gOptionMismatch          = GetToken (STRING_TOKEN (OPTION_MISMATCH), gHiiHandle);
  gFormSuppress            = GetToken (STRING_TOKEN (FORM_SUPPRESSED), gHiiHandle);
  gProtocolNotFound        = GetToken (STRING_TOKEN (PROTOCOL_NOT_FOUND), gHiiHandle);
  gFormNotFound            = GetToken (STRING_TOKEN (STATUS_BROWSER_FORM_NOT_FOUND), gHiiHandle);
  gNoSubmitIf              = GetToken (STRING_TOKEN (STATUS_BROWSER_NO_SUBMIT_IF), gHiiHandle);
  gBrowserError            = GetToken (STRING_TOKEN (STATUS_BROWSER_ERROR), gHiiHandle);
  gConfirmDefaultMsg       = GetToken (STRING_TOKEN (CONFIRM_DEFAULT_MESSAGE), gHiiHandle);
  gConfirmDiscardMsg       = GetToken (STRING_TOKEN (CONFIRM_DISCARD_MESSAGE), gHiiHandle);
  gConfirmSubmitMsg        = GetToken (STRING_TOKEN (CONFIRM_SUBMIT_MESSAGE), gHiiHandle);
  gConfirmResetMsg         = GetToken (STRING_TOKEN (CONFIRM_RESET_MESSAGE), gHiiHandle);
  gConfirmExitMsg          = GetToken (STRING_TOKEN (CONFIRM_EXIT_MESSAGE), gHiiHandle);
  gConfirmDefaultMsg2nd    = GetToken (STRING_TOKEN (CONFIRM_DEFAULT_MESSAGE_2ND), gHiiHandle);
  gConfirmSubmitMsg2nd     = GetToken (STRING_TOKEN (CONFIRM_SUBMIT_MESSAGE_2ND), gHiiHandle);
  gConfirmResetMsg2nd      = GetToken (STRING_TOKEN (CONFIRM_RESET_MESSAGE_2ND), gHiiHandle);
  gConfirmExitMsg2nd       = GetToken (STRING_TOKEN (CONFIRM_EXIT_MESSAGE_2ND), gHiiHandle);
  gConfirmOpt              = GetToken (STRING_TOKEN (CONFIRM_OPTION), gHiiHandle);
  gConfirmOptYes           = GetToken (STRING_TOKEN (CONFIRM_OPTION_YES), gHiiHandle);
  gConfirmOptNo            = GetToken (STRING_TOKEN (CONFIRM_OPTION_NO), gHiiHandle);
  gConfirmOptOk            = GetToken (STRING_TOKEN (CONFIRM_OPTION_OK), gHiiHandle);
  gConfirmOptCancel        = GetToken (STRING_TOKEN (CONFIRM_OPTION_CANCEL), gHiiHandle);
  gYesOption               = GetToken (STRING_TOKEN (YES_SELECTABLE_OPTION), gHiiHandle);
  gNoOption                = GetToken (STRING_TOKEN (NO_SELECTABLE_OPTION), gHiiHandle);
  gOkOption                = GetToken (STRING_TOKEN (OK_SELECTABLE_OPTION), gHiiHandle);
  gCancelOption            = GetToken (STRING_TOKEN (CANCEL_SELECTABLE_OPTION), gHiiHandle);
  gErrorPopup              = GetToken (STRING_TOKEN (ERROR_POPUP_STRING), gHiiHandle);
  gWarningPopup            = GetToken (STRING_TOKEN (WARNING_POPUP_STRING), gHiiHandle);
  gInfoPopup               = GetToken (STRING_TOKEN (INFO_POPUP_STRING), gHiiHandle);
  gConfirmMsgConnect       = GetToken (STRING_TOKEN (CONFIRM_OPTION_CONNECT), gHiiHandle);
  gConfirmMsgEnd           = GetToken (STRING_TOKEN (CONFIRM_OPTION_END), gHiiHandle);
  gPasswordUnsupported     = GetToken (STRING_TOKEN (PASSWORD_NOT_SUPPORTED), gHiiHandle);
}

/**
  Free up the resource allocated for all strings required
  by Setup Browser.

**/
VOID
FreeDisplayStrings (
  VOID
  )
{
  FreePool (mUnknownString);
  FreePool (gEmptyString);
  FreePool (gSaveFailed);
  FreePool (gNoSubmitIfFailed);
  FreePool (gReconnectFail);
  FreePool (gReconnectRequired);
  FreePool (gChangesOpt);
  FreePool (gReconnectConfirmChanges);
  FreePool (gSaveProcess);
  FreePool (gSaveNoSubmitProcess);
  FreePool (gDiscardChange);
  FreePool (gJumpToFormSet);
  FreePool (gCheckError);
  FreePool (gPromptForData);
  FreePool (gPromptForPassword);
  FreePool (gPromptForNewPassword);
  FreePool (gConfirmPassword);
  FreePool (gConfirmError);
  FreePool (gPassowordInvalid);
  FreePool (gPressEnter);
  FreePool (gMiniString);
  FreePool (gOptionMismatch);
  FreePool (gFormSuppress);
  FreePool (gProtocolNotFound);
  FreePool (gBrowserError);
  FreePool (gNoSubmitIf);
  FreePool (gFormNotFound);
  FreePool (gConfirmDefaultMsg);
  FreePool (gConfirmSubmitMsg);
  FreePool (gConfirmDiscardMsg);
  FreePool (gConfirmResetMsg);
  FreePool (gConfirmExitMsg);
  FreePool (gConfirmDefaultMsg2nd);
  FreePool (gConfirmSubmitMsg2nd);
  FreePool (gConfirmResetMsg2nd);
  FreePool (gConfirmExitMsg2nd);
  FreePool (gConfirmOpt);
  FreePool (gConfirmOptYes);
  FreePool (gConfirmOptNo);
  FreePool (gConfirmOptOk);
  FreePool (gConfirmOptCancel);
  FreePool (gYesOption);
  FreePool (gNoOption);
  FreePool (gOkOption);
  FreePool (gCancelOption);
  FreePool (gErrorPopup);
  FreePool (gWarningPopup);
  FreePool (gInfoPopup);
  FreePool (gConfirmMsgConnect);
  FreePool (gConfirmMsgEnd);
  FreePool (gPasswordUnsupported);
}

/**
  Get prompt string id from the opcode data buffer.

  @param  OpCode                 The input opcode buffer.

  @return The prompt string id.

**/
EFI_STRING_ID
GetPrompt (
  IN EFI_IFR_OP_HEADER  *OpCode
  )
{
  EFI_IFR_STATEMENT_HEADER  *Header;

  if (OpCode->Length <= sizeof (EFI_IFR_OP_HEADER)) {
    return 0;
  }

  Header = (EFI_IFR_STATEMENT_HEADER  *)(OpCode + 1);

  return Header->Prompt;
}

/**
  Get the supported width for a particular op-code

  @param  MenuOption             The menu option.
  @param  AdjustWidth            The width which is saved for the space.

  @return Returns the number of CHAR16 characters that is support.

**/
UINT16
GetWidth (
  IN  UI_MENU_OPTION  *MenuOption,
  OUT UINT16          *AdjustWidth
  )
{
  CHAR16                         *String;
  UINTN                          Size;
  EFI_IFR_TEXT                   *TextOp;
  UINT16                         ReturnWidth;
  FORM_DISPLAY_ENGINE_STATEMENT  *Statement;

  Statement = MenuOption->ThisTag;

  //
  // For modal form, clean the entire row.
  //
  if ((gFormData->Attribute & HII_DISPLAY_MODAL) != 0) {
    if (AdjustWidth  != NULL) {
      *AdjustWidth = LEFT_SKIPPED_COLUMNS;
    }

    return (UINT16)(gStatementDimensions.RightColumn - gStatementDimensions.LeftColumn - 2 * (gModalSkipColumn + LEFT_SKIPPED_COLUMNS));
  }

  Size = 0;

  //
  // See if the second text parameter is really NULL
  //
  if (Statement->OpCode->OpCode == EFI_IFR_TEXT_OP) {
    TextOp = (EFI_IFR_TEXT *)Statement->OpCode;
    if (TextOp->TextTwo != 0) {
      String = GetToken (TextOp->TextTwo, gFormData->HiiHandle);
      Size   = StrLen (String);
      FreePool (String);
    }
  }

  if ((Statement->OpCode->OpCode == EFI_IFR_SUBTITLE_OP) ||
      (Statement->OpCode->OpCode == EFI_IFR_REF_OP) ||
      (Statement->OpCode->OpCode == EFI_IFR_PASSWORD_OP) ||
      (Statement->OpCode->OpCode == EFI_IFR_ACTION_OP) ||
      (Statement->OpCode->OpCode == EFI_IFR_RESET_BUTTON_OP) ||
      //
      // Allow a wide display if text op-code and no secondary text op-code
      //
      ((Statement->OpCode->OpCode == EFI_IFR_TEXT_OP) && (Size == 0))
      )
  {
    //
    // Return the space width.
    //
    if (AdjustWidth != NULL) {
      *AdjustWidth = 2;
    }

    //
    // Keep consistent with current behavior.
    //
    ReturnWidth = (UINT16)(gPromptBlockWidth + gOptionBlockWidth - 2);
  } else {
    if (AdjustWidth != NULL) {
      *AdjustWidth = 1;
    }

    ReturnWidth =  (UINT16)(gPromptBlockWidth - 1);
  }

  //
  // For nest in statement, should the subtitle indent.
  //
  if (MenuOption->NestInStatement) {
    ReturnWidth -= SUBTITLE_INDENT;
  }

  return ReturnWidth;
}

/**
  Will copy LineWidth amount of a string in the OutputString buffer and return the
  number of CHAR16 characters that were copied into the OutputString buffer.
  The output string format is:
    Glyph Info + String info + '\0'.

  In the code, it deals \r,\n,\r\n same as \n\r, also it not process the \r or \g.

  @param  InputString            String description for this option.
  @param  LineWidth              Width of the desired string to extract in CHAR16
                                 characters
  @param  GlyphWidth             The glyph width of the begin of the char in the string.
  @param  Index                  Where in InputString to start the copy process
  @param  OutputString           Buffer to copy the string into

  @return Returns the number of CHAR16 characters that were copied into the OutputString
  buffer, include extra glyph info and '\0' info.

**/
UINT16
GetLineByWidth (
  IN      CHAR16  *InputString,
  IN      UINT16  LineWidth,
  IN OUT  UINT16  *GlyphWidth,
  IN OUT  UINTN   *Index,
  OUT     CHAR16  **OutputString
  )
{
  UINT16   StrOffset;
  UINT16   GlyphOffset;
  UINT16   OriginalGlyphWidth;
  BOOLEAN  ReturnFlag;
  UINT16   LastSpaceOffset;
  UINT16   LastGlyphWidth;

  if ((InputString == NULL) || (Index == NULL) || (OutputString == NULL)) {
    return 0;
  }

  if ((LineWidth == 0) || (*GlyphWidth == 0)) {
    return 0;
  }

  //
  // Save original glyph width.
  //
  OriginalGlyphWidth = *GlyphWidth;
  LastGlyphWidth     = OriginalGlyphWidth;
  ReturnFlag         = FALSE;
  LastSpaceOffset    = 0;

  //
  // NARROW_CHAR can not be printed in screen, so if a line only contain  the two CHARs: 'NARROW_CHAR + CHAR_CARRIAGE_RETURN' , it is a empty line  in Screen.
  // To avoid displaying this  empty line in screen,  just skip  the two CHARs here.
  //
  if ((InputString[*Index] == NARROW_CHAR) && (InputString[*Index + 1] == CHAR_CARRIAGE_RETURN)) {
    *Index = *Index + 2;
  }

  //
  // Fast-forward the string and see if there is a carriage-return in the string
  //
  for (StrOffset = 0, GlyphOffset = 0; GlyphOffset <= LineWidth; StrOffset++) {
    switch (InputString[*Index + StrOffset]) {
      case NARROW_CHAR:
        *GlyphWidth = 1;
        break;

      case WIDE_CHAR:
        *GlyphWidth = 2;
        break;

      case CHAR_CARRIAGE_RETURN:
      case CHAR_LINEFEED:
      case CHAR_NULL:
        ReturnFlag = TRUE;
        break;

      default:
        GlyphOffset = GlyphOffset + *GlyphWidth;

        //
        // Record the last space info in this line. Will be used in rewind.
        //
        if ((InputString[*Index + StrOffset] == CHAR_SPACE) && (GlyphOffset <= LineWidth)) {
          LastSpaceOffset = StrOffset;
          LastGlyphWidth  = *GlyphWidth;
        }

        break;
    }

    if (ReturnFlag) {
      break;
    }
  }

  //
  // Rewind the string from the maximum size until we see a space to break the line
  //
  if (GlyphOffset > LineWidth) {
    //
    // Rewind the string to last space char in this line.
    //
    if (LastSpaceOffset != 0) {
      StrOffset   = LastSpaceOffset;
      *GlyphWidth = LastGlyphWidth;
    } else {
      //
      // Roll back to last char in the line width.
      //
      StrOffset--;
    }
  }

  //
  // The CHAR_NULL has process last time, this time just return 0 to stand for the end.
  //
  if ((StrOffset == 0) && (InputString[*Index + StrOffset] == CHAR_NULL)) {
    return 0;
  }

  //
  // Need extra glyph info and '\0' info, so +2.
  //
  *OutputString = AllocateZeroPool ((StrOffset + 2) * sizeof (CHAR16));
  if (*OutputString == NULL) {
    return 0;
  }

  //
  // Save the glyph info at the begin of the string, will used by Print function.
  //
  if (OriginalGlyphWidth == 1) {
    *(*OutputString) = NARROW_CHAR;
  } else {
    *(*OutputString) = WIDE_CHAR;
  }

  CopyMem ((*OutputString) + 1, &InputString[*Index], StrOffset * sizeof (CHAR16));

  if (InputString[*Index + StrOffset] == CHAR_SPACE) {
    //
    // Skip the space info at the begin of next line.
    //
    *Index = (UINT16)(*Index + StrOffset + 1);
  } else if (InputString[*Index + StrOffset] == CHAR_LINEFEED) {
    //
    // Skip the /n or /n/r info.
    //
    if (InputString[*Index + StrOffset + 1] == CHAR_CARRIAGE_RETURN) {
      *Index = (UINT16)(*Index + StrOffset + 2);
    } else {
      *Index = (UINT16)(*Index + StrOffset + 1);
    }
  } else if (InputString[*Index + StrOffset] == CHAR_CARRIAGE_RETURN) {
    //
    // Skip the /r or /r/n info.
    //
    if (InputString[*Index + StrOffset + 1] == CHAR_LINEFEED) {
      *Index = (UINT16)(*Index + StrOffset + 2);
    } else {
      *Index = (UINT16)(*Index + StrOffset + 1);
    }
  } else {
    *Index = (UINT16)(*Index + StrOffset);
  }

  //
  // Include extra glyph info and '\0' info, so +2.
  //
  return StrOffset + 2;
}

/**
  Add one menu option by specified description and context.

  @param  Statement              Statement of this Menu Option.
  @param  MenuItemCount          The index for this Option in the Menu.
  @param  NestIn                 Whether this statement is nest in another statement.

**/
VOID
UiAddMenuOption (
  IN FORM_DISPLAY_ENGINE_STATEMENT  *Statement,
  IN UINT16                         *MenuItemCount,
  IN BOOLEAN                        NestIn
  )
{
  UI_MENU_OPTION  *MenuOption;
  UINTN           Index;
  UINTN           Count;
  UINT16          NumberOfLines;
  UINT16          GlyphWidth;
  UINT16          Width;
  UINTN           ArrayEntry;
  CHAR16          *OutputString;
  EFI_STRING_ID   PromptId;

  NumberOfLines = 1;
  ArrayEntry    = 0;
  GlyphWidth    = 1;
  Count         = 1;
  MenuOption    = NULL;

  PromptId = GetPrompt (Statement->OpCode);
  ASSERT (PromptId != 0);

  if ((Statement->OpCode->OpCode == EFI_IFR_DATE_OP) || (Statement->OpCode->OpCode == EFI_IFR_TIME_OP)) {
    Count = 3;
  }

  for (Index = 0; Index < Count; Index++) {
    MenuOption = AllocateZeroPool (sizeof (UI_MENU_OPTION));
    ASSERT (MenuOption);

    MenuOption->Signature       = UI_MENU_OPTION_SIGNATURE;
    MenuOption->Description     = GetToken (PromptId, gFormData->HiiHandle);
    MenuOption->Handle          = gFormData->HiiHandle;
    MenuOption->ThisTag         = Statement;
    MenuOption->NestInStatement = NestIn;
    MenuOption->EntryNumber     = *MenuItemCount;

    MenuOption->Sequence = Index;

    if ((Statement->Attribute & HII_DISPLAY_GRAYOUT) != 0) {
      MenuOption->GrayOut = TRUE;
    } else {
      MenuOption->GrayOut = FALSE;
    }

    if (((Statement->Attribute & HII_DISPLAY_LOCK) != 0) || ((gFormData->Attribute & HII_DISPLAY_LOCK) != 0)) {
      MenuOption->GrayOut = TRUE;
    }

    //
    // If the form or the question has the lock attribute, deal same as grayout.
    //
    if (((gFormData->Attribute & HII_DISPLAY_LOCK) != 0) || ((Statement->Attribute & HII_DISPLAY_LOCK) != 0)) {
      MenuOption->GrayOut = TRUE;
    }

    switch (Statement->OpCode->OpCode) {
      case EFI_IFR_ORDERED_LIST_OP:
      case EFI_IFR_ONE_OF_OP:
      case EFI_IFR_NUMERIC_OP:
      case EFI_IFR_TIME_OP:
      case EFI_IFR_DATE_OP:
      case EFI_IFR_CHECKBOX_OP:
      case EFI_IFR_PASSWORD_OP:
      case EFI_IFR_STRING_OP:
        //
        // User could change the value of these items
        //
        MenuOption->IsQuestion = TRUE;
        break;
      case EFI_IFR_TEXT_OP:
        if (FeaturePcdGet (PcdBrowserGrayOutTextStatement)) {
          //
          // Initializing GrayOut option as TRUE for Text setup options
          // so that those options will be Gray in colour and un selectable.
          //
          MenuOption->GrayOut = TRUE;
        }

        break;
      default:
        MenuOption->IsQuestion = FALSE;
        break;
    }

    if ((Statement->Attribute & HII_DISPLAY_READONLY) != 0) {
      MenuOption->ReadOnly = TRUE;
      if (FeaturePcdGet (PcdBrowerGrayOutReadOnlyMenu)) {
        MenuOption->GrayOut = TRUE;
      }
    }

    if ((Index == 0) &&
        (Statement->OpCode->OpCode != EFI_IFR_DATE_OP) &&
        (Statement->OpCode->OpCode != EFI_IFR_TIME_OP))
    {
      Width = GetWidth (MenuOption, NULL);
      for ( ; GetLineByWidth (MenuOption->Description, Width, &GlyphWidth, &ArrayEntry, &OutputString) != 0x0000;) {
        //
        // If there is more string to process print on the next row and increment the Skip value
        //
        if (StrLen (&MenuOption->Description[ArrayEntry]) != 0) {
          NumberOfLines++;
        }

        FreePool (OutputString);
      }
    } else {
      //
      // Add three MenuOptions for Date/Time
      // Data format :      [01/02/2004]      [11:22:33]
      // Line number :        0  0    1         0  0  1
      //
      NumberOfLines = 0;
    }

    if (Index == 2) {
      //
      // Override LineNumber for the MenuOption in Date/Time sequence
      //
      MenuOption->Skip = 1;
    } else {
      MenuOption->Skip = NumberOfLines;
    }

    InsertTailList (&gMenuOption, &MenuOption->Link);
  }

  (*MenuItemCount)++;
}

/**
  Create the menu list base on the form data info.

**/
VOID
ConvertStatementToMenu (
  VOID
  )
{
  UINT16                         MenuItemCount;
  LIST_ENTRY                     *Link;
  LIST_ENTRY                     *NestLink;
  FORM_DISPLAY_ENGINE_STATEMENT  *Statement;
  FORM_DISPLAY_ENGINE_STATEMENT  *NestStatement;

  MenuItemCount = 0;
  InitializeListHead (&gMenuOption);

  Link = GetFirstNode (&gFormData->StatementListHead);
  while (!IsNull (&gFormData->StatementListHead, Link)) {
    Statement = FORM_DISPLAY_ENGINE_STATEMENT_FROM_LINK (Link);
    Link      = GetNextNode (&gFormData->StatementListHead, Link);

    //
    // Skip the opcode not recognized by Display core.
    //
    if (Statement->OpCode->OpCode == EFI_IFR_GUID_OP) {
      continue;
    }

    UiAddMenuOption (Statement, &MenuItemCount, FALSE);

    //
    // Check the statement nest in this host statement.
    //
    NestLink = GetFirstNode (&Statement->NestStatementList);
    while (!IsNull (&Statement->NestStatementList, NestLink)) {
      NestStatement = FORM_DISPLAY_ENGINE_STATEMENT_FROM_LINK (NestLink);
      NestLink      = GetNextNode (&Statement->NestStatementList, NestLink);

      //
      // Skip the opcode not recognized by Display core.
      //
      if (NestStatement->OpCode->OpCode == EFI_IFR_GUID_OP) {
        continue;
      }

      UiAddMenuOption (NestStatement, &MenuItemCount, TRUE);
    }
  }
}

/**
  Count the storage space of a Unicode string.

  This function handles the Unicode string with NARROW_CHAR
  and WIDE_CHAR control characters. NARROW_HCAR and WIDE_CHAR
  does not count in the resultant output. If a WIDE_CHAR is
  hit, then 2 Unicode character will consume an output storage
  space with size of CHAR16 till a NARROW_CHAR is hit.

  If String is NULL, then ASSERT ().

  @param String          The input string to be counted.

  @return Storage space for the input string.

**/
UINTN
GetStringWidth (
  IN CHAR16  *String
  )
{
  UINTN  Index;
  UINTN  Count;
  UINTN  IncrementValue;

  ASSERT (String != NULL);
  if (String == NULL) {
    return 0;
  }

  Index          = 0;
  Count          = 0;
  IncrementValue = 1;

  do {
    //
    // Advance to the null-terminator or to the first width directive
    //
    for ( ;
          (String[Index] != NARROW_CHAR) && (String[Index] != WIDE_CHAR) && (String[Index] != 0);
          Index++, Count = Count + IncrementValue
          )
    {
    }

    //
    // We hit the null-terminator, we now have a count
    //
    if (String[Index] == 0) {
      break;
    }

    //
    // We encountered a narrow directive - strip it from the size calculation since it doesn't get printed
    // and also set the flag that determines what we increment by.(if narrow, increment by 1, if wide increment by 2)
    //
    if (String[Index] == NARROW_CHAR) {
      //
      // Skip to the next character
      //
      Index++;
      IncrementValue = 1;
    } else {
      //
      // Skip to the next character
      //
      Index++;
      IncrementValue = 2;
    }
  } while (String[Index] != 0);

  //
  // Increment by one to include the null-terminator in the size
  //
  Count++;

  return Count * sizeof (CHAR16);
}

/**
  Base on the input option string to update the skip value for a menu option.

  @param  MenuOption             The MenuOption to be checked.
  @param  OptionString           The input option string.

**/
VOID
UpdateSkipInfoForMenu (
  IN UI_MENU_OPTION  *MenuOption,
  IN CHAR16          *OptionString
  )
{
  UINTN   Index;
  UINT16  Width;
  UINTN   Row;
  CHAR16  *OutputString;
  UINT16  GlyphWidth;

  Width      = (UINT16)gOptionBlockWidth - 1;
  GlyphWidth = 1;
  Row        = 1;

  for (Index = 0; GetLineByWidth (OptionString, Width, &GlyphWidth, &Index, &OutputString) != 0x0000;) {
    if (StrLen (&OptionString[Index]) != 0) {
      Row++;
    }

    FreePool (OutputString);
  }

  if ((Row > MenuOption->Skip) &&
      (MenuOption->ThisTag->OpCode->OpCode != EFI_IFR_DATE_OP) &&
      (MenuOption->ThisTag->OpCode->OpCode != EFI_IFR_TIME_OP))
  {
    MenuOption->Skip = Row;
  }
}

/**
  Update display lines for a Menu Option.

  @param  MenuOption             The MenuOption to be checked.

**/
VOID
UpdateOptionSkipLines (
  IN UI_MENU_OPTION  *MenuOption
  )
{
  CHAR16  *OptionString;

  OptionString = NULL;

  ProcessOptions (MenuOption, FALSE, &OptionString, TRUE);
  if (OptionString != NULL) {
    UpdateSkipInfoForMenu (MenuOption, OptionString);

    FreePool (OptionString);
  }

  if ((MenuOption->ThisTag->OpCode->OpCode  == EFI_IFR_TEXT_OP) && (((EFI_IFR_TEXT *)MenuOption->ThisTag->OpCode)->TextTwo != 0)) {
    OptionString = GetToken (((EFI_IFR_TEXT *)MenuOption->ThisTag->OpCode)->TextTwo, gFormData->HiiHandle);

    if (OptionString != NULL) {
      UpdateSkipInfoForMenu (MenuOption, OptionString);

      FreePool (OptionString);
    }
  }
}

/**
  Check whether this Menu Option could be print.

  Check Prompt string, option string or text two string not NULL.

  This is an internal function.

  @param  MenuOption             The MenuOption to be checked.

  @retval TRUE                   This Menu Option is printable.
  @retval FALSE                  This Menu Option could not be printable.

**/
BOOLEAN
PrintableMenu (
  UI_MENU_OPTION  *MenuOption
  )
{
  EFI_STATUS  Status;
  EFI_STRING  OptionString;

  OptionString = NULL;

  if (MenuOption->Description[0] != '\0') {
    return TRUE;
  }

  Status = ProcessOptions (MenuOption, FALSE, &OptionString, FALSE);
  if (EFI_ERROR (Status)) {
    return FALSE;
  }

  if ((OptionString != NULL) && (OptionString[0] != '\0')) {
    FreePool (OptionString);
    return TRUE;
  }

  if ((MenuOption->ThisTag->OpCode->OpCode  == EFI_IFR_TEXT_OP) && (((EFI_IFR_TEXT *)MenuOption->ThisTag->OpCode)->TextTwo != 0)) {
    OptionString = GetToken (((EFI_IFR_TEXT *)MenuOption->ThisTag->OpCode)->TextTwo, gFormData->HiiHandle);
    ASSERT (OptionString != NULL);
    if (OptionString[0] != '\0') {
      FreePool (OptionString);
      return TRUE;
    }
  }

  return FALSE;
}

/**
  Check whether this Menu Option could be highlighted.

  This is an internal function.

  @param  MenuOption             The MenuOption to be checked.

  @retval TRUE                   This Menu Option is selectable.
  @retval FALSE                  This Menu Option could not be selected.

**/
BOOLEAN
IsSelectable (
  UI_MENU_OPTION  *MenuOption
  )
{
  if ((MenuOption->ThisTag->OpCode->OpCode == EFI_IFR_SUBTITLE_OP) ||
      MenuOption->GrayOut || MenuOption->ReadOnly || !PrintableMenu (MenuOption))
  {
    return FALSE;
  } else {
    return TRUE;
  }
}

/**
  Move to next selectable statement.

  This is an internal function.

  @param  GoUp                   The navigation direction. TRUE: up, FALSE: down.
  @param  CurrentPosition        Current position.
  @param  GapToTop               Gap position to top or bottom.
  @param  FindInForm             Whether find menu in current form or beyond.

  @return The row distance from current MenuOption to next selectable MenuOption.

  @retval -1       Reach the begin of the menu, still can't find the selectable menu.
  @retval Value    Find the selectable menu, maybe the truly selectable, maybe the
                   first menu showing beyond current form or last menu showing in
                   current form.
                   The value is the line number between the new selected menu and the
                   current select menu, not include the new selected menu.

**/
INTN
MoveToNextStatement (
  IN     BOOLEAN     GoUp,
  IN OUT LIST_ENTRY  **CurrentPosition,
  IN     UINTN       GapToTop,
  IN     BOOLEAN     FindInForm
  )
{
  INTN            Distance;
  LIST_ENTRY      *Pos;
  UI_MENU_OPTION  *NextMenuOption;
  UI_MENU_OPTION  *PreMenuOption;

  Distance = 0;
  Pos      = *CurrentPosition;

  if (Pos == &gMenuOption) {
    return -1;
  }

  PreMenuOption = MENU_OPTION_FROM_LINK (Pos);

  while (TRUE) {
    NextMenuOption = MENU_OPTION_FROM_LINK (Pos);
    //
    // NextMenuOption->Row == 0 means this menu has not calculate
    // the NextMenuOption->Skip value yet, just calculate here.
    //
    if (NextMenuOption->Row == 0) {
      UpdateOptionSkipLines (NextMenuOption);
    }

    //
    // Check whether the menu is beyond current showing form,
    // return the first one beyond the showing form.
    //
    if ((UINTN)Distance + NextMenuOption->Skip > GapToTop) {
      if (FindInForm) {
        NextMenuOption = PreMenuOption;
      }

      break;
    }

    //
    // return the selectable menu in the showing form.
    //
    if (IsSelectable (NextMenuOption)) {
      break;
    }

    Distance += NextMenuOption->Skip;

    //
    // Arrive at begin of the menu list.
    //
    if ((GoUp ? Pos->BackLink : Pos->ForwardLink) == &gMenuOption) {
      Distance = -1;
      break;
    }

    Pos           = (GoUp ? Pos->BackLink : Pos->ForwardLink);
    PreMenuOption = NextMenuOption;
  }

  *CurrentPosition = &NextMenuOption->Link;
  return Distance;
}

/**
  Process option string for date/time opcode.

  @param  MenuOption              Menu option point to date/time.
  @param  OptionString            Option string input for process.
  @param  AddOptCol               Whether need to update MenuOption->OptCol.

**/
VOID
ProcessStringForDateTime (
  UI_MENU_OPTION  *MenuOption,
  CHAR16          *OptionString,
  BOOLEAN         AddOptCol
  )
{
  UINTN                          Index;
  UINTN                          Count;
  FORM_DISPLAY_ENGINE_STATEMENT  *Statement;
  EFI_IFR_DATE                   *Date;
  EFI_IFR_TIME                   *Time;

  ASSERT (MenuOption != NULL && OptionString != NULL);

  Statement = MenuOption->ThisTag;
  Date      = NULL;
  Time      = NULL;
  if (Statement->OpCode->OpCode == EFI_IFR_DATE_OP) {
    Date = (EFI_IFR_DATE *)Statement->OpCode;
  } else if (Statement->OpCode->OpCode == EFI_IFR_TIME_OP) {
    Time = (EFI_IFR_TIME *)Statement->OpCode;
  }

  //
  // If leading spaces on OptionString - remove the spaces
  //
  for (Index = 0; OptionString[Index] == L' '; Index++) {
    //
    // Base on the blockspace to get the option column info.
    //
    if (AddOptCol) {
      MenuOption->OptCol++;
    }
  }

  for (Count = 0; OptionString[Index] != CHAR_NULL; Index++) {
    OptionString[Count] = OptionString[Index];
    Count++;
  }

  OptionString[Count] = CHAR_NULL;

  //
  // Enable to suppress field in the opcode base on the flag.
  //
  if (Statement->OpCode->OpCode == EFI_IFR_DATE_OP) {
    //
    // OptionString format is: <**:  **: ****>
    //                        |month|day|year|
    //                          4     3    5
    //
    if ((Date->Flags & EFI_QF_DATE_MONTH_SUPPRESS) && (MenuOption->Sequence == 0)) {
      //
      // At this point, only "<**:" in the optionstring.
      // Clean the day's ** field, after clean, the format is "<  :"
      //
      SetUnicodeMem (&OptionString[1], 2, L' ');
    } else if ((Date->Flags & EFI_QF_DATE_DAY_SUPPRESS) && (MenuOption->Sequence == 1)) {
      //
      // At this point, only "**:" in the optionstring.
      // Clean the month's "**" field, after clean, the format is "  :"
      //
      SetUnicodeMem (&OptionString[0], 2, L' ');
    } else if ((Date->Flags & EFI_QF_DATE_YEAR_SUPPRESS) && (MenuOption->Sequence == 2)) {
      //
      // At this point, only "****>" in the optionstring.
      // Clean the year's "****" field, after clean, the format is "  >"
      //
      SetUnicodeMem (&OptionString[0], 4, L' ');
    }
  } else if (Statement->OpCode->OpCode == EFI_IFR_TIME_OP) {
    //
    // OptionString format is: <**:  **:    **>
    //                        |hour|minute|second|
    //                          4     3      3
    //
    if ((Time->Flags & QF_TIME_HOUR_SUPPRESS) && (MenuOption->Sequence == 0)) {
      //
      // At this point, only "<**:" in the optionstring.
      // Clean the hour's ** field, after clean, the format is "<  :"
      //
      SetUnicodeMem (&OptionString[1], 2, L' ');
    } else if ((Time->Flags & QF_TIME_MINUTE_SUPPRESS) && (MenuOption->Sequence == 1)) {
      //
      // At this point, only "**:" in the optionstring.
      // Clean the minute's "**" field, after clean, the format is "  :"
      //
      SetUnicodeMem (&OptionString[0], 2, L' ');
    } else if ((Time->Flags & QF_TIME_SECOND_SUPPRESS) && (MenuOption->Sequence == 2)) {
      //
      // At this point, only "**>" in the optionstring.
      // Clean the second's "**" field, after clean, the format is "  >"
      //
      SetUnicodeMem (&OptionString[0], 2, L' ');
    }
  }
}

/**
  Adjust Data and Time position accordingly.
  Data format :      [01/02/2004]      [11:22:33]
  Line number :        0  0    1         0  0  1

  This is an internal function.

  @param  DirectionUp            the up or down direction. False is down. True is
                                 up.
  @param  CurrentPosition        Current position. On return: Point to the last
                                 Option (Year or Second) if up; Point to the first
                                 Option (Month or Hour) if down.

  @return Return line number to pad. It is possible that we stand on a zero-advance
  @return data or time opcode, so pad one line when we judge if we are going to scroll outside.

**/
UINTN
AdjustDateAndTimePosition (
  IN     BOOLEAN     DirectionUp,
  IN OUT LIST_ENTRY  **CurrentPosition
  )
{
  UINTN           Count;
  LIST_ENTRY      *NewPosition;
  UI_MENU_OPTION  *MenuOption;
  UINTN           PadLineNumber;

  PadLineNumber = 0;
  NewPosition   = *CurrentPosition;
  MenuOption    = MENU_OPTION_FROM_LINK (NewPosition);

  if ((MenuOption->ThisTag->OpCode->OpCode == EFI_IFR_DATE_OP) ||
      (MenuOption->ThisTag->OpCode->OpCode == EFI_IFR_TIME_OP))
  {
    //
    // Calculate the distance from current position to the last Date/Time MenuOption
    //
    Count = 0;
    while (MenuOption->Skip == 0) {
      Count++;
      NewPosition   = NewPosition->ForwardLink;
      MenuOption    = MENU_OPTION_FROM_LINK (NewPosition);
      PadLineNumber = 1;
    }

    NewPosition = *CurrentPosition;
    if (DirectionUp) {
      //
      // Since the behavior of hitting the up arrow on a Date/Time MenuOption is intended
      // to be one that back to the previous set of MenuOptions, we need to advance to the first
      // Date/Time MenuOption and leave the remaining logic in CfUiUp intact so the appropriate
      // checking can be done.
      //
      while (Count++ < 2) {
        NewPosition = NewPosition->BackLink;
      }
    } else {
      //
      // Since the behavior of hitting the down arrow on a Date/Time MenuOption is intended
      // to be one that progresses to the next set of MenuOptions, we need to advance to the last
      // Date/Time MenuOption and leave the remaining logic in CfUiDown intact so the appropriate
      // checking can be done.
      //
      while (Count-- > 0) {
        NewPosition = NewPosition->ForwardLink;
      }
    }

    *CurrentPosition = NewPosition;
  }

  return PadLineNumber;
}

/**
  Get step info from numeric opcode.

  @param[in] OpCode     The input numeric op code.

  @return step info for this opcode.
**/
UINT64
GetFieldFromNum (
  IN  EFI_IFR_OP_HEADER  *OpCode
  )
{
  EFI_IFR_NUMERIC  *NumericOp;
  UINT64           Step;

  NumericOp = (EFI_IFR_NUMERIC *)OpCode;

  switch (NumericOp->Flags & EFI_IFR_NUMERIC_SIZE) {
    case EFI_IFR_NUMERIC_SIZE_1:
      Step = NumericOp->data.u8.Step;
      break;

    case EFI_IFR_NUMERIC_SIZE_2:
      Step = NumericOp->data.u16.Step;
      break;

    case EFI_IFR_NUMERIC_SIZE_4:
      Step = NumericOp->data.u32.Step;
      break;

    case EFI_IFR_NUMERIC_SIZE_8:
      Step = NumericOp->data.u64.Step;
      break;

    default:
      Step = 0;
      break;
  }

  return Step;
}

/**
  Find the registered HotKey based on KeyData.

  @param[in] KeyData     A pointer to a buffer that describes the keystroke
                         information for the hot key.

  @return The registered HotKey context. If no found, NULL will return.
**/
BROWSER_HOT_KEY *
GetHotKeyFromRegisterList (
  IN EFI_INPUT_KEY  *KeyData
  )
{
  LIST_ENTRY       *Link;
  BROWSER_HOT_KEY  *HotKey;

  Link = GetFirstNode (&gFormData->HotKeyListHead);
  while (!IsNull (&gFormData->HotKeyListHead, Link)) {
    HotKey = BROWSER_HOT_KEY_FROM_LINK (Link);

    if (HotKey->KeyData->ScanCode == KeyData->ScanCode) {
      return HotKey;
    }

    Link = GetNextNode (&gFormData->HotKeyListHead, Link);
  }

  return NULL;
}

/**
  Determine if the menu is the last menu that can be selected.

  This is an internal function.

  @param  Direction              The scroll direction. False is down. True is up.
  @param  CurrentPos             The current focus.

  @return FALSE -- the menu isn't the last menu that can be selected.
  @return TRUE  -- the menu is the last menu that can be selected.

**/
BOOLEAN
ValueIsScroll (
  IN  BOOLEAN     Direction,
  IN  LIST_ENTRY  *CurrentPos
  )
{
  LIST_ENTRY  *Temp;

  Temp = Direction ? CurrentPos->BackLink : CurrentPos->ForwardLink;

  if (Temp == &gMenuOption) {
    return TRUE;
  }

  return FALSE;
}

/**
  Wait for a given event to fire, or for an optional timeout to expire.

  @param  Event                  The event to wait for

  @retval UI_EVENT_TYPE          The type of the event which is trigged.

**/
UI_EVENT_TYPE
UiWaitForEvent (
  IN EFI_EVENT  Event
  )
{
  EFI_STATUS     Status;
  UINTN          Index;
  UINTN          EventNum;
  UINT64         Timeout;
  EFI_EVENT      TimerEvent;
  EFI_EVENT      WaitList[3];
  UI_EVENT_TYPE  EventType;

  TimerEvent = NULL;
  Timeout    = FormExitTimeout (gFormData);

  if (Timeout != 0) {
    Status = gBS->CreateEvent (EVT_TIMER, 0, NULL, NULL, &TimerEvent);

    //
    // Set the timer event
    //
    gBS->SetTimer (
           TimerEvent,
           TimerRelative,
           Timeout
           );
  }

  WaitList[0] = Event;
  EventNum    = 1;
  if (gFormData->FormRefreshEvent != NULL) {
    WaitList[EventNum] = gFormData->FormRefreshEvent;
    EventNum++;
  }

  if (Timeout != 0) {
    WaitList[EventNum] = TimerEvent;
    EventNum++;
  }

  Status = gBS->WaitForEvent (EventNum, WaitList, &Index);
  ASSERT_EFI_ERROR (Status);

  switch (Index) {
    case 0:
      EventType = UIEventKey;
      break;

    case 1:
      if (gFormData->FormRefreshEvent != NULL) {
        EventType = UIEventDriver;
      } else {
        ASSERT (Timeout != 0 && EventNum == 2);
        EventType = UIEventTimeOut;
      }

      break;

    default:
      ASSERT (Index == 2 && EventNum == 3);
      EventType = UIEventTimeOut;
      break;
  }

  if (Timeout != 0) {
    gBS->CloseEvent (TimerEvent);
  }

  return EventType;
}

/**
  Get question id info from the input opcode header.

  @param  OpCode                 The input opcode header pointer.

  @retval                        The question id for this opcode.

**/
EFI_QUESTION_ID
GetQuestionIdInfo (
  IN   EFI_IFR_OP_HEADER  *OpCode
  )
{
  EFI_IFR_QUESTION_HEADER  *QuestionHeader;

  if (OpCode->Length < sizeof (EFI_IFR_OP_HEADER) + sizeof (EFI_IFR_QUESTION_HEADER)) {
    return 0;
  }

  QuestionHeader = (EFI_IFR_QUESTION_HEADER *)((UINT8 *)OpCode + sizeof (EFI_IFR_OP_HEADER));

  return QuestionHeader->QuestionId;
}

/**
  Find the top of screen menu base on the current menu.

  @param  CurPos                 Current input menu.
  @param  Rows                   Totol screen rows.
  @param  SkipValue              SkipValue for this new form.

  @retval TopOfScreen            Top of screen menu for the new form.

**/
LIST_ENTRY *
FindTopOfScreenMenu (
  IN  LIST_ENTRY  *CurPos,
  IN  UINTN       Rows,
  OUT UINTN       *SkipValue
  )
{
  LIST_ENTRY      *Link;
  LIST_ENTRY      *TopOfScreen;
  UI_MENU_OPTION  *PreviousMenuOption;

  Link               = CurPos;
  PreviousMenuOption = NULL;

  while (Link->BackLink != &gMenuOption) {
    Link               = Link->BackLink;
    PreviousMenuOption = MENU_OPTION_FROM_LINK (Link);
    if (PreviousMenuOption->Row == 0) {
      UpdateOptionSkipLines (PreviousMenuOption);
    }

    if (Rows <= PreviousMenuOption->Skip) {
      break;
    }

    Rows = Rows - PreviousMenuOption->Skip;
  }

  if (Link->BackLink == &gMenuOption) {
    TopOfScreen = gMenuOption.ForwardLink;
    if ((PreviousMenuOption != NULL) && (Rows < PreviousMenuOption->Skip)) {
      *SkipValue = PreviousMenuOption->Skip - Rows;
    } else {
      *SkipValue = 0;
    }
  } else {
    TopOfScreen = Link;
    *SkipValue  = PreviousMenuOption->Skip - Rows;
  }

  return TopOfScreen;
}

/**
  Get the index info for this opcode.

  @param  OpCode      The input opcode for the statement.

  @retval  The index of this statement.

**/
UINTN
GetIndexInfoForOpcode (
  IN EFI_IFR_OP_HEADER  *OpCode
  )
{
  LIST_ENTRY      *NewPos;
  UI_MENU_OPTION  *MenuOption;
  UINTN           Index;

  NewPos = gMenuOption.ForwardLink;
  Index  = 0;

  for (NewPos = gMenuOption.ForwardLink; NewPos != &gMenuOption; NewPos = NewPos->ForwardLink) {
    MenuOption = MENU_OPTION_FROM_LINK (NewPos);

    if (CompareMem (MenuOption->ThisTag->OpCode, OpCode, OpCode->Length) == 0) {
      if (MenuOption->ThisTag->OpCode == OpCode) {
        return Index;
      }

      Index++;
    }
  }

  return Index;
}

/**
  Is this the saved highlight statement.

  @param  HighLightedStatement      The input highlight statement.

  @retval  TRUE   This is the highlight statement.
  @retval  FALSE  This is not the highlight statement.

**/
BOOLEAN
IsSavedHighlightStatement (
  IN FORM_DISPLAY_ENGINE_STATEMENT  *HighLightedStatement
  )
{
  if ((gFormData->HiiHandle == gHighligthMenuInfo.HiiHandle) &&
      (gFormData->FormId == gHighligthMenuInfo.FormId))
  {
    if (gHighligthMenuInfo.HLTQuestionId != 0) {
      return (BOOLEAN)(gHighligthMenuInfo.HLTQuestionId == GetQuestionIdInfo (HighLightedStatement->OpCode));
    } else {
      if (CompareMem (gHighligthMenuInfo.HLTOpCode, HighLightedStatement->OpCode, gHighligthMenuInfo.HLTOpCode->Length) == 0) {
        if ((gHighligthMenuInfo.HLTIndex == 0) || (gHighligthMenuInfo.HLTIndex == GetIndexInfoForOpcode (HighLightedStatement->OpCode))) {
          return TRUE;
        } else {
          return FALSE;
        }
      }
    }
  }

  return FALSE;
}

/**
  Is this the highlight menu.

  @param  MenuOption      The input Menu option.

  @retval  TRUE   This is the highlight menu option.
  @retval  FALSE  This is not the highlight menu option.

**/
BOOLEAN
IsHighLightMenuOption (
  IN UI_MENU_OPTION  *MenuOption
  )
{
  if (gHighligthMenuInfo.HLTQuestionId != 0) {
    if (GetQuestionIdInfo (MenuOption->ThisTag->OpCode) == gHighligthMenuInfo.HLTQuestionId) {
      return (BOOLEAN)(MenuOption->Sequence == gHighligthMenuInfo.HLTSequence);
    }
  } else {
    if (CompareMem (gHighligthMenuInfo.HLTOpCode, MenuOption->ThisTag->OpCode, gHighligthMenuInfo.HLTOpCode->Length) == 0) {
      if ((gHighligthMenuInfo.HLTIndex == 0) || (gHighligthMenuInfo.HLTIndex == GetIndexInfoForOpcode (MenuOption->ThisTag->OpCode))) {
        return (BOOLEAN)(MenuOption->Sequence == gHighligthMenuInfo.HLTSequence);
      } else {
        return FALSE;
      }
    }
  }

  return FALSE;
}

/**
  Find the highlight menu.

  If the input is NULL, base on the record highlight info in
  gHighligthMenuInfo to find the last highlight menu.

  @param  HighLightedStatement      The input highlight statement.

  @retval  The highlight menu index.

**/
LIST_ENTRY *
FindHighLightMenuOption (
  IN FORM_DISPLAY_ENGINE_STATEMENT  *HighLightedStatement
  )
{
  LIST_ENTRY      *NewPos;
  UI_MENU_OPTION  *MenuOption;

  NewPos     = gMenuOption.ForwardLink;
  MenuOption = MENU_OPTION_FROM_LINK (NewPos);

  if (HighLightedStatement != NULL) {
    while (MenuOption->ThisTag != HighLightedStatement) {
      NewPos = NewPos->ForwardLink;
      if (NewPos == &gMenuOption) {
        //
        // Not Found it, break
        //
        break;
      }

      MenuOption = MENU_OPTION_FROM_LINK (NewPos);
    }

    //
    // Must find the highlight statement.
    //
    ASSERT (NewPos != &gMenuOption);
  } else {
    while (!IsHighLightMenuOption (MenuOption)) {
      NewPos = NewPos->ForwardLink;
      if (NewPos == &gMenuOption) {
        //
        // Not Found it, break
        //
        break;
      }

      MenuOption = MENU_OPTION_FROM_LINK (NewPos);
    }

    //
    // Highlight statement has disappear (suppressed/disableed)
    //
    if (NewPos == &gMenuOption) {
      NewPos = NULL;
    }
  }

  return NewPos;
}

/**
  Is this the Top of screen menu.

  @param  MenuOption      The input Menu option.

  @retval  TRUE   This is the Top of screen menu option.
  @retval  FALSE  This is not the Top of screen menu option.

**/
BOOLEAN
IsTopOfScreeMenuOption (
  IN UI_MENU_OPTION  *MenuOption
  )
{
  if (gHighligthMenuInfo.TOSQuestionId != 0) {
    return (BOOLEAN)(GetQuestionIdInfo (MenuOption->ThisTag->OpCode) == gHighligthMenuInfo.TOSQuestionId);
  }

  if (CompareMem (gHighligthMenuInfo.TOSOpCode, MenuOption->ThisTag->OpCode, gHighligthMenuInfo.TOSOpCode->Length) == 0) {
    if ((gHighligthMenuInfo.TOSIndex == 0) || (gHighligthMenuInfo.TOSIndex == GetIndexInfoForOpcode (MenuOption->ThisTag->OpCode))) {
      return TRUE;
    } else {
      return FALSE;
    }
  }

  return FALSE;
}

/**
  Calculate the distance between two menus and include the skip value of StartMenu.

  @param  StartMenu             The link_entry pointer to start menu.
  @param  EndMenu               The link_entry pointer to end menu.

**/
UINTN
GetDistanceBetweenMenus (
  IN LIST_ENTRY  *StartMenu,
  IN LIST_ENTRY  *EndMenu
  )
{
  LIST_ENTRY      *Link;
  UI_MENU_OPTION  *MenuOption;
  UINTN           Distance;

  Distance = 0;

  Link = StartMenu;
  while (Link != EndMenu) {
    MenuOption = MENU_OPTION_FROM_LINK (Link);
    if (MenuOption->Row == 0) {
      UpdateOptionSkipLines (MenuOption);
    }

    Distance += MenuOption->Skip;
    Link      = Link->BackLink;
  }

  return Distance;
}

/**
  Find the top of screen menu base on the previous record menu info.

  @param  HighLightMenu      The link_entry pointer to highlight menu.

  @retval  Return the the link_entry pointer top of screen menu.

**/
LIST_ENTRY *
FindTopOfScreenMenuOption (
  IN LIST_ENTRY  *HighLightMenu
  )
{
  LIST_ENTRY      *NewPos;
  UI_MENU_OPTION  *MenuOption;
  UINTN           TopRow;
  UINTN           BottomRow;

  TopRow    = gStatementDimensions.TopRow    + SCROLL_ARROW_HEIGHT;
  BottomRow = gStatementDimensions.BottomRow - SCROLL_ARROW_HEIGHT;

  NewPos     = gMenuOption.ForwardLink;
  MenuOption = MENU_OPTION_FROM_LINK (NewPos);

  while (!IsTopOfScreeMenuOption (MenuOption)) {
    NewPos = NewPos->ForwardLink;
    if (NewPos == &gMenuOption) {
      //
      // Not Found it, break
      //
      break;
    }

    MenuOption = MENU_OPTION_FROM_LINK (NewPos);
  }

  //
  // Last time top of screen menu has disappeared.
  //
  if (NewPos == &gMenuOption) {
    return NULL;
  }

  //
  // Check whether highlight menu and top of screen menu can be shown within one page,
  // if can't, return NULL to re-calcaulate the top of scrren menu. Because some new menus
  // may be dynamically inserted between highlightmenu and previous top of screen menu,
  // So previous record top of screen menu is not appropriate for current display.
  //
  if (GetDistanceBetweenMenus (HighLightMenu, NewPos) + 1 > BottomRow - TopRow) {
    return NULL;
  }

  return NewPos;
}

/**
  Find the first menu which will be show at the top.

  @param  FormData               The data info for this form.
  @param  TopOfScreen            The link_entry pointer to top menu.
  @param  HighlightMenu          The menu which will be highlight.
  @param  SkipValue              The skip value for the top menu.

**/
VOID
FindTopMenu (
  IN  FORM_DISPLAY_ENGINE_FORM  *FormData,
  OUT LIST_ENTRY                **TopOfScreen,
  OUT LIST_ENTRY                **HighlightMenu,
  OUT UINTN                     *SkipValue
  )
{
  UINTN           TopRow;
  UINTN           BottomRow;
  UI_MENU_OPTION  *MenuOption;
  UINTN           TmpValue;

  TopRow    = gStatementDimensions.TopRow    + SCROLL_ARROW_HEIGHT;
  BottomRow = gStatementDimensions.BottomRow - SCROLL_ARROW_HEIGHT;
  //
  // When option mismatch happens,there exist two cases,one is reenter the form, just like the if case below,
  // and the other is exit current form and enter last form, it can be covered by the else case.
  //
  if (gMisMatch && (gFormData->HiiHandle == gHighligthMenuInfo.HiiHandle) && (gFormData->FormId == gHighligthMenuInfo.FormId)) {
    //
    // Reenter caused by option mismatch or auto exit caused by refresh form(refresh interval/guid),
    // base on the record highlight info to find the highlight menu.
    //

    *HighlightMenu = FindHighLightMenuOption (NULL);
    if (*HighlightMenu != NULL) {
      //
      // Update skip info for this highlight menu.
      //
      MenuOption = MENU_OPTION_FROM_LINK (*HighlightMenu);
      UpdateOptionSkipLines (MenuOption);

      //
      // Found the last time highlight menu.
      //
      *TopOfScreen = FindTopOfScreenMenuOption (*HighlightMenu);
      if (*TopOfScreen != NULL) {
        //
        // Found the last time selectable top of screen menu.
        //
        AdjustDateAndTimePosition (TRUE, TopOfScreen);
        MenuOption = MENU_OPTION_FROM_LINK (*TopOfScreen);
        UpdateOptionSkipLines (MenuOption);

        *SkipValue = gHighligthMenuInfo.SkipValue;
      } else {
        //
        // Not found last time top of screen menu, so base on current highlight menu
        // to find the new top of screen menu.
        // Make the current highlight menu at the bottom of the form to calculate the
        // top of screen menu.
        //
        if (MenuOption->Skip >= BottomRow - TopRow) {
          *TopOfScreen = *HighlightMenu;
          TmpValue     = 0;
        } else {
          *TopOfScreen = FindTopOfScreenMenu (*HighlightMenu, BottomRow - TopRow - MenuOption->Skip, &TmpValue);
        }

        *SkipValue = TmpValue;
      }
    } else {
      //
      // Last time highlight menu has disappear, find the first highlightable menu as the default one.
      //
      *HighlightMenu = gMenuOption.ForwardLink;
      if (!IsListEmpty (&gMenuOption)) {
        MoveToNextStatement (FALSE, HighlightMenu, BottomRow - TopRow, TRUE);
      }

      *TopOfScreen = gMenuOption.ForwardLink;
      *SkipValue   = 0;
    }
  } else if (FormData->HighLightedStatement != NULL) {
    if (IsSavedHighlightStatement (FormData->HighLightedStatement)) {
      //
      // Input highlight menu is same as last time highlight menu.
      // Base on last time highlight menu to set the top of screen menu and highlight menu.
      //
      *HighlightMenu = FindHighLightMenuOption (NULL);
      ASSERT (*HighlightMenu != NULL);

      //
      // Update skip info for this highlight menu.
      //
      MenuOption = MENU_OPTION_FROM_LINK (*HighlightMenu);
      UpdateOptionSkipLines (MenuOption);

      *TopOfScreen = FindTopOfScreenMenuOption (*HighlightMenu);
      if (*TopOfScreen == NULL) {
        //
        // Not found last time top of screen menu, so base on current highlight menu
        // to find the new top of screen menu.
        // Make the current highlight menu at the bottom of the form to calculate the
        // top of screen menu.
        //
        if (MenuOption->Skip >= BottomRow - TopRow) {
          *TopOfScreen = *HighlightMenu;
          TmpValue     = 0;
        } else {
          *TopOfScreen = FindTopOfScreenMenu (*HighlightMenu, BottomRow - TopRow - MenuOption->Skip, &TmpValue);
        }

        *SkipValue = TmpValue;
      } else {
        AdjustDateAndTimePosition (TRUE, TopOfScreen);
        MenuOption = MENU_OPTION_FROM_LINK (*TopOfScreen);
        UpdateOptionSkipLines (MenuOption);

        *SkipValue = gHighligthMenuInfo.SkipValue;
      }

      AdjustDateAndTimePosition (TRUE, TopOfScreen);
    } else {
      //
      // Input highlight menu is not save as last time highlight menu.
      //
      *HighlightMenu = FindHighLightMenuOption (FormData->HighLightedStatement);
      MenuOption     = MENU_OPTION_FROM_LINK (*HighlightMenu);
      UpdateOptionSkipLines (MenuOption);

      //
      // Make the current highlight menu at the bottom of the form to calculate the
      // top of screen menu.
      //
      if (MenuOption->Skip >= BottomRow - TopRow) {
        *TopOfScreen = *HighlightMenu;
        TmpValue     = 0;
      } else {
        *TopOfScreen = FindTopOfScreenMenu (*HighlightMenu, BottomRow - TopRow - MenuOption->Skip, &TmpValue);
      }

      *SkipValue = TmpValue;
    }

    AdjustDateAndTimePosition (TRUE, TopOfScreen);
  } else {
    //
    // If not has input highlight statement, just return the first one in this form.
    //
    *TopOfScreen   = gMenuOption.ForwardLink;
    *HighlightMenu = gMenuOption.ForwardLink;
    if (!IsListEmpty (&gMenuOption)) {
      MoveToNextStatement (FALSE, HighlightMenu, BottomRow - TopRow, TRUE);
    }

    *SkipValue = 0;
  }

  gMisMatch = FALSE;

  //
  // First enter to show the menu, update highlight info.
  //
  UpdateHighlightMenuInfo (*HighlightMenu, *TopOfScreen, *SkipValue);
}

/**
  Record the highlight menu and top of screen menu info.

  @param  Highlight               The menu opton which is highlight.
  @param  TopOfScreen             The menu opton which is at the top of the form.
  @param  SkipValue               The skip line info for the top of screen menu.

**/
VOID
UpdateHighlightMenuInfo (
  IN  LIST_ENTRY  *Highlight,
  IN  LIST_ENTRY  *TopOfScreen,
  IN  UINTN       SkipValue
  )
{
  UI_MENU_OPTION                 *MenuOption;
  FORM_DISPLAY_ENGINE_STATEMENT  *Statement;

  gHighligthMenuInfo.HiiHandle = gFormData->HiiHandle;
  gHighligthMenuInfo.FormId    = gFormData->FormId;
  gHighligthMenuInfo.SkipValue = (UINT16)SkipValue;

  if (!IsListEmpty (&gMenuOption)) {
    MenuOption = MENU_OPTION_FROM_LINK (Highlight);
    Statement  = MenuOption->ThisTag;

    gUserInput->SelectedStatement = Statement;

    gHighligthMenuInfo.HLTSequence   = MenuOption->Sequence;
    gHighligthMenuInfo.HLTQuestionId = GetQuestionIdInfo (Statement->OpCode);
    if (gHighligthMenuInfo.HLTQuestionId == 0) {
      //
      // if question id == 0, save the opcode buffer..
      //
      if (gHighligthMenuInfo.HLTOpCode != NULL) {
        FreePool (gHighligthMenuInfo.HLTOpCode);
      }

      gHighligthMenuInfo.HLTOpCode = AllocateCopyPool (Statement->OpCode->Length, Statement->OpCode);
      ASSERT (gHighligthMenuInfo.HLTOpCode != NULL);

      gHighligthMenuInfo.HLTIndex = GetIndexInfoForOpcode (Statement->OpCode);
    }

    MenuOption = MENU_OPTION_FROM_LINK (TopOfScreen);
    Statement  = MenuOption->ThisTag;

    gHighligthMenuInfo.TOSQuestionId = GetQuestionIdInfo (Statement->OpCode);
    if (gHighligthMenuInfo.TOSQuestionId == 0) {
      //
      // if question id == 0, save the opcode buffer..
      //
      if (gHighligthMenuInfo.TOSOpCode != NULL) {
        FreePool (gHighligthMenuInfo.TOSOpCode);
      }

      gHighligthMenuInfo.TOSOpCode = AllocateCopyPool (Statement->OpCode->Length, Statement->OpCode);
      ASSERT (gHighligthMenuInfo.TOSOpCode != NULL);

      gHighligthMenuInfo.TOSIndex = GetIndexInfoForOpcode (Statement->OpCode);
    }
  } else {
    gUserInput->SelectedStatement = NULL;

    gHighligthMenuInfo.HLTSequence   = 0;
    gHighligthMenuInfo.HLTQuestionId = 0;
    if (gHighligthMenuInfo.HLTOpCode != NULL) {
      FreePool (gHighligthMenuInfo.HLTOpCode);
    }

    gHighligthMenuInfo.HLTOpCode = NULL;
    gHighligthMenuInfo.HLTIndex  = 0;

    gHighligthMenuInfo.TOSQuestionId = 0;
    if (gHighligthMenuInfo.TOSOpCode != NULL) {
      FreePool (gHighligthMenuInfo.TOSOpCode);
    }

    gHighligthMenuInfo.TOSOpCode = NULL;
    gHighligthMenuInfo.TOSIndex  = 0;
  }
}

/**
  Update attribut for this menu.

  @param  MenuOption               The menu opton which this attribut used to.
  @param  Highlight                Whether this menu will be highlight.

**/
VOID
SetDisplayAttribute (
  IN UI_MENU_OPTION  *MenuOption,
  IN BOOLEAN         Highlight
  )
{
  FORM_DISPLAY_ENGINE_STATEMENT  *Statement;

  Statement = MenuOption->ThisTag;

  if (Highlight) {
    gST->ConOut->SetAttribute (gST->ConOut, GetHighlightTextColor ());
    return;
  }

  if (MenuOption->GrayOut) {
    gST->ConOut->SetAttribute (gST->ConOut, GetGrayedTextColor ());
  } else {
    if (Statement->OpCode->OpCode == EFI_IFR_SUBTITLE_OP) {
      gST->ConOut->SetAttribute (gST->ConOut, GetSubTitleTextColor ());
    } else {
      gST->ConOut->SetAttribute (gST->ConOut, GetFieldTextColor ());
    }
  }
}

/**
  Print string for this menu option.

  @param  MenuOption               The menu opton which this attribut used to.
  @param  Col                      The column that this string will be print at.
  @param  Row                      The row that this string will be print at.
  @param  String                   The string which need to print.
  @param  Width                    The width need to print, if string is less than the
                                   width, the block space will be used.
  @param  Highlight                Whether this menu will be highlight.

**/
VOID
DisplayMenuString (
  IN UI_MENU_OPTION  *MenuOption,
  IN UINTN           Col,
  IN UINTN           Row,
  IN CHAR16          *String,
  IN UINTN           Width,
  IN BOOLEAN         Highlight
  )
{
  UINTN  Length;

  //
  // Print string with normal color.
  //
  if (!Highlight) {
    PrintStringAtWithWidth (Col, Row, String, Width);
    return;
  }

  //
  // Print the highlight menu string.
  // First print the highlight string.
  //
  SetDisplayAttribute (MenuOption, TRUE);
  Length = PrintStringAt (Col, Row, String);

  //
  // Second, clean the empty after the string.
  //
  SetDisplayAttribute (MenuOption, FALSE);
  PrintStringAtWithWidth (Col + Length, Row, L"", Width - Length);
}

/**
  Check whether this menu can has option string.

  @param  MenuOption               The menu opton which this attribut used to.

  @retval TRUE                     This menu option can have option string.
  @retval FALSE                    This menu option can't have option string.

**/
BOOLEAN
HasOptionString (
  IN UI_MENU_OPTION  *MenuOption
  )
{
  FORM_DISPLAY_ENGINE_STATEMENT  *Statement;
  CHAR16                         *String;
  UINTN                          Size;
  EFI_IFR_TEXT                   *TextOp;

  Size      = 0;
  Statement = MenuOption->ThisTag;

  //
  // See if the second text parameter is really NULL
  //
  if (Statement->OpCode->OpCode == EFI_IFR_TEXT_OP) {
    TextOp = (EFI_IFR_TEXT *)Statement->OpCode;
    if (TextOp->TextTwo != 0) {
      String = GetToken (TextOp->TextTwo, gFormData->HiiHandle);
      Size   = StrLen (String);
      FreePool (String);
    }
  }

  if ((Statement->OpCode->OpCode == EFI_IFR_SUBTITLE_OP) ||
      (Statement->OpCode->OpCode == EFI_IFR_REF_OP) ||
      (Statement->OpCode->OpCode == EFI_IFR_PASSWORD_OP) ||
      (Statement->OpCode->OpCode == EFI_IFR_ACTION_OP) ||
      (Statement->OpCode->OpCode == EFI_IFR_RESET_BUTTON_OP) ||
      //
      // Allow a wide display if text op-code and no secondary text op-code
      //
      ((Statement->OpCode->OpCode == EFI_IFR_TEXT_OP) && (Size == 0))
      )
  {
    return FALSE;
  }

  return TRUE;
}

/**
  Double confirm with user about the action.

  @param  Action               The user input action.

  @retval TRUE                 User confirm with the input or not need user confirm.
  @retval FALSE                User want ignore this input.

**/
BOOLEAN
FxConfirmPopup (
  IN UINT32  Action
  )
{
  EFI_INPUT_KEY  Key;
  CHAR16         *CfmStr;
  UINTN          CfmStrLen;
  UINT32         CheckFlags;
  BOOLEAN        RetVal;
  UINTN          CatLen;
  UINTN          MaxLen;

  CfmStrLen = 0;
  CatLen    = StrLen (gConfirmMsgConnect);

  //
  // Below action need extra popup dialog to confirm.
  //
  CheckFlags = BROWSER_ACTION_DISCARD |
               BROWSER_ACTION_DEFAULT |
               BROWSER_ACTION_SUBMIT |
               BROWSER_ACTION_RESET |
               BROWSER_ACTION_EXIT;

  //
  // Not need to confirm with user, just return TRUE.
  //
  if ((Action & CheckFlags) == 0) {
    return TRUE;
  }

  if ((Action & BROWSER_ACTION_DISCARD) == BROWSER_ACTION_DISCARD) {
    CfmStrLen += StrLen (gConfirmDiscardMsg);
  }

  if ((Action & BROWSER_ACTION_DEFAULT) == BROWSER_ACTION_DEFAULT) {
    if (CfmStrLen != 0) {
      CfmStrLen += CatLen;
    }

    CfmStrLen += StrLen (gConfirmDefaultMsg);
  }

  if ((Action & BROWSER_ACTION_SUBMIT)  == BROWSER_ACTION_SUBMIT) {
    if (CfmStrLen != 0) {
      CfmStrLen += CatLen;
    }

    CfmStrLen += StrLen (gConfirmSubmitMsg);
  }

  if ((Action & BROWSER_ACTION_RESET)  == BROWSER_ACTION_RESET) {
    if (CfmStrLen != 0) {
      CfmStrLen += CatLen;
    }

    CfmStrLen += StrLen (gConfirmResetMsg);
  }

  if ((Action & BROWSER_ACTION_EXIT)  == BROWSER_ACTION_EXIT) {
    if (CfmStrLen != 0) {
      CfmStrLen += CatLen;
    }

    CfmStrLen += StrLen (gConfirmExitMsg);
  }

  //
  // Allocate buffer to save the string.
  // String + "?" + "\0"
  //
  MaxLen = CfmStrLen + 1 + 1;
  CfmStr = AllocateZeroPool (MaxLen * sizeof (CHAR16));
  ASSERT (CfmStr != NULL);

  if ((Action & BROWSER_ACTION_DISCARD) == BROWSER_ACTION_DISCARD) {
    StrCpyS (CfmStr, MaxLen, gConfirmDiscardMsg);
  }

  if ((Action & BROWSER_ACTION_DEFAULT) == BROWSER_ACTION_DEFAULT) {
    if (CfmStr[0] != 0) {
      StrCatS (CfmStr, MaxLen, gConfirmMsgConnect);
      StrCatS (CfmStr, MaxLen, gConfirmDefaultMsg2nd);
    } else {
      StrCpyS (CfmStr, MaxLen, gConfirmDefaultMsg);
    }
  }

  if ((Action & BROWSER_ACTION_SUBMIT)  == BROWSER_ACTION_SUBMIT) {
    if (CfmStr[0] != 0) {
      StrCatS (CfmStr, MaxLen, gConfirmMsgConnect);
      StrCatS (CfmStr, MaxLen, gConfirmSubmitMsg2nd);
    } else {
      StrCpyS (CfmStr, MaxLen, gConfirmSubmitMsg);
    }
  }

  if ((Action & BROWSER_ACTION_RESET)  == BROWSER_ACTION_RESET) {
    if (CfmStr[0] != 0) {
      StrCatS (CfmStr, MaxLen, gConfirmMsgConnect);
      StrCatS (CfmStr, MaxLen, gConfirmResetMsg2nd);
    } else {
      StrCpyS (CfmStr, MaxLen, gConfirmResetMsg);
    }
  }

  if ((Action & BROWSER_ACTION_EXIT)  == BROWSER_ACTION_EXIT) {
    if (CfmStr[0] != 0) {
      StrCatS (CfmStr, MaxLen, gConfirmMsgConnect);
      StrCatS (CfmStr, MaxLen, gConfirmExitMsg2nd);
    } else {
      StrCpyS (CfmStr, MaxLen, gConfirmExitMsg);
    }
  }

  StrCatS (CfmStr, MaxLen, gConfirmMsgEnd);

  do {
    CreateDialog (&Key, gEmptyString, CfmStr, gConfirmOpt, gEmptyString, NULL);
  } while (((Key.UnicodeChar | UPPER_LOWER_CASE_OFFSET) != (gConfirmOptYes[0] | UPPER_LOWER_CASE_OFFSET)) &&
           ((Key.UnicodeChar | UPPER_LOWER_CASE_OFFSET) != (gConfirmOptNo[0] | UPPER_LOWER_CASE_OFFSET)) &&
           (Key.ScanCode != SCAN_ESC));

  if ((Key.UnicodeChar | UPPER_LOWER_CASE_OFFSET) == (gConfirmOptYes[0] | UPPER_LOWER_CASE_OFFSET)) {
    RetVal = TRUE;
  } else {
    RetVal = FALSE;
  }

  FreePool (CfmStr);

  return RetVal;
}

/**
  Print string for this menu option.

  @param  MenuOption               The menu opton which this attribut used to.
  @param  SkipWidth                The skip width between the left to the start of the prompt.
  @param  BeginCol                 The begin column for one menu.
  @param  SkipLine                 The skip line for this menu.
  @param  BottomRow                The bottom row for this form.
  @param  Highlight                Whether this menu will be highlight.
  @param  UpdateCol                Whether need to update the column info for Date/Time.

  @retval EFI_SUCESSS              Process the user selection success.

**/
EFI_STATUS
DisplayOneMenu (
  IN UI_MENU_OPTION  *MenuOption,
  IN UINTN           SkipWidth,
  IN UINTN           BeginCol,
  IN UINTN           SkipLine,
  IN UINTN           BottomRow,
  IN BOOLEAN         Highlight,
  IN BOOLEAN         UpdateCol
  )
{
  FORM_DISPLAY_ENGINE_STATEMENT  *Statement;
  UINTN                          Index;
  UINT16                         Width;
  UINT16                         PromptWidth;
  CHAR16                         *StringPtr;
  CHAR16                         *OptionString;
  CHAR16                         *OutputString;
  UINT16                         GlyphWidth;
  UINTN                          Temp;
  UINTN                          Temp2;
  UINTN                          Temp3;
  EFI_STATUS                     Status;
  UINTN                          Row;
  BOOLEAN                        IsProcessingFirstRow;
  UINTN                          Col;
  UINTN                          PromptLineNum;
  UINTN                          OptionLineNum;
  CHAR16                         AdjustValue;
  UINTN                          MaxRow;

  Statement            = MenuOption->ThisTag;
  Temp                 = SkipLine;
  Temp2                = SkipLine;
  Temp3                = SkipLine;
  AdjustValue          = 0;
  PromptLineNum        = 0;
  OptionLineNum        = 0;
  MaxRow               = 0;
  IsProcessingFirstRow = TRUE;

  //
  // Set default color.
  //
  SetDisplayAttribute (MenuOption, FALSE);

  //
  // 1. Paint the option string.
  //
  Status = ProcessOptions (MenuOption, FALSE, &OptionString, FALSE);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  if (OptionString != NULL) {
    if ((Statement->OpCode->OpCode == EFI_IFR_DATE_OP) || (Statement->OpCode->OpCode == EFI_IFR_TIME_OP)) {
      //
      // Adjust option string for date/time opcode.
      //
      ProcessStringForDateTime (MenuOption, OptionString, UpdateCol);
    }

    Width         = (UINT16)gOptionBlockWidth - 1;
    Row           = MenuOption->Row;
    GlyphWidth    = 1;
    OptionLineNum = 0;

    for (Index = 0; GetLineByWidth (OptionString, Width, &GlyphWidth, &Index, &OutputString) != 0x0000;) {
      if (((Temp2 == 0)) && (Row <= BottomRow)) {
        if ((Statement->OpCode->OpCode == EFI_IFR_DATE_OP) || (Statement->OpCode->OpCode == EFI_IFR_TIME_OP)) {
          //
          // For date/time question, it has three menu options for this qustion.
          // The first/second menu options with the skip value is 0. the last one
          // with skip value is 1.
          //
          if (MenuOption->Skip != 0) {
            //
            // For date/ time, print the last past (year for date and second for time)
            // - 7 means skip [##/##/ for date and [##:##: for time.
            //
            DisplayMenuString (MenuOption, MenuOption->OptCol, Row, OutputString, Width + 1 - 7, Highlight);
          } else {
            //
            // For date/ time, print the first and second past (year for date and second for time)
            // The OutputString has a NARROW_CHAR or WIDE_CHAR at the begin of the string,
            // so need to - 1 to remove it, otherwise, it will clean 1 extr char follow it.
            DisplayMenuString (MenuOption, MenuOption->OptCol, Row, OutputString, StrLen (OutputString) - 1, Highlight);
          }
        } else {
          DisplayMenuString (MenuOption, MenuOption->OptCol, Row, OutputString, Width + 1, Highlight);
        }

        OptionLineNum++;
      }

      //
      // If there is more string to process print on the next row and increment the Skip value
      //
      if (StrLen (&OptionString[Index]) != 0) {
        if (Temp2 == 0) {
          Row++;
          //
          // Since the Number of lines for this menu entry may or may not be reflected accurately
          // since the prompt might be 1 lines and option might be many, and vice versa, we need to do
          // some testing to ensure we are keeping this in-sync.
          //
          // If the difference in rows is greater than or equal to the skip value, increase the skip value
          //
          if ((Row - MenuOption->Row) >= MenuOption->Skip) {
            MenuOption->Skip++;
          }
        }
      }

      FreePool (OutputString);
      if (Temp2 != 0) {
        Temp2--;
      }
    }

    Highlight = FALSE;

    FreePool (OptionString);
  }

  //
  // 2. Paint the description.
  //
  PromptWidth   = GetWidth (MenuOption, &AdjustValue);
  Row           = MenuOption->Row;
  GlyphWidth    = 1;
  PromptLineNum = 0;

  if ((MenuOption->Description == NULL) || (MenuOption->Description[0] == '\0')) {
    PrintStringAtWithWidth (BeginCol, Row, L"", PromptWidth + AdjustValue + SkipWidth);
    PromptLineNum++;
  } else {
    for (Index = 0; GetLineByWidth (MenuOption->Description, PromptWidth, &GlyphWidth, &Index, &OutputString) != 0x0000;) {
      if ((Temp == 0) && (Row <= BottomRow)) {
        //
        // 1.Clean the start LEFT_SKIPPED_COLUMNS
        //
        PrintStringAtWithWidth (BeginCol, Row, L"", SkipWidth);

        if ((Statement->OpCode->OpCode == EFI_IFR_REF_OP) && (MenuOption->Col >= 2) && IsProcessingFirstRow) {
          //
          // Print Arrow for Goto button.
          //
          PrintCharAt (
            MenuOption->Col - 2,
            Row,
            GEOMETRICSHAPE_RIGHT_TRIANGLE
            );
          IsProcessingFirstRow = FALSE;
        }

        DisplayMenuString (MenuOption, MenuOption->Col, Row, OutputString, PromptWidth + AdjustValue, Highlight);
        PromptLineNum++;
      }

      //
      // If there is more string to process print on the next row and increment the Skip value
      //
      if (StrLen (&MenuOption->Description[Index]) != 0) {
        if (Temp == 0) {
          Row++;
        }
      }

      FreePool (OutputString);
      if (Temp != 0) {
        Temp--;
      }
    }

    Highlight = FALSE;
  }

  //
  // 3. If this is a text op with secondary text information
  //
  if ((Statement->OpCode->OpCode  == EFI_IFR_TEXT_OP) && (((EFI_IFR_TEXT *)Statement->OpCode)->TextTwo != 0)) {
    StringPtr = GetToken (((EFI_IFR_TEXT *)Statement->OpCode)->TextTwo, gFormData->HiiHandle);

    Width         = (UINT16)gOptionBlockWidth - 1;
    Row           = MenuOption->Row;
    GlyphWidth    = 1;
    OptionLineNum = 0;

    for (Index = 0; GetLineByWidth (StringPtr, Width, &GlyphWidth, &Index, &OutputString) != 0x0000;) {
      if ((Temp3 == 0) && (Row <= BottomRow)) {
        DisplayMenuString (MenuOption, MenuOption->OptCol, Row, OutputString, Width + 1, Highlight);
        OptionLineNum++;
      }

      //
      // If there is more string to process print on the next row and increment the Skip value
      //
      if (StrLen (&StringPtr[Index]) != 0) {
        if (Temp3 == 0) {
          Row++;
          //
          // If the rows for text two is greater than or equal to the skip value, increase the skip value
          //
          if ((Row - MenuOption->Row) >= MenuOption->Skip) {
            MenuOption->Skip++;
          }
        }
      }

      FreePool (OutputString);
      if (Temp3 != 0) {
        Temp3--;
      }
    }

    FreePool (StringPtr);
  }

  //
  // 4.Line number for Option string and prompt string are not equal.
  //  Clean the column whose line number is less.
  //
  if (HasOptionString (MenuOption) && (OptionLineNum != PromptLineNum)) {
    Col    =  OptionLineNum < PromptLineNum ? MenuOption->OptCol : BeginCol;
    Row    = (OptionLineNum < PromptLineNum ? OptionLineNum : PromptLineNum) + MenuOption->Row;
    Width  = (UINT16)(OptionLineNum < PromptLineNum ? gOptionBlockWidth : PromptWidth + AdjustValue + SkipWidth);
    MaxRow = (OptionLineNum < PromptLineNum ? PromptLineNum : OptionLineNum) + MenuOption->Row - 1;

    while (Row <= MaxRow) {
      DisplayMenuString (MenuOption, Col, Row++, L"", Width, FALSE);
    }
  }

  return EFI_SUCCESS;
}

/**
  Display menu and wait for user to select one menu option, then return it.
  If AutoBoot is enabled, then if user doesn't select any option,
  after period of time, it will automatically return the first menu option.

  @param  FormData               The current form data info.

  @retval EFI_SUCESSS            Process the user selection success.
  @retval EFI_NOT_FOUND          Process option string for orderedlist/Oneof fail.

**/
EFI_STATUS
UiDisplayMenu (
  IN  FORM_DISPLAY_ENGINE_FORM  *FormData
  )
{
  UINTN                          SkipValue;
  INTN                           Difference;
  UINTN                          DistanceValue;
  UINTN                          Row;
  UINTN                          Col;
  UINTN                          Temp;
  UINTN                          Temp2;
  UINTN                          TopRow;
  UINTN                          BottomRow;
  UINTN                          Index;
  CHAR16                         *StringPtr;
  CHAR16                         *StringRightPtr;
  CHAR16                         *StringErrorPtr;
  CHAR16                         *OptionString;
  CHAR16                         *HelpString;
  CHAR16                         *HelpHeaderString;
  CHAR16                         *HelpBottomString;
  BOOLEAN                        NewLine;
  BOOLEAN                        Repaint;
  BOOLEAN                        UpArrow;
  BOOLEAN                        DownArrow;
  EFI_STATUS                     Status;
  EFI_INPUT_KEY                  Key;
  LIST_ENTRY                     *Link;
  LIST_ENTRY                     *NewPos;
  LIST_ENTRY                     *TopOfScreen;
  LIST_ENTRY                     *SavedListEntry;
  UI_MENU_OPTION                 *MenuOption;
  UI_MENU_OPTION                 *NextMenuOption;
  UI_MENU_OPTION                 *SavedMenuOption;
  UI_CONTROL_FLAG                ControlFlag;
  UI_SCREEN_OPERATION            ScreenOperation;
  FORM_DISPLAY_ENGINE_STATEMENT  *Statement;
  BROWSER_HOT_KEY                *HotKey;
  UINTN                          HelpPageIndex;
  UINTN                          HelpPageCount;
  UINTN                          RowCount;
  UINTN                          HelpLine;
  UINTN                          HelpHeaderLine;
  UINTN                          HelpBottomLine;
  BOOLEAN                        MultiHelpPage;
  UINT16                         EachLineWidth;
  UINT16                         HeaderLineWidth;
  UINT16                         BottomLineWidth;
  EFI_STRING_ID                  HelpInfo;
  UI_EVENT_TYPE                  EventType;
  BOOLEAN                        SkipHighLight;
  EFI_HII_VALUE                  *StatementValue;

  EventType        = UIEventNone;
  Status           = EFI_SUCCESS;
  HelpString       = NULL;
  HelpHeaderString = NULL;
  HelpBottomString = NULL;
  OptionString     = NULL;
  ScreenOperation  = UiNoOperation;
  NewLine          = TRUE;
  HelpPageCount    = 0;
  HelpLine         = 0;
  RowCount         = 0;
  HelpBottomLine   = 0;
  HelpHeaderLine   = 0;
  HelpPageIndex    = 0;
  MultiHelpPage    = FALSE;
  EachLineWidth    = 0;
  HeaderLineWidth  = 0;
  BottomLineWidth  = 0;
  UpArrow          = FALSE;
  DownArrow        = FALSE;
  SkipValue        = 0;
  SkipHighLight    = FALSE;

  NextMenuOption   = NULL;
  SavedMenuOption  = NULL;
  HotKey           = NULL;
  Repaint          = TRUE;
  MenuOption       = NULL;
  gModalSkipColumn = (CHAR16)(gStatementDimensions.RightColumn - gStatementDimensions.LeftColumn) / 6;

  ZeroMem (&Key, sizeof (EFI_INPUT_KEY));

  TopRow    = gStatementDimensions.TopRow    + SCROLL_ARROW_HEIGHT;
  BottomRow = gStatementDimensions.BottomRow - SCROLL_ARROW_HEIGHT - 1;

  Row = TopRow;
  if ((FormData->Attribute & HII_DISPLAY_MODAL) != 0) {
    Col = gStatementDimensions.LeftColumn + LEFT_SKIPPED_COLUMNS + gModalSkipColumn;
  } else {
    Col = gStatementDimensions.LeftColumn + LEFT_SKIPPED_COLUMNS;
  }

  FindTopMenu (FormData, &TopOfScreen, &NewPos, &SkipValue);
  if (!IsListEmpty (&gMenuOption)) {
    NextMenuOption                = MENU_OPTION_FROM_LINK (NewPos);
    gUserInput->SelectedStatement = NextMenuOption->ThisTag;
  }

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

  ControlFlag = CfInitialization;
  while (TRUE) {
    switch (ControlFlag) {
      case CfInitialization:
        if ((gOldFormEntry.HiiHandle != FormData->HiiHandle) ||
            (!CompareGuid (&gOldFormEntry.FormSetGuid, &FormData->FormSetGuid)))
        {
          //
          // Clear Statement range if different formset is painted.
          //
          ClearLines (
            gStatementDimensions.LeftColumn,
            gStatementDimensions.RightColumn,
            TopRow - SCROLL_ARROW_HEIGHT,
            BottomRow + SCROLL_ARROW_HEIGHT,
            GetFieldTextColor ()
            );
        }

        ControlFlag = CfRepaint;
        break;

      case CfRepaint:
        ControlFlag = CfRefreshHighLight;

        if (Repaint) {
          //
          // Display menu
          //
          DownArrow = FALSE;
          UpArrow   = FALSE;
          Row       = TopRow;

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

          //
          // 1. Check whether need to print the arrow up.
          //
          if (!ValueIsScroll (TRUE, TopOfScreen)) {
            UpArrow = TRUE;
          }

          if ((FormData->Attribute & HII_DISPLAY_MODAL) != 0) {
            PrintStringAtWithWidth (gStatementDimensions.LeftColumn + gModalSkipColumn, TopRow - 1, L"", gStatementDimensions.RightColumn - gStatementDimensions.LeftColumn - 2 * gModalSkipColumn);
          } else {
            PrintStringAtWithWidth (gStatementDimensions.LeftColumn, TopRow - 1, L"", gStatementDimensions.RightColumn - gStatementDimensions.LeftColumn);
          }

          if (UpArrow) {
            gST->ConOut->SetAttribute (gST->ConOut, GetArrowColor ());
            PrintCharAt (
              gStatementDimensions.LeftColumn + gPromptBlockWidth + gOptionBlockWidth + 1,
              TopRow - SCROLL_ARROW_HEIGHT,
              ARROW_UP
              );
            gST->ConOut->SetAttribute (gST->ConOut, GetFieldTextColor ());
          }

          //
          // 2.Paint the menu.
          //
          for (Link = TopOfScreen; Link != &gMenuOption; Link = Link->ForwardLink) {
            MenuOption      = MENU_OPTION_FROM_LINK (Link);
            MenuOption->Row = Row;
            MenuOption->Col = Col;
            if ((FormData->Attribute & HII_DISPLAY_MODAL) != 0) {
              MenuOption->OptCol = gStatementDimensions.LeftColumn + LEFT_SKIPPED_COLUMNS + gPromptBlockWidth + gModalSkipColumn;
            } else {
              MenuOption->OptCol = gStatementDimensions.LeftColumn + LEFT_SKIPPED_COLUMNS + gPromptBlockWidth;
            }

            if (MenuOption->NestInStatement) {
              MenuOption->Col += SUBTITLE_INDENT;
            }

            //
            // Save the highlight menu, will be used in CfRefreshHighLight case.
            //
            if (Link == NewPos) {
              SavedMenuOption = MenuOption;
              SkipHighLight   = TRUE;
            }

            if ((FormData->Attribute & HII_DISPLAY_MODAL) != 0) {
              Status = DisplayOneMenu (
                         MenuOption,
                         MenuOption->Col - gStatementDimensions.LeftColumn,
                         gStatementDimensions.LeftColumn + gModalSkipColumn,
                         Link == TopOfScreen ? SkipValue : 0,
                         BottomRow,
                         (BOOLEAN)((Link == NewPos) && IsSelectable (MenuOption)),
                         TRUE
                         );
            } else {
              Status = DisplayOneMenu (
                         MenuOption,
                         MenuOption->Col - gStatementDimensions.LeftColumn,
                         gStatementDimensions.LeftColumn,
                         Link == TopOfScreen ? SkipValue : 0,
                         BottomRow,
                         (BOOLEAN)((Link == NewPos) && IsSelectable (MenuOption)),
                         TRUE
                         );
            }

            if (EFI_ERROR (Status)) {
              if (gMisMatch) {
                return EFI_SUCCESS;
              } else {
                return Status;
              }
            }

            //
            // 3. Update the row info which will be used by next menu.
            //
            if (Link == TopOfScreen) {
              Row += MenuOption->Skip - SkipValue;
            } else {
              Row += MenuOption->Skip;
            }

            if (Row > BottomRow) {
              if (!ValueIsScroll (FALSE, Link)) {
                DownArrow = TRUE;
              }

              Row = BottomRow + 1;
              break;
            }
          }

          //
          // 3. Menus in this form may not cover all form, clean the remain field.
          //
          while (Row <= BottomRow) {
            if ((FormData->Attribute & HII_DISPLAY_MODAL) != 0) {
              PrintStringAtWithWidth (gStatementDimensions.LeftColumn + gModalSkipColumn, Row++, L"", gStatementDimensions.RightColumn - gStatementDimensions.LeftColumn - 2 * gModalSkipColumn);
            } else {
              PrintStringAtWithWidth (gStatementDimensions.LeftColumn, Row++, L"", gStatementDimensions.RightColumn - gHelpBlockWidth - gStatementDimensions.LeftColumn);
            }
          }

          //
          // 4. Print the down arrow row.
          //
          if ((FormData->Attribute & HII_DISPLAY_MODAL) != 0) {
            PrintStringAtWithWidth (gStatementDimensions.LeftColumn + gModalSkipColumn, BottomRow + 1, L"", gStatementDimensions.RightColumn - gStatementDimensions.LeftColumn - 2 *  +gModalSkipColumn);
          } else {
            PrintStringAtWithWidth (gStatementDimensions.LeftColumn, BottomRow + 1, L"", gStatementDimensions.RightColumn - gStatementDimensions.LeftColumn);
          }

          if (DownArrow) {
            gST->ConOut->SetAttribute (gST->ConOut, GetArrowColor ());
            PrintCharAt (
              gStatementDimensions.LeftColumn + gPromptBlockWidth + gOptionBlockWidth + 1,
              BottomRow + SCROLL_ARROW_HEIGHT,
              ARROW_DOWN
              );
            gST->ConOut->SetAttribute (gST->ConOut, GetFieldTextColor ());
          }

          MenuOption = NULL;
        }

        break;

      case CfRefreshHighLight:

        //
        // MenuOption: Last menu option that need to remove hilight
        //             MenuOption is set to NULL in Repaint
        // NewPos:     Current menu option that need to hilight
        //
        ControlFlag = CfUpdateHelpString;

        ASSERT (NewPos != NULL);
        UpdateHighlightMenuInfo (NewPos, TopOfScreen, SkipValue);

        if (SkipHighLight) {
          SkipHighLight = FALSE;
          MenuOption    = SavedMenuOption;
          RefreshKeyHelp (gFormData, SavedMenuOption->ThisTag, FALSE);
          break;
        }

        if (IsListEmpty (&gMenuOption)) {
          //
          // No menu option, just update the hotkey filed.
          //
          RefreshKeyHelp (gFormData, NULL, FALSE);
          break;
        }

        if ((MenuOption != NULL) && (TopOfScreen == &MenuOption->Link)) {
          Temp = SkipValue;
        } else {
          Temp = 0;
        }

        if (NewPos == TopOfScreen) {
          Temp2 = SkipValue;
        } else {
          Temp2 = 0;
        }

        if ((MenuOption == NULL) || (NewPos != &MenuOption->Link)) {
          if (MenuOption != NULL) {
            //
            // Remove the old highlight menu.
            //
            Status = DisplayOneMenu (
                       MenuOption,
                       MenuOption->Col - gStatementDimensions.LeftColumn,
                       gStatementDimensions.LeftColumn,
                       Temp,
                       BottomRow,
                       FALSE,
                       FALSE
                       );
          }

          //
          // This is the current selected statement
          //
          MenuOption = MENU_OPTION_FROM_LINK (NewPos);
          RefreshKeyHelp (gFormData, MenuOption->ThisTag, FALSE);

          if (!IsSelectable (MenuOption)) {
            break;
          }

          Status = DisplayOneMenu (
                     MenuOption,
                     MenuOption->Col - gStatementDimensions.LeftColumn,
                     gStatementDimensions.LeftColumn,
                     Temp2,
                     BottomRow,
                     TRUE,
                     FALSE
                     );
        }

        break;

      case CfUpdateHelpString:
        ControlFlag = CfPrepareToReadKey;
        if ((FormData->Attribute & HII_DISPLAY_MODAL) != 0) {
          break;
        }

        //
        // NewLine means only update highlight menu (remove old highlight and highlith
        // the new one), not need to full repain the form.
        //
        if (Repaint || NewLine) {
          if (IsListEmpty (&gMenuOption)) {
            //
            // Don't print anything if no mwnu option.
            //
            StringPtr = GetToken (STRING_TOKEN (EMPTY_STRING), gHiiHandle);
          } else {
            //
            // Don't print anything if it is a NULL help token
            //
            ASSERT (MenuOption != NULL);
            HelpInfo       = ((EFI_IFR_STATEMENT_HEADER *)((CHAR8 *)MenuOption->ThisTag->OpCode + sizeof (EFI_IFR_OP_HEADER)))->Help;
            Statement      = MenuOption->ThisTag;
            StatementValue = &Statement->CurrentValue;
            if ((HelpInfo == 0) || !IsSelectable (MenuOption)) {
              if (((Statement->OpCode->OpCode == EFI_IFR_DATE_OP) && (StatementValue->Value.date.Month == 0xff)) || ((Statement->OpCode->OpCode == EFI_IFR_TIME_OP) && (StatementValue->Value.time.Hour == 0xff))) {
                StringPtr = GetToken (STRING_TOKEN (GET_TIME_FAIL), gHiiHandle);
              } else {
                StringPtr = GetToken (STRING_TOKEN (EMPTY_STRING), gHiiHandle);
              }
            } else {
              if (((Statement->OpCode->OpCode == EFI_IFR_DATE_OP) && (StatementValue->Value.date.Month == 0xff)) || ((Statement->OpCode->OpCode == EFI_IFR_TIME_OP) && (StatementValue->Value.time.Hour == 0xff))) {
                StringRightPtr = GetToken (HelpInfo, gFormData->HiiHandle);
                StringErrorPtr = GetToken (STRING_TOKEN (GET_TIME_FAIL), gHiiHandle);
                StringPtr      = AllocateZeroPool ((StrLen (StringRightPtr) + StrLen (StringErrorPtr)+ 1) * sizeof (CHAR16));
                StrCpyS (StringPtr, StrLen (StringRightPtr) + StrLen (StringErrorPtr) + 1, StringRightPtr);
                StrCatS (StringPtr, StrLen (StringRightPtr) + StrLen (StringErrorPtr) + 1, StringErrorPtr);
                FreePool (StringRightPtr);
                FreePool (StringErrorPtr);
              } else {
                StringPtr = GetToken (HelpInfo, gFormData->HiiHandle);
              }
            }
          }

          RowCount      = BottomRow - TopRow + 1;
          HelpPageIndex = 0;
          //
          // 1.Calculate how many line the help string need to print.
          //
          if (HelpString != NULL) {
            FreePool (HelpString);
            HelpString = NULL;
          }

          HelpLine = ProcessHelpString (StringPtr, &HelpString, &EachLineWidth, RowCount);
          FreePool (StringPtr);

          if (HelpLine > RowCount) {
            MultiHelpPage = TRUE;
            StringPtr     = GetToken (STRING_TOKEN (ADJUST_HELP_PAGE_UP), gHiiHandle);
            if (HelpHeaderString != NULL) {
              FreePool (HelpHeaderString);
              HelpHeaderString = NULL;
            }

            HelpHeaderLine = ProcessHelpString (StringPtr, &HelpHeaderString, &HeaderLineWidth, 0);
            FreePool (StringPtr);
            StringPtr = GetToken (STRING_TOKEN (ADJUST_HELP_PAGE_DOWN), gHiiHandle);
            if (HelpBottomString != NULL) {
              FreePool (HelpBottomString);
              HelpBottomString = NULL;
            }

            HelpBottomLine = ProcessHelpString (StringPtr, &HelpBottomString, &BottomLineWidth, 0);
            FreePool (StringPtr);
            //
            // Calculate the help page count.
            //
            if (HelpLine > 2 * RowCount - 2) {
              HelpPageCount = (HelpLine - RowCount + 1) / (RowCount - 2) + 1;
              if ((HelpLine - RowCount + 1) % (RowCount - 2) != 0) {
                HelpPageCount += 1;
              }
            } else {
              HelpPageCount = 2;
            }
          } else {
            MultiHelpPage = FALSE;
          }
        }

        //
        // Check whether need to show the 'More(U/u)' at the begin.
        // Base on current direct info, here shows aligned to the right side of the column.
        // If the direction is multi line and aligned to right side may have problem, so
        // add ASSERT code here.
        //
        if (HelpPageIndex > 0) {
          gST->ConOut->SetAttribute (gST->ConOut, GetInfoTextColor ());
          for (Index = 0; Index < HelpHeaderLine; Index++) {
            ASSERT (HelpHeaderLine == 1);
            ASSERT (GetStringWidth (HelpHeaderString) / 2 < ((UINT32)gHelpBlockWidth - 1));
            PrintStringAtWithWidth (
              gStatementDimensions.RightColumn - gHelpBlockWidth,
              Index + TopRow,
              gEmptyString,
              gHelpBlockWidth
              );
            PrintStringAt (
              gStatementDimensions.RightColumn - GetStringWidth (HelpHeaderString) / 2 - 1,
              Index + TopRow,
              &HelpHeaderString[Index * HeaderLineWidth]
              );
          }
        }

        gST->ConOut->SetAttribute (gST->ConOut, GetHelpTextColor ());
        //
        // Print the help string info.
        //
        if (!MultiHelpPage) {
          for (Index = 0; Index < HelpLine; Index++) {
            PrintStringAtWithWidth (
              gStatementDimensions.RightColumn - gHelpBlockWidth,
              Index + TopRow,
              &HelpString[Index * EachLineWidth],
              gHelpBlockWidth
              );
          }

          for ( ; Index < RowCount; Index++) {
            PrintStringAtWithWidth (
              gStatementDimensions.RightColumn - gHelpBlockWidth,
              Index + TopRow,
              gEmptyString,
              gHelpBlockWidth
              );
          }

          gST->ConOut->SetCursorPosition (gST->ConOut, gStatementDimensions.RightColumn-1, BottomRow);
        } else {
          if (HelpPageIndex == 0) {
            for (Index = 0; Index < RowCount - HelpBottomLine; Index++) {
              PrintStringAtWithWidth (
                gStatementDimensions.RightColumn - gHelpBlockWidth,
                Index + TopRow,
                &HelpString[Index * EachLineWidth],
                gHelpBlockWidth
                );
            }
          } else {
            for (Index = 0; (Index < RowCount - HelpBottomLine - HelpHeaderLine) &&
                 (Index + HelpPageIndex * (RowCount - 2) + 1 < HelpLine); Index++)
            {
              PrintStringAtWithWidth (
                gStatementDimensions.RightColumn - gHelpBlockWidth,
                Index + TopRow + HelpHeaderLine,
                &HelpString[(Index + HelpPageIndex * (RowCount - 2) + 1)* EachLineWidth],
                gHelpBlockWidth
                );
            }

            if (HelpPageIndex == HelpPageCount - 1) {
              for ( ; Index < RowCount - HelpHeaderLine; Index++) {
                PrintStringAtWithWidth (
                  gStatementDimensions.RightColumn - gHelpBlockWidth,
                  Index + TopRow + HelpHeaderLine,
                  gEmptyString,
                  gHelpBlockWidth
                  );
              }

              gST->ConOut->SetCursorPosition (gST->ConOut, gStatementDimensions.RightColumn-1, BottomRow);
            }
          }
        }

        //
        // Check whether need to print the 'More(D/d)' at the bottom.
        // Base on current direct info, here shows aligned to the right side of the column.
        // If the direction is multi line and aligned to right side may have problem, so
        // add ASSERT code here.
        //
        if ((HelpPageIndex < HelpPageCount - 1) && MultiHelpPage) {
          gST->ConOut->SetAttribute (gST->ConOut, GetInfoTextColor ());
          for (Index = 0; Index < HelpBottomLine; Index++) {
            ASSERT (HelpBottomLine == 1);
            ASSERT (GetStringWidth (HelpBottomString) / 2 < ((UINT32)gHelpBlockWidth - 1));
            PrintStringAtWithWidth (
              gStatementDimensions.RightColumn - gHelpBlockWidth,
              BottomRow + Index - HelpBottomLine + 1,
              gEmptyString,
              gHelpBlockWidth
              );
            PrintStringAt (
              gStatementDimensions.RightColumn - GetStringWidth (HelpBottomString) / 2 - 1,
              BottomRow + Index - HelpBottomLine + 1,
              &HelpBottomString[Index * BottomLineWidth]
              );
          }
        }

        //
        // Reset this flag every time we finish using it.
        //
        Repaint = FALSE;
        NewLine = FALSE;
        break;

      case CfPrepareToReadKey:
        ControlFlag     = CfReadKey;
        ScreenOperation = UiNoOperation;
        break;

      case CfReadKey:
        ControlFlag = CfScreenOperation;

        //
        // Wait for user's selection
        //
        while (TRUE) {
          Status = gST->ConIn->ReadKeyStroke (gST->ConIn, &Key);
          if (!EFI_ERROR (Status)) {
            EventType = UIEventKey;
            break;
          }

          //
          // If we encounter error, continue to read another key in.
          //
          if (Status != EFI_NOT_READY) {
            continue;
          }

          EventType = UiWaitForEvent (gST->ConIn->WaitForKey);
          if (EventType == UIEventKey) {
            gST->ConIn->ReadKeyStroke (gST->ConIn, &Key);
          }

          break;
        }

        if (EventType == UIEventDriver) {
          gMisMatch          = TRUE;
          gUserInput->Action = BROWSER_ACTION_NONE;
          ControlFlag        = CfExit;
          break;
        }

        if (EventType == UIEventTimeOut) {
          gUserInput->Action = BROWSER_ACTION_FORM_EXIT;
          ControlFlag        = CfExit;
          break;
        }

        switch (Key.UnicodeChar) {
          case CHAR_CARRIAGE_RETURN:
            if ((MenuOption == NULL) || MenuOption->GrayOut || MenuOption->ReadOnly) {
              ControlFlag = CfReadKey;
              break;
            }

            ScreenOperation = UiSelect;
            gDirection      = 0;
            break;

          //
          // We will push the adjustment of these numeric values directly to the input handler
          //  NOTE: we won't handle manual input numeric
          //
          case '+':
          case '-':
            //
            // If the screen has no menu items, and the user didn't select UiReset
            // ignore the selection and go back to reading keys.
            //
            ASSERT (MenuOption != NULL);
            if (IsListEmpty (&gMenuOption) || MenuOption->GrayOut || MenuOption->ReadOnly) {
              ControlFlag = CfReadKey;
              break;
            }

            Statement = MenuOption->ThisTag;
            if (  (Statement->OpCode->OpCode == EFI_IFR_DATE_OP)
               || (Statement->OpCode->OpCode == EFI_IFR_TIME_OP)
               || ((Statement->OpCode->OpCode == EFI_IFR_NUMERIC_OP) && (GetFieldFromNum (Statement->OpCode) != 0))
                  )
            {
              if (Key.UnicodeChar == '+') {
                gDirection = SCAN_RIGHT;
              } else {
                gDirection = SCAN_LEFT;
              }

              Status = ProcessOptions (MenuOption, TRUE, &OptionString, TRUE);
              if (OptionString != NULL) {
                FreePool (OptionString);
              }

              if (EFI_ERROR (Status)) {
                //
                // Repaint to clear possible error prompt pop-up
                //
                Repaint = TRUE;
                NewLine = TRUE;
              } else {
                ControlFlag = CfExit;
              }
            }

            break;

          case '^':
            ScreenOperation = UiUp;
            break;

          case 'V':
          case 'v':
            ScreenOperation = UiDown;
            break;

          case ' ':
            if (IsListEmpty (&gMenuOption)) {
              ControlFlag = CfReadKey;
              break;
            }

            ASSERT (MenuOption != NULL);
            if ((MenuOption->ThisTag->OpCode->OpCode == EFI_IFR_CHECKBOX_OP) && !MenuOption->GrayOut && !MenuOption->ReadOnly) {
              ScreenOperation = UiSelect;
            }

            break;

          case 'D':
          case 'd':
            if (!MultiHelpPage) {
              ControlFlag = CfReadKey;
              break;
            }

            ControlFlag   = CfUpdateHelpString;
            HelpPageIndex = HelpPageIndex < HelpPageCount - 1 ? HelpPageIndex + 1 : HelpPageCount - 1;
            break;

          case 'U':
          case 'u':
            if (!MultiHelpPage) {
              ControlFlag = CfReadKey;
              break;
            }

            ControlFlag   = CfUpdateHelpString;
            HelpPageIndex = HelpPageIndex > 0 ? HelpPageIndex - 1 : 0;
            break;

          case CHAR_NULL:
            for (Index = 0; Index < mScanCodeNumber; Index++) {
              if (Key.ScanCode == gScanCodeToOperation[Index].ScanCode) {
                ScreenOperation = gScanCodeToOperation[Index].ScreenOperation;
                break;
              }
            }

            if (((FormData->Attribute & HII_DISPLAY_MODAL) != 0) && ((Key.ScanCode == SCAN_ESC) || (Index == mScanCodeNumber))) {
              //
              // ModalForm has no ESC key and Hot Key.
              //
              ControlFlag = CfReadKey;
            } else if (Index == mScanCodeNumber) {
              //
              // Check whether Key matches the registered hot key.
              //
              HotKey = NULL;
              HotKey = GetHotKeyFromRegisterList (&Key);
              if (HotKey != NULL) {
                ScreenOperation = UiHotKey;
              }
            }

            break;
        }

        break;

      case CfScreenOperation:
        if ((ScreenOperation != UiReset) && (ScreenOperation != UiHotKey)) {
          //
          // If the screen has no menu items, and the user didn't select UiReset or UiHotKey
          // ignore the selection and go back to reading keys.
          //
          if (IsListEmpty (&gMenuOption)) {
            ControlFlag = CfReadKey;
            break;
          }
        }

        for (Index = 0;
             Index < ARRAY_SIZE (gScreenOperationToControlFlag);
             Index++
             )
        {
          if (ScreenOperation == gScreenOperationToControlFlag[Index].ScreenOperation) {
            ControlFlag = gScreenOperationToControlFlag[Index].ControlFlag;
            break;
          }
        }

        break;

      case CfUiSelect:
        ControlFlag = CfRepaint;

        ASSERT (MenuOption != NULL);
        Statement = MenuOption->ThisTag;
        if (Statement->OpCode->OpCode == EFI_IFR_TEXT_OP) {
          break;
        }

        switch (Statement->OpCode->OpCode) {
          case EFI_IFR_REF_OP:
          case EFI_IFR_ACTION_OP:
          case EFI_IFR_RESET_BUTTON_OP:
            ControlFlag = CfExit;
            break;

          default:
            //
            // Editable Questions: oneof, ordered list, checkbox, numeric, string, password
            //
            RefreshKeyHelp (gFormData, Statement, TRUE);
            Status = ProcessOptions (MenuOption, TRUE, &OptionString, TRUE);

            if (OptionString != NULL) {
              FreePool (OptionString);
            }

            if (EFI_ERROR (Status)) {
              Repaint = TRUE;
              NewLine = TRUE;
              RefreshKeyHelp (gFormData, Statement, FALSE);
              break;
            } else {
              ControlFlag = CfExit;
              break;
            }
        }

        break;

      case CfUiReset:
        //
        // We come here when someone press ESC
        // If the policy is not exit front page when user press ESC, process here.
        //
        if (!FormExitPolicy ()) {
          Repaint     = TRUE;
          NewLine     = TRUE;
          ControlFlag = CfRepaint;
          break;
        }

        gUserInput->Action = BROWSER_ACTION_FORM_EXIT;
        ControlFlag        = CfExit;
        break;

      case CfUiHotKey:
        ControlFlag = CfRepaint;

        ASSERT (HotKey != NULL);

        if (FxConfirmPopup (HotKey->Action)) {
          gUserInput->Action = HotKey->Action;
          if ((HotKey->Action & BROWSER_ACTION_DEFAULT) == BROWSER_ACTION_DEFAULT) {
            gUserInput->DefaultId = HotKey->DefaultId;
          }

          ControlFlag = CfExit;
        } else {
          Repaint     = TRUE;
          NewLine     = TRUE;
          ControlFlag = CfRepaint;
        }

        break;

      case CfUiLeft:
        ControlFlag = CfRepaint;
        ASSERT (MenuOption != NULL);
        if ((MenuOption->ThisTag->OpCode->OpCode == EFI_IFR_DATE_OP) || (MenuOption->ThisTag->OpCode->OpCode == EFI_IFR_TIME_OP)) {
          if (MenuOption->Sequence != 0) {
            //
            // In the middle or tail of the Date/Time op-code set, go left.
            //
            ASSERT (NewPos != NULL);
            NewPos = NewPos->BackLink;
          }
        }

        break;

      case CfUiRight:
        ControlFlag = CfRepaint;
        ASSERT (MenuOption != NULL);
        if ((MenuOption->ThisTag->OpCode->OpCode == EFI_IFR_DATE_OP) || (MenuOption->ThisTag->OpCode->OpCode == EFI_IFR_TIME_OP)) {
          if (MenuOption->Sequence != 2) {
            //
            // In the middle or tail of the Date/Time op-code set, go left.
            //
            ASSERT (NewPos != NULL);
            NewPos = NewPos->ForwardLink;
          }
        }

        break;

      case CfUiUp:
        ControlFlag = CfRepaint;
        NewLine     = TRUE;

        SavedListEntry = NewPos;
        ASSERT (NewPos != NULL);

        MenuOption = MENU_OPTION_FROM_LINK (NewPos);
        ASSERT (MenuOption != NULL);

        //
        // Adjust Date/Time position before we advance forward.
        //
        AdjustDateAndTimePosition (TRUE, &NewPos);

        NewPos = NewPos->BackLink;
        //
        // Find next selectable menu or the first menu beyond current form.
        //
        Difference = MoveToNextStatement (TRUE, &NewPos, MenuOption->Row - TopRow, FALSE);
        if (Difference < 0) {
          //
          // We hit the begining MenuOption that can be focused
          // so we simply scroll to the top.
          //
          Repaint = TRUE;
          if ((TopOfScreen != gMenuOption.ForwardLink) || (SkipValue != 0)) {
            TopOfScreen = gMenuOption.ForwardLink;
            NewPos      = SavedListEntry;
            SkipValue   = 0;
          } else {
            //
            // Scroll up to the last page when we have arrived at top page.
            //
            TopOfScreen = FindTopOfScreenMenu (gMenuOption.BackLink, BottomRow - TopRow, &SkipValue);
            NewPos      = gMenuOption.BackLink;
            MoveToNextStatement (TRUE, &NewPos, BottomRow - TopRow, TRUE);
          }
        } else {
          NextMenuOption = MENU_OPTION_FROM_LINK (NewPos);

          if (MenuOption->Row < TopRow + Difference + NextMenuOption->Skip) {
            //
            // Previous focus MenuOption is above the TopOfScreen, so we need to scroll
            //
            TopOfScreen = NewPos;
            Repaint     = TRUE;
            SkipValue   = 0;
          }

          //
          // Check whether new highlight menu is selectable, if not, keep highlight on the old one.
          //
          // BottomRow - TopRow + 1 means the total rows current forms supported.
          // Difference + NextMenuOption->Skip + 1 means the distance between last highlight menu
          // and new top menu. New top menu will all shows in next form, but last highlight menu
          // may only shows 1 line. + 1 at right part means at least need to keep 1 line for the
          // last highlight menu.
          //
          if (!IsSelectable (NextMenuOption) && IsSelectable (MenuOption) &&
              (BottomRow - TopRow + 1 >= Difference + NextMenuOption->Skip + 1))
          {
            NewPos = SavedListEntry;
          }
        }

        UpdateStatusBar (INPUT_ERROR, FALSE);

        //
        // If we encounter a Date/Time op-code set, rewind to the first op-code of the set.
        //
        AdjustDateAndTimePosition (TRUE, &TopOfScreen);
        AdjustDateAndTimePosition (TRUE, &NewPos);

        UpdateHighlightMenuInfo (NewPos, TopOfScreen, SkipValue);
        break;

      case CfUiPageUp:
        //
        // SkipValue means lines is skipped when show the top menu option.
        //
        ControlFlag = CfRepaint;
        NewLine     = TRUE;
        Repaint     = TRUE;

        Link = TopOfScreen;
        //
        // First minus the menu of the top screen, it's value is SkipValue.
        //
        if (SkipValue >= BottomRow - TopRow + 1) {
          //
          // SkipValue > (BottomRow - TopRow + 1) means current menu has more than one
          // form of options to be show, so just update the SkipValue to show the next
          // parts of options.
          //
          SkipValue -= BottomRow - TopRow + 1;
          NewPos     = TopOfScreen;
          break;
        } else {
          Index = (BottomRow + 1) - SkipValue - TopRow;
        }

        TopOfScreen = FindTopOfScreenMenu (TopOfScreen, Index, &SkipValue);
        NewPos      = TopOfScreen;
        MoveToNextStatement (FALSE, &NewPos, BottomRow - TopRow, FALSE);

        UpdateStatusBar (INPUT_ERROR, FALSE);

        //
        // If we encounter a Date/Time op-code set, rewind to the first op-code of the set.
        // Don't do this when we are already in the first page.
        //
        AdjustDateAndTimePosition (TRUE, &TopOfScreen);
        AdjustDateAndTimePosition (TRUE, &NewPos);

        UpdateHighlightMenuInfo (NewPos, TopOfScreen, SkipValue);
        break;

      case CfUiPageDown:
        //
        // SkipValue means lines is skipped when show the top menu option.
        //
        ControlFlag = CfRepaint;
        NewLine     = TRUE;
        Repaint     = TRUE;

        Link           = TopOfScreen;
        NextMenuOption = MENU_OPTION_FROM_LINK (Link);
        Index          = TopRow + NextMenuOption->Skip - SkipValue;
        //
        // Count to the menu option which will show at the top of the next form.
        //
        while ((Index <= BottomRow + 1) && (Link->ForwardLink != &gMenuOption)) {
          Link           = Link->ForwardLink;
          NextMenuOption = MENU_OPTION_FROM_LINK (Link);
          Index          = Index + NextMenuOption->Skip;
        }

        if ((Link->ForwardLink == &gMenuOption) && (Index <= BottomRow + 1)) {
          //
          // Highlight on the last menu which can be highlight.
          //
          Repaint = FALSE;
          MoveToNextStatement (TRUE, &Link, Index - TopRow, TRUE);
        } else {
          //
          // Calculate the skip line for top of screen menu.
          //
          if (Link == TopOfScreen) {
            //
            // The top of screen menu option occupies the entire form.
            //
            SkipValue += BottomRow - TopRow + 1;
          } else {
            SkipValue = NextMenuOption->Skip - (Index - (BottomRow + 1));
          }

          TopOfScreen = Link;
          MenuOption  = NULL;
          //
          // Move to the Next selectable menu.
          //
          MoveToNextStatement (FALSE, &Link, BottomRow - TopRow, TRUE);
        }

        //
        // Save the menu as the next highlight menu.
        //
        NewPos = Link;

        UpdateStatusBar (INPUT_ERROR, FALSE);

        //
        // If we encounter a Date/Time op-code set, rewind to the first op-code of the set.
        // Don't do this when we are already in the last page.
        //
        AdjustDateAndTimePosition (TRUE, &TopOfScreen);
        AdjustDateAndTimePosition (TRUE, &NewPos);

        UpdateHighlightMenuInfo (NewPos, TopOfScreen, SkipValue);
        break;

      case CfUiDown:
        //
        // SkipValue means lines is skipped when show the top menu option.
        // NewPos  points to the menu which is highlighted now.
        //
        ControlFlag = CfRepaint;
        NewLine     = TRUE;

        if (NewPos == TopOfScreen) {
          Temp2 = SkipValue;
        } else {
          Temp2 = 0;
        }

        SavedListEntry = NewPos;
        //
        // Since the behavior of hitting the down arrow on a Date/Time op-code is intended
        // to be one that progresses to the next set of op-codes, we need to advance to the last
        // Date/Time op-code and leave the remaining logic in UiDown intact so the appropriate
        // checking can be done.  The only other logic we need to introduce is that if a Date/Time
        // op-code is the last entry in the menu, we need to rewind back to the first op-code of
        // the Date/Time op-code.
        //
        AdjustDateAndTimePosition (FALSE, &NewPos);

        MenuOption = MENU_OPTION_FROM_LINK (NewPos);
        NewPos     = NewPos->ForwardLink;
        //
        // Find the next selectable menu.
        //
        if (MenuOption->Row + MenuOption->Skip - Temp2 > BottomRow + 1) {
          if ((gMenuOption.ForwardLink == NewPos) || (&gMenuOption == NewPos)) {
            Difference = -1;
          } else {
            Difference = 0;
          }
        } else {
          Difference = MoveToNextStatement (FALSE, &NewPos, BottomRow + 1 - (MenuOption->Row + MenuOption->Skip - Temp2), FALSE);
        }

        if (Difference < 0) {
          //
          // Scroll to the first page.
          //
          if ((TopOfScreen != gMenuOption.ForwardLink) || (SkipValue != 0)) {
            TopOfScreen = gMenuOption.ForwardLink;
            Repaint     = TRUE;
            MenuOption  = NULL;
          } else {
            MenuOption = MENU_OPTION_FROM_LINK (SavedListEntry);
          }

          NewPos = gMenuOption.ForwardLink;
          MoveToNextStatement (FALSE, &NewPos, BottomRow - TopRow, TRUE);

          SkipValue = 0;
          //
          // If we are at the end of the list and sitting on a Date/Time op, rewind to the head.
          //
          AdjustDateAndTimePosition (TRUE, &TopOfScreen);
          AdjustDateAndTimePosition (TRUE, &NewPos);

          UpdateHighlightMenuInfo (NewPos, TopOfScreen, SkipValue);
          break;
        }

        //
        // Get next selected menu info.
        //
        AdjustDateAndTimePosition (FALSE, &NewPos);
        NextMenuOption = MENU_OPTION_FROM_LINK (NewPos);
        if (NextMenuOption->Row == 0) {
          UpdateOptionSkipLines (NextMenuOption);
        }

        //
        // Calculate new highlight menu end row.
        //
        Temp = (MenuOption->Row + MenuOption->Skip - Temp2) + Difference + NextMenuOption->Skip - 1;
        if (Temp > BottomRow) {
          //
          // Get the top screen menu info.
          //
          AdjustDateAndTimePosition (FALSE, &TopOfScreen);
          SavedMenuOption = MENU_OPTION_FROM_LINK (TopOfScreen);

          //
          // Current Top screen menu occupy (SavedMenuOption->Skip - SkipValue) rows.
          // Full shows the new selected menu need to skip (Temp - BottomRow - 1) rows.
          //
          if ((Temp - BottomRow) >= (SavedMenuOption->Skip - SkipValue)) {
            //
            // Skip the top op-code
            //
            TopOfScreen   = TopOfScreen->ForwardLink;
            DistanceValue = (Temp - BottomRow) - (SavedMenuOption->Skip - SkipValue);

            SavedMenuOption = MENU_OPTION_FROM_LINK (TopOfScreen);

            //
            // If we have a remainder, skip that many more op-codes until we drain the remainder
            // Special case is the selected highlight menu has more than one form of menus.
            //
            while (DistanceValue >= SavedMenuOption->Skip && TopOfScreen != NewPos) {
              //
              // Since the Difference is greater than or equal to this op-code's skip value, skip it
              //
              DistanceValue   = DistanceValue - (INTN)SavedMenuOption->Skip;
              TopOfScreen     = TopOfScreen->ForwardLink;
              SavedMenuOption = MENU_OPTION_FROM_LINK (TopOfScreen);
            }

            //
            // Since we will act on this op-code in the next routine, and increment the
            // SkipValue, set the skips to one less than what is required.
            //
            if (TopOfScreen != NewPos) {
              SkipValue = DistanceValue;
            } else {
              SkipValue = 0;
            }
          } else {
            //
            // Since we will act on this op-code in the next routine, and increment the
            // SkipValue, set the skips to one less than what is required.
            //
            SkipValue += Temp - BottomRow;
          }

          Repaint = TRUE;
        } else if (!IsSelectable (NextMenuOption)) {
          //
          // Continue to go down until scroll to next page or the selectable option is found.
          //
          ScreenOperation = UiDown;
          ControlFlag     = CfScreenOperation;
          break;
        }

        MenuOption = MENU_OPTION_FROM_LINK (SavedListEntry);

        //
        // Check whether new highlight menu is selectable, if not, keep highlight on the old one.
        //
        // BottomRow - TopRow + 1 means the total rows current forms supported.
        // Difference + NextMenuOption->Skip + 1 means the distance between last highlight menu
        // and new top menu. New top menu will all shows in next form, but last highlight menu
        // may only shows 1 line. + 1 at right part means at least need to keep 1 line for the
        // last highlight menu.
        //
        if (!IsSelectable (NextMenuOption) && IsSelectable (MenuOption) &&
            (BottomRow - TopRow + 1 >= Difference + NextMenuOption->Skip + 1))
        {
          NewPos = SavedListEntry;
        }

        UpdateStatusBar (INPUT_ERROR, FALSE);

        //
        // If we are at the end of the list and sitting on a Date/Time op, rewind to the head.
        //
        AdjustDateAndTimePosition (TRUE, &TopOfScreen);
        AdjustDateAndTimePosition (TRUE, &NewPos);

        UpdateHighlightMenuInfo (NewPos, TopOfScreen, SkipValue);
        break;

      case CfUiNoOperation:
        ControlFlag = CfRepaint;
        break;

      case CfExit:
        gST->ConOut->SetAttribute (gST->ConOut, EFI_TEXT_ATTR (EFI_LIGHTGRAY, EFI_BLACK));
        if (HelpString != NULL) {
          FreePool (HelpString);
        }

        if (HelpHeaderString != NULL) {
          FreePool (HelpHeaderString);
        }

        if (HelpBottomString != NULL) {
          FreePool (HelpBottomString);
        }

        return EFI_SUCCESS;

      default:
        break;
    }
  }
}

/**
  Free the UI Menu Option structure data.

  @param   MenuOptionList         Point to the menu option list which need to be free.

**/
VOID
FreeMenuOptionData (
  LIST_ENTRY  *MenuOptionList
  )
{
  LIST_ENTRY      *Link;
  UI_MENU_OPTION  *Option;

  //
  // Free menu option list
  //
  while (!IsListEmpty (MenuOptionList)) {
    Link   = GetFirstNode (MenuOptionList);
    Option = MENU_OPTION_FROM_LINK (Link);
    if (Option->Description != NULL) {
      FreePool (Option->Description);
    }

    RemoveEntryList (&Option->Link);
    FreePool (Option);
  }
}

/**

  Base on the browser status info to show an pop up message.

**/
VOID
BrowserStatusProcess (
  VOID
  )
{
  CHAR16              *ErrorInfo;
  EFI_INPUT_KEY       Key;
  EFI_EVENT           WaitList[2];
  EFI_EVENT           RefreshIntervalEvent;
  EFI_EVENT           TimeOutEvent;
  UINT8               TimeOut;
  EFI_STATUS          Status;
  UINTN               Index;
  WARNING_IF_CONTEXT  EventContext;
  EFI_IFR_OP_HEADER   *OpCodeBuf;
  EFI_STRING_ID       StringToken;
  CHAR16              DiscardChange;
  CHAR16              JumpToFormSet;
  CHAR16              *PrintString;

  if (gFormData->BrowserStatus == BROWSER_SUCCESS) {
    return;
  }

  StringToken          = 0;
  TimeOutEvent         = NULL;
  RefreshIntervalEvent = NULL;
  OpCodeBuf            = NULL;
  if (gFormData->HighLightedStatement != NULL) {
    OpCodeBuf = gFormData->HighLightedStatement->OpCode;
  }

  if (gFormData->BrowserStatus == (BROWSER_WARNING_IF)) {
    ASSERT (OpCodeBuf != NULL && OpCodeBuf->OpCode == EFI_IFR_WARNING_IF_OP);

    TimeOut     = ((EFI_IFR_WARNING_IF *)OpCodeBuf)->TimeOut;
    StringToken = ((EFI_IFR_WARNING_IF *)OpCodeBuf)->Warning;
  } else {
    TimeOut = 0;
    if ((gFormData->BrowserStatus == (BROWSER_NO_SUBMIT_IF)) &&
        ((OpCodeBuf != NULL) && (OpCodeBuf->OpCode == EFI_IFR_NO_SUBMIT_IF_OP)))
    {
      StringToken = ((EFI_IFR_NO_SUBMIT_IF *)OpCodeBuf)->Error;
    } else if ((gFormData->BrowserStatus == (BROWSER_INCONSISTENT_IF)) &&
               ((OpCodeBuf != NULL) && (OpCodeBuf->OpCode == EFI_IFR_INCONSISTENT_IF_OP)))
    {
      StringToken = ((EFI_IFR_INCONSISTENT_IF *)OpCodeBuf)->Error;
    }
  }

  if (StringToken != 0) {
    ErrorInfo = GetToken (StringToken, gFormData->HiiHandle);
  } else if (gFormData->ErrorString != NULL) {
    //
    // Only used to compatible with old setup browser.
    // Not use this field in new browser core.
    //
    ErrorInfo = gFormData->ErrorString;
  } else {
    switch (gFormData->BrowserStatus) {
      case BROWSER_SUBMIT_FAIL:
        ErrorInfo = gSaveFailed;
        break;

      case BROWSER_FORM_NOT_FOUND:
        ErrorInfo = gFormNotFound;
        break;

      case BROWSER_FORM_SUPPRESS:
        ErrorInfo = gFormSuppress;
        break;

      case BROWSER_PROTOCOL_NOT_FOUND:
        ErrorInfo = gProtocolNotFound;
        break;

      case BROWSER_SUBMIT_FAIL_NO_SUBMIT_IF:
        ErrorInfo = gNoSubmitIfFailed;
        break;

      case BROWSER_RECONNECT_FAIL:
        ErrorInfo = gReconnectFail;
        break;

      case BROWSER_RECONNECT_SAVE_CHANGES:
        ErrorInfo = gReconnectConfirmChanges;
        break;

      case BROWSER_RECONNECT_REQUIRED:
        ErrorInfo = gReconnectRequired;
        break;

      default:
        ErrorInfo = gBrowserError;
        break;
    }
  }

  switch (gFormData->BrowserStatus) {
    case BROWSER_SUBMIT_FAIL:
    case BROWSER_SUBMIT_FAIL_NO_SUBMIT_IF:
    case BROWSER_RECONNECT_SAVE_CHANGES:
      ASSERT (gUserInput != NULL);
      if (gFormData->BrowserStatus == (BROWSER_SUBMIT_FAIL)) {
        PrintString   = gSaveProcess;
        JumpToFormSet = gJumpToFormSet[0];
        DiscardChange = gDiscardChange[0];
      } else if (gFormData->BrowserStatus == (BROWSER_RECONNECT_SAVE_CHANGES)) {
        PrintString   = gChangesOpt;
        JumpToFormSet = gConfirmOptYes[0];
        DiscardChange = gConfirmOptNo[0];
      } else {
        PrintString   = gSaveNoSubmitProcess;
        JumpToFormSet = gCheckError[0];
        DiscardChange = gDiscardChange[0];
      }

      do {
        CreateDialog (&Key, gEmptyString, ErrorInfo, PrintString, gEmptyString, NULL);
      } while (((Key.UnicodeChar | UPPER_LOWER_CASE_OFFSET) != (DiscardChange | UPPER_LOWER_CASE_OFFSET)) &&
               ((Key.UnicodeChar | UPPER_LOWER_CASE_OFFSET) != (JumpToFormSet | UPPER_LOWER_CASE_OFFSET)));

      if ((Key.UnicodeChar | UPPER_LOWER_CASE_OFFSET) == (DiscardChange | UPPER_LOWER_CASE_OFFSET)) {
        gUserInput->Action = BROWSER_ACTION_DISCARD;
      } else {
        gUserInput->Action = BROWSER_ACTION_GOTO;
      }

      break;

    default:
      if (TimeOut == 0) {
        do {
          CreateDialog (&Key, gEmptyString, ErrorInfo, gPressEnter, gEmptyString, NULL);
        } while (Key.UnicodeChar != CHAR_CARRIAGE_RETURN);
      } else {
        Status = gBS->CreateEvent (EVT_NOTIFY_WAIT, TPL_CALLBACK, EmptyEventProcess, NULL, &TimeOutEvent);
        ASSERT_EFI_ERROR (Status);

        EventContext.SyncEvent = TimeOutEvent;
        EventContext.TimeOut   = &TimeOut;
        EventContext.ErrorInfo = ErrorInfo;

        Status = gBS->CreateEvent (EVT_TIMER | EVT_NOTIFY_SIGNAL, TPL_CALLBACK, RefreshTimeOutProcess, &EventContext, &RefreshIntervalEvent);
        ASSERT_EFI_ERROR (Status);

        //
        // Show the dialog first to avoid long time not reaction.
        //
        gBS->SignalEvent (RefreshIntervalEvent);

        Status = gBS->SetTimer (RefreshIntervalEvent, TimerPeriodic, ONE_SECOND);
        ASSERT_EFI_ERROR (Status);

        while (TRUE) {
          Status = gST->ConIn->ReadKeyStroke (gST->ConIn, &Key);
          if (!EFI_ERROR (Status) && (Key.UnicodeChar == CHAR_CARRIAGE_RETURN)) {
            break;
          }

          if (Status != EFI_NOT_READY) {
            continue;
          }

          WaitList[0] = TimeOutEvent;
          WaitList[1] = gST->ConIn->WaitForKey;

          Status = gBS->WaitForEvent (2, WaitList, &Index);
          ASSERT_EFI_ERROR (Status);

          if (Index == 0) {
            //
            // Timeout occur, close the hoot time out event.
            //
            break;
          }
        }

        gBS->CloseEvent (TimeOutEvent);
        gBS->CloseEvent (RefreshIntervalEvent);
      }

      break;
  }

  if (StringToken != 0) {
    FreePool (ErrorInfo);
  }
}

/**
  Display one form, and return user input.

  @param FormData                Form Data to be shown.
  @param UserInputData           User input data.

  @retval EFI_SUCCESS            1.Form Data is shown, and user input is got.
                                 2.Error info has show and return.
  @retval EFI_INVALID_PARAMETER  The input screen dimension is not valid
  @retval EFI_NOT_FOUND          New form data has some error.
**/
EFI_STATUS
EFIAPI
FormDisplay (
  IN  FORM_DISPLAY_ENGINE_FORM  *FormData,
  OUT USER_INPUT                *UserInputData
  )
{
  EFI_STATUS  Status;

  ASSERT (FormData != NULL);
  if (FormData == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  gUserInput = UserInputData;
  gFormData  = FormData;

  //
  // Process the status info first.
  //
  BrowserStatusProcess ();
  if (gFormData->BrowserStatus != BROWSER_SUCCESS) {
    //
    // gFormData->BrowserStatus != BROWSER_SUCCESS, means only need to print the error info, return here.
    //
    return EFI_SUCCESS;
  }

  Status = DisplayPageFrame (FormData, &gStatementDimensions);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Global Widths should be initialized before any MenuOption creation
  // or the GetWidth() used in UiAddMenuOption() will return incorrect value.
  //
  //
  //  Left                                              right
  //   |<-.->|<-.........->|<- .........->|<-...........->|
  //     Skip    Prompt         Option         Help
  //
  gOptionBlockWidth = (CHAR16)((gStatementDimensions.RightColumn - gStatementDimensions.LeftColumn) / 3) + 1;
  gHelpBlockWidth   = (CHAR16)(gOptionBlockWidth - 1 - LEFT_SKIPPED_COLUMNS);
  gPromptBlockWidth = (CHAR16)(gStatementDimensions.RightColumn - gStatementDimensions.LeftColumn - 2 * (gOptionBlockWidth - 1) - 1);

  ConvertStatementToMenu ();

  //
  // Check whether layout is changed.
  //
  if (  mIsFirstForm
     || (gOldFormEntry.HiiHandle != FormData->HiiHandle)
     || (!CompareGuid (&gOldFormEntry.FormSetGuid, &FormData->FormSetGuid))
     || (gOldFormEntry.FormId != FormData->FormId))
  {
    mStatementLayoutIsChanged = TRUE;
  } else {
    mStatementLayoutIsChanged = FALSE;
  }

  Status = UiDisplayMenu (FormData);

  //
  // Backup last form info.
  //
  mIsFirstForm            = FALSE;
  gOldFormEntry.HiiHandle = FormData->HiiHandle;
  CopyGuid (&gOldFormEntry.FormSetGuid, &FormData->FormSetGuid);
  gOldFormEntry.FormId = FormData->FormId;

  //
  // Free the Ui menu option list.
  //
  FreeMenuOptionData (&gMenuOption);

  return Status;
}

/**
  Clear Screen to the initial state.
**/
VOID
EFIAPI
DriverClearDisplayPage (
  VOID
  )
{
  ClearDisplayPage ();
  mIsFirstForm = TRUE;
}

/**
  Set Buffer to Value for Size bytes.

  @param  Buffer                 Memory to set.
  @param  Size                   Number of bytes to set
  @param  Value                  Value of the set operation.

**/
VOID
SetUnicodeMem (
  IN VOID    *Buffer,
  IN UINTN   Size,
  IN CHAR16  Value
  )
{
  CHAR16  *Ptr;

  Ptr = Buffer;
  while ((Size--)  != 0) {
    *(Ptr++) = Value;
  }
}

/**
  Initialize Setup Browser driver.

  @param ImageHandle     The image handle.
  @param SystemTable     The system table.

  @retval EFI_SUCCESS    The Setup Browser module is initialized correctly..
  @return Other value if failed to initialize the Setup Browser module.

**/
EFI_STATUS
EFIAPI
InitializeDisplayEngine (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_STATUS                              Status;
  EFI_INPUT_KEY                           HotKey;
  EFI_STRING                              NewString;
  EDKII_FORM_BROWSER_EXTENSION2_PROTOCOL  *FormBrowserEx2;

  //
  // Publish our HII data
  //
  gHiiHandle = HiiAddPackages (
                 &gDisplayEngineGuid,
                 ImageHandle,
                 DisplayEngineStrings,
                 NULL
                 );
  ASSERT (gHiiHandle != NULL);

  //
  // Install Form Display protocol
  //
  Status = gBS->InstallProtocolInterface (
                  &mPrivateData.Handle,
                  &gEdkiiFormDisplayEngineProtocolGuid,
                  EFI_NATIVE_INTERFACE,
                  &mPrivateData.FromDisplayProt
                  );
  ASSERT_EFI_ERROR (Status);

  //
  // Install HII Popup Protocol.
  //
  Status = gBS->InstallProtocolInterface (
                  &mPrivateData.Handle,
                  &gEfiHiiPopupProtocolGuid,
                  EFI_NATIVE_INTERFACE,
                  &mPrivateData.HiiPopup
                  );
  ASSERT_EFI_ERROR (Status);

  InitializeDisplayStrings ();

  ZeroMem (&gHighligthMenuInfo, sizeof (gHighligthMenuInfo));
  ZeroMem (&gOldFormEntry, sizeof (gOldFormEntry));

  //
  // Use BrowserEx2 protocol to register HotKey.
  //
  Status = gBS->LocateProtocol (&gEdkiiFormBrowserEx2ProtocolGuid, NULL, (VOID **)&FormBrowserEx2);
  if (!EFI_ERROR (Status)) {
    //
    // Register the default HotKey F9 and F10 again.
    //
    HotKey.UnicodeChar = CHAR_NULL;
    HotKey.ScanCode    = SCAN_F10;
    NewString          = HiiGetString (gHiiHandle, STRING_TOKEN (FUNCTION_TEN_STRING), NULL);
    ASSERT (NewString != NULL);
    FormBrowserEx2->RegisterHotKey (&HotKey, BROWSER_ACTION_SUBMIT, 0, NewString);
    FreePool (NewString);

    HotKey.ScanCode = SCAN_F9;
    NewString       = HiiGetString (gHiiHandle, STRING_TOKEN (FUNCTION_NINE_STRING), NULL);
    ASSERT (NewString != NULL);
    FormBrowserEx2->RegisterHotKey (&HotKey, BROWSER_ACTION_DEFAULT, EFI_HII_DEFAULT_CLASS_STANDARD, NewString);
    FreePool (NewString);
  }

  return EFI_SUCCESS;
}

/**
  This is the default unload handle for display core drivers.

  @param[in]  ImageHandle       The drivers' driver image.

  @retval EFI_SUCCESS           The image is unloaded.
  @retval Others                Failed to unload the image.

**/
EFI_STATUS
EFIAPI
UnloadDisplayEngine (
  IN EFI_HANDLE  ImageHandle
  )
{
  HiiRemovePackages (gHiiHandle);

  FreeDisplayStrings ();

  if (gHighligthMenuInfo.HLTOpCode != NULL) {
    FreePool (gHighligthMenuInfo.HLTOpCode);
  }

  if (gHighligthMenuInfo.TOSOpCode != NULL) {
    FreePool (gHighligthMenuInfo.TOSOpCode);
  }

  return EFI_SUCCESS;
}
