/** @file
Utility functions for User Interface functions.

Copyright (c) 2004 - 2013, Intel Corporation. All rights reserved.<BR>
This program and the accompanying materials
are licensed and made available under the terms and conditions of the BSD License
which accompanies this distribution.  The full text of the license may be found at
http://opensource.org/licenses/bsd-license.php

THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.

**/

#include "Setup.h"

LIST_ENTRY          gMenuOption;
LIST_ENTRY          gMenuList;
MENU_REFRESH_ENTRY  *gMenuRefreshHead;                // Menu list used for refresh timer opcode.
MENU_REFRESH_ENTRY  *gMenuEventGuidRefreshHead;       // Menu list used for refresh event guid opcode.

//
// 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 = sizeof (gScanCodeToOperation) / sizeof (gScanCodeToOperation[0]);

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

BOOLEAN  mInputError;
BOOLEAN GetLineByWidthFinished = FALSE;


/**
  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 Menu option list.

**/
VOID
UiInitMenu (
  VOID
  )
{
  InitializeListHead (&gMenuOption);
}


/**
  Free Menu option linked list.

**/
VOID
UiFreeMenu (
  VOID
  )
{
  UI_MENU_OPTION  *MenuOption;

  while (!IsListEmpty (&gMenuOption)) {
    MenuOption = MENU_OPTION_FROM_LINK (gMenuOption.ForwardLink);
    RemoveEntryList (&MenuOption->Link);

    //
    // We allocated space for this description when we did a GetToken, free it here
    //
    if (MenuOption->Skip != 0) {
      //
      // For date/time, MenuOption->Description is shared by three Menu Options
      // Data format :      [01/02/2004]      [11:22:33]
      // Line number :        0  0    1         0  0  1
      //
      FreePool (MenuOption->Description);
    }
    FreePool (MenuOption);
  }
}


/**
  Create a menu with specified formset GUID and form ID, and add it as a child
  of the given parent menu.

  @param  Parent                 The parent of menu to be added.
  @param  HiiHandle              Hii handle related to this formset.
  @param  FormSetGuid            The Formset Guid of menu to be added.
  @param  FormId                 The Form ID of menu to be added.

  @return A pointer to the newly added menu or NULL if memory is insufficient.

**/
UI_MENU_LIST *
UiAddMenuList (
  IN OUT UI_MENU_LIST     *Parent,
  IN EFI_HII_HANDLE       HiiHandle,
  IN EFI_GUID             *FormSetGuid,
  IN UINT16               FormId
  )
{
  UI_MENU_LIST  *MenuList;

  MenuList = AllocateZeroPool (sizeof (UI_MENU_LIST));
  if (MenuList == NULL) {
    return NULL;
  }

  MenuList->Signature = UI_MENU_LIST_SIGNATURE;
  InitializeListHead (&MenuList->ChildListHead);

  MenuList->HiiHandle = HiiHandle;
  CopyMem (&MenuList->FormSetGuid, FormSetGuid, sizeof (EFI_GUID));
  MenuList->FormId = FormId;
  MenuList->Parent = Parent;

  if (Parent == NULL) {
    //
    // If parent is not specified, it is the root Form of a Formset
    //
    InsertTailList (&gMenuList, &MenuList->Link);
  } else {
    InsertTailList (&Parent->ChildListHead, &MenuList->Link);
  }

  return MenuList;
}


/**
  Search Menu with given FormId, FormSetGuid and Handle in all cached menu list.

  @param  Parent                 The parent of menu to search.
  @param  Handle                 Hii handle related to this formset.
  @param  FormSetGuid            The Formset GUID of the menu to search.  
  @param  FormId                 The Form ID of menu to search.

  @return A pointer to menu found or NULL if not found.

**/
UI_MENU_LIST *
UiFindChildMenuList (
  IN UI_MENU_LIST         *Parent,
  IN EFI_HII_HANDLE       Handle,
  IN EFI_GUID             *FormSetGuid, 
  IN UINT16               FormId
  )
{
  LIST_ENTRY      *Link;
  UI_MENU_LIST    *Child;
  UI_MENU_LIST    *MenuList;

  ASSERT (Parent != NULL);

  if (Parent->FormId == FormId && CompareGuid (FormSetGuid, &Parent->FormSetGuid) && Parent->HiiHandle == Handle) {
    return Parent;
  }

  Link = GetFirstNode (&Parent->ChildListHead);
  while (!IsNull (&Parent->ChildListHead, Link)) {
    Child = UI_MENU_LIST_FROM_LINK (Link);

    MenuList = UiFindChildMenuList (Child, Handle, FormSetGuid, FormId);
    if (MenuList != NULL) {
      return MenuList;
    }

    Link = GetNextNode (&Parent->ChildListHead, Link);
  }

  return NULL;
}


/**
  Search Menu with given Handle, FormSetGuid and FormId in all cached menu list.

  @param  Handle                 Hii handle related to this formset.
  @param  FormSetGuid            The Formset GUID of the menu to search.
  @param  FormId                 The Form ID of menu to search.

  @return A pointer to menu found or NULL if not found.

**/
UI_MENU_LIST *
UiFindMenuList (
  IN EFI_HII_HANDLE       Handle,
  IN EFI_GUID             *FormSetGuid,
  IN UINT16               FormId
  )
{
  LIST_ENTRY      *Link;
  UI_MENU_LIST    *MenuList;
  UI_MENU_LIST    *Child;

  Link = GetFirstNode (&gMenuList);
  while (!IsNull (&gMenuList, Link)) {
    MenuList = UI_MENU_LIST_FROM_LINK (Link);

    Child = UiFindChildMenuList(MenuList, Handle, FormSetGuid, FormId);
    if (Child != NULL) {

      //
      // If this form already in the menu history list,
      // just free the list between old this form.
      //
      UiFreeMenuList(&Child->ChildListHead);
      return Child;
    }

    Link = GetNextNode (&gMenuList, Link);
  }

  return NULL;
}

/**
  Free Menu list linked list.

  @param  MenuListHead    One Menu list point in the menu list.

**/
VOID
UiFreeMenuList (
  LIST_ENTRY   *MenuListHead
  )
{
  UI_MENU_LIST    *MenuList;

  while (!IsListEmpty (MenuListHead)) {
    MenuList = UI_MENU_LIST_FROM_LINK (MenuListHead->ForwardLink);
    RemoveEntryList (&MenuList->Link);
    
    UiFreeMenuList(&MenuList->ChildListHead);
    FreePool (MenuList);
  }

}

/**
  Free Menu option linked list.

**/
VOID
UiFreeRefreshList (
  VOID
  )
{
  MENU_REFRESH_ENTRY  *OldMenuRefreshEntry;

  while (gMenuRefreshHead != NULL) {
    OldMenuRefreshEntry = gMenuRefreshHead->Next;
    FreePool (gMenuRefreshHead);
    gMenuRefreshHead = OldMenuRefreshEntry;
  }

  while (gMenuEventGuidRefreshHead != NULL) {
    OldMenuRefreshEntry = gMenuEventGuidRefreshHead->Next;
    if (gMenuEventGuidRefreshHead != NULL) {
      gBS->CloseEvent(gMenuEventGuidRefreshHead->Event);
    }
    FreePool (gMenuEventGuidRefreshHead);
    gMenuEventGuidRefreshHead = OldMenuRefreshEntry;
  }
}


/**
  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_BROWSER_STATEMENT          *Statement;

  ASSERT (MenuOption != NULL && OptionString != NULL);
  
  Statement = MenuOption->ThisTag;
  
  //
  // 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->Operand == EFI_IFR_DATE_OP) {
    //
    // OptionString format is: <**:  **: ****>
    //                        |month|day|year|
    //                          4     3    5
    //
    if ((Statement->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 ((Statement->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 ((Statement->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->Operand == EFI_IFR_TIME_OP) {
    //
    // OptionString format is: <**:  **:    **>
    //                        |hour|minute|second|
    //                          4     3      3
    //
    if ((Statement->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 ((Statement->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 ((Statement->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' ');
    }
  }
}

/**
  Refresh question.

  @param     MenuRefreshEntry    Menu refresh structure which has info about the refresh question.
**/
EFI_STATUS 
RefreshQuestion (
  IN   MENU_REFRESH_ENTRY    *MenuRefreshEntry
  )
{
  CHAR16                          *OptionString;
  EFI_STATUS                      Status;
  UI_MENU_SELECTION               *Selection;
  FORM_BROWSER_STATEMENT          *Question;

  Selection = MenuRefreshEntry->Selection;
  Question = MenuRefreshEntry->MenuOption->ThisTag;

  Status = GetQuestionValue (Selection->FormSet, Selection->Form, Question, GetSetValueWithHiiDriver);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  OptionString = NULL;
  ProcessOptions (Selection, MenuRefreshEntry->MenuOption, FALSE, &OptionString);

  if (OptionString != NULL) {
    //
    // If old Text is longer than new string, need to clean the old string before paint the newer.
    // This option is no need for time/date opcode, because time/data opcode has fixed string length.
    //
    if ((MenuRefreshEntry->MenuOption->ThisTag->Operand != EFI_IFR_DATE_OP) &&
      (MenuRefreshEntry->MenuOption->ThisTag->Operand != EFI_IFR_TIME_OP)) {
      ClearLines (
        MenuRefreshEntry->CurrentColumn, 
        MenuRefreshEntry->CurrentColumn + gOptionBlockWidth - 1,
        MenuRefreshEntry->CurrentRow,
        MenuRefreshEntry->CurrentRow,
        PcdGet8 (PcdBrowserFieldTextColor) | FIELD_BACKGROUND
        );
    }

    gST->ConOut->SetAttribute (gST->ConOut, MenuRefreshEntry->CurrentAttribute);
    ProcessStringForDateTime(MenuRefreshEntry->MenuOption, OptionString, FALSE);
    PrintStringAt (MenuRefreshEntry->CurrentColumn, MenuRefreshEntry->CurrentRow, OptionString);
    FreePool (OptionString);
  }

  //
  // Question value may be changed, need invoke its Callback()
  //
  Status = ProcessCallBackFunction (Selection, Question, EFI_BROWSER_ACTION_CHANGING, FALSE);

  return Status;
}

/**
  Refresh the question which has refresh guid event attribute.
  
  @param Event    The event which has this function related.     
  @param Context  The input context info related to this event or the status code return to the caller.
**/
VOID
EFIAPI
RefreshQuestionNotify(
  IN      EFI_EVENT Event,
  IN      VOID      *Context
  )
{
  MENU_REFRESH_ENTRY              *MenuRefreshEntry;
  UI_MENU_SELECTION               *Selection;

  //
  // Reset FormPackage update flag
  //
  mHiiPackageListUpdated = FALSE;

  MenuRefreshEntry = (MENU_REFRESH_ENTRY *)Context;
  ASSERT (MenuRefreshEntry != NULL);
  Selection = MenuRefreshEntry->Selection;

  RefreshQuestion (MenuRefreshEntry);
  
  if (mHiiPackageListUpdated) {
    //
    // Package list is updated, force to reparse IFR binary of target Formset
    //
    mHiiPackageListUpdated = FALSE;
    Selection->Action = UI_ACTION_REFRESH_FORMSET;
  } 
}


/**
  Refresh screen.

**/
EFI_STATUS
RefreshForm (
  VOID
  )
{
  MENU_REFRESH_ENTRY              *MenuRefreshEntry;
  EFI_STATUS                      Status;
  UI_MENU_SELECTION               *Selection;

  if (gMenuRefreshHead != NULL) {
    //
    // call from refresh interval process.
    //
    MenuRefreshEntry = gMenuRefreshHead;
    Selection = MenuRefreshEntry->Selection;
    //
    // Reset FormPackage update flag
    //
    mHiiPackageListUpdated = FALSE;

    do {
      Status = RefreshQuestion (MenuRefreshEntry);
      if (EFI_ERROR (Status)) {
        return Status;
      }

      MenuRefreshEntry = MenuRefreshEntry->Next;

    } while (MenuRefreshEntry != NULL);

    if (mHiiPackageListUpdated) {
      //
      // Package list is updated, force to reparse IFR binary of target Formset
      //
      mHiiPackageListUpdated = FALSE;
      Selection->Action = UI_ACTION_REFRESH_FORMSET;
      return EFI_SUCCESS;
    }
  }

  return EFI_TIMEOUT;
}


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

  @param  Event                  The event to wait for
  @param  Timeout                An optional timeout value in 100 ns units.
  @param  RefreshInterval        Menu refresh interval (in seconds).

  @retval EFI_SUCCESS            Event fired before Timeout expired.
  @retval EFI_TIME_OUT           Timout expired before Event fired.

**/
EFI_STATUS
UiWaitForSingleEvent (
  IN EFI_EVENT                Event,
  IN UINT64                   Timeout, OPTIONAL
  IN UINT8                    RefreshInterval OPTIONAL
  )
{
  EFI_STATUS  Status;
  UINTN       Index;
  EFI_EVENT   TimerEvent;
  EFI_EVENT   WaitList[2];

  if (Timeout != 0) {
    //
    // Create a timer event
    //
    Status = gBS->CreateEvent (EVT_TIMER, 0, NULL, NULL, &TimerEvent);
    if (!EFI_ERROR (Status)) {
      //
      // Set the timer event
      //
      gBS->SetTimer (
            TimerEvent,
            TimerRelative,
            Timeout
            );

      //
      // Wait for the original event or the timer
      //
      WaitList[0] = Event;
      WaitList[1] = TimerEvent;
      Status      = gBS->WaitForEvent (2, WaitList, &Index);
      gBS->CloseEvent (TimerEvent);

      //
      // If the timer expired, change the return to timed out
      //
      if (!EFI_ERROR (Status) && Index == 1) {
        Status = EFI_TIMEOUT;
      }
    }
  } else {
    //
    // Update screen every second
    //
    if (RefreshInterval == 0) {
      Timeout = ONE_SECOND;
    } else {
      Timeout = RefreshInterval * ONE_SECOND;
    }

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

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

      //
      // Wait for the original event or the timer
      //
      WaitList[0] = Event;
      WaitList[1] = TimerEvent;
      Status      = gBS->WaitForEvent (2, WaitList, &Index);

      //
      // If the timer expired, update anything that needs a refresh and keep waiting
      //
      if (!EFI_ERROR (Status) && Index == 1) {
        Status = EFI_TIMEOUT;
        if (RefreshInterval != 0) {
          Status = RefreshForm ();
        }
      }

      gBS->CloseEvent (TimerEvent);
    } while (Status == EFI_TIMEOUT);
  }

  return Status;
}


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

  @param  String                 String description for this option.
  @param  Handle                 Hii handle for the package list.
  @param  Form                   The form this statement belong to.
  @param  Statement              Statement of this Menu Option.
  @param  NumberOfLines          Display lines for this Menu Option.
  @param  MenuItemCount          The index for this Option in the Menu.

  @retval Pointer                Pointer to the added Menu Option.

**/
UI_MENU_OPTION *
UiAddMenuOption (
  IN CHAR16                  *String,
  IN EFI_HII_HANDLE          Handle,
  IN FORM_BROWSER_FORM       *Form,
  IN FORM_BROWSER_STATEMENT  *Statement,
  IN UINT16                  NumberOfLines,
  IN UINT16                  MenuItemCount
  )
{
  UI_MENU_OPTION  *MenuOption;
  UINTN           Index;
  UINTN           Count;

  Count = 1;
  MenuOption = NULL;

  if (Statement->Operand == EFI_IFR_DATE_OP || Statement->Operand == EFI_IFR_TIME_OP) {
    //
    // Add three MenuOptions for Date/Time
    // Data format :      [01/02/2004]      [11:22:33]
    // Line number :        0  0    1         0  0  1
    //
    NumberOfLines = 0;
    Count = 3;

    if (Statement->Storage == NULL) {
      //
      // For RTC type of date/time, set default refresh interval to be 1 second
      //
      if (Statement->RefreshInterval == 0) {
        Statement->RefreshInterval = 1;
      }
    }
  }

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

    MenuOption->Signature   = UI_MENU_OPTION_SIGNATURE;
    MenuOption->Description = String;
    MenuOption->Handle      = Handle;
    MenuOption->ThisTag     = Statement;
    MenuOption->EntryNumber = MenuItemCount;

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

    if (EvaluateExpressionList(Statement->Expression, FALSE, NULL, NULL) == ExpressGrayOut ) {
      MenuOption->GrayOut = TRUE;
    } else {
      MenuOption->GrayOut = FALSE;
    }

    //
    // If the form or the question has the lock attribute, deal same as grayout.
    //
    if (Form->Locked || Statement->Locked) {
      MenuOption->GrayOut = TRUE;
    }
    
    switch (Statement->Operand) {
    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 skipped on purpose
      //
    default:
      MenuOption->IsQuestion = FALSE;
      break;
    }

    if ((Statement->ValueExpression != NULL) ||
        ((Statement->QuestionFlags & EFI_IFR_FLAG_READ_ONLY) != 0)) {
      MenuOption->ReadOnly = TRUE;
      if (FeaturePcdGet (PcdBrowerGrayOutReadOnlyMenu)) {
        MenuOption->GrayOut = TRUE;
      }
    }

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

  return MenuOption;
}


/**
  Routine used to abstract a generic dialog interface and return the selected key or string

  @param  NumberOfLines          The number of lines for the dialog box
  @param  HotKey                 Defines whether a single character is parsed
                                 (TRUE) and returned in KeyValue or a string is
                                 returned in StringBuffer.  Two special characters
                                 are considered when entering a string, a SCAN_ESC
                                 and an CHAR_CARRIAGE_RETURN.  SCAN_ESC terminates
                                 string input and returns
  @param  MaximumStringSize      The maximum size in bytes of a typed in string
                                 (each character is a CHAR16) and the minimum
                                 string returned is two bytes
  @param  StringBuffer           The passed in pointer to the buffer which will
                                 hold the typed in string if HotKey is FALSE
  @param  KeyValue               The EFI_KEY value returned if HotKey is TRUE..
  @param  ...                    A series of (quantity == NumberOfLines) text
                                 strings which will be used to construct the dialog
                                 box

  @retval EFI_SUCCESS            Displayed dialog and received user interaction
  @retval EFI_INVALID_PARAMETER  One of the parameters was invalid (e.g.
                                 (StringBuffer == NULL) && (HotKey == FALSE))
  @retval EFI_DEVICE_ERROR       User typed in an ESC character to exit the routine

**/
EFI_STATUS
EFIAPI
CreateDialog (
  IN  UINTN                       NumberOfLines,
  IN  BOOLEAN                     HotKey,
  IN  UINTN                       MaximumStringSize,
  OUT CHAR16                      *StringBuffer,
  OUT EFI_INPUT_KEY               *KeyValue,
  ...
  )
{
  VA_LIST       Marker;
  UINTN         Count;
  EFI_INPUT_KEY Key;
  UINTN         LargestString;
  CHAR16        *TempString;
  CHAR16        *BufferedString;
  CHAR16        *StackString;
  CHAR16        KeyPad[2];
  UINTN         Start;
  UINTN         Top;
  UINTN         Index;
  EFI_STATUS    Status;
  BOOLEAN       SelectionComplete;
  UINTN         InputOffset;
  UINTN         CurrentAttribute;
  UINTN         DimensionsWidth;
  UINTN         DimensionsHeight;

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

  SelectionComplete = FALSE;
  InputOffset       = 0;
  TempString        = AllocateZeroPool (MaximumStringSize * 2);
  BufferedString    = AllocateZeroPool (MaximumStringSize * 2);
  CurrentAttribute  = gST->ConOut->Mode->Attribute;

  ASSERT (TempString);
  ASSERT (BufferedString);

  //
  // Zero the outgoing buffer
  //
  ZeroMem (StringBuffer, MaximumStringSize);

  if (HotKey) {
    if (KeyValue == NULL) {
      return EFI_INVALID_PARAMETER;
    }
  } else {
    if (StringBuffer == NULL) {
      return EFI_INVALID_PARAMETER;
    }
  }
  //
  // Disable cursor
  //
  gST->ConOut->EnableCursor (gST->ConOut, FALSE);

  LargestString = 0;

  VA_START (Marker, KeyValue);

  //
  // Determine the largest string in the dialog box
  // Notice we are starting with 1 since String is the first string
  //
  for (Count = 0; Count < NumberOfLines; Count++) {
    StackString = VA_ARG (Marker, CHAR16 *);

    if (StackString[0] == L' ') {
      InputOffset = Count + 1;
    }

    if ((GetStringWidth (StackString) / 2) > LargestString) {
      //
      // Size of the string visually and subtract the width by one for the null-terminator
      //
      LargestString = (GetStringWidth (StackString) / 2);
    }
  }
  VA_END (Marker);

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

  Count = 0;

  //
  // Display the Popup
  //
  VA_START (Marker, KeyValue);
  CreateSharedPopUp (LargestString, NumberOfLines, Marker);
  VA_END (Marker);

  //
  // Take the first key typed and report it back?
  //
  if (HotKey) {
    Status = WaitForKeyStroke (&Key);
    ASSERT_EFI_ERROR (Status);
    CopyMem (KeyValue, &Key, sizeof (EFI_INPUT_KEY));

  } else {
    do {
      Status = WaitForKeyStroke (&Key);

      switch (Key.UnicodeChar) {
      case CHAR_NULL:
        switch (Key.ScanCode) {
        case SCAN_ESC:
          FreePool (TempString);
          FreePool (BufferedString);
          gST->ConOut->SetAttribute (gST->ConOut, CurrentAttribute);
          gST->ConOut->EnableCursor (gST->ConOut, TRUE);
          return EFI_DEVICE_ERROR;

        default:
          break;
        }

        break;

      case CHAR_CARRIAGE_RETURN:
        SelectionComplete = TRUE;
        FreePool (TempString);
        FreePool (BufferedString);
        gST->ConOut->SetAttribute (gST->ConOut, CurrentAttribute);
        gST->ConOut->EnableCursor (gST->ConOut, TRUE);
        return EFI_SUCCESS;
        break;

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

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

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

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

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

        PrintStringAt (Start + 1, Top + InputOffset, BufferedString);
        break;
      }
    } while (!SelectionComplete);
  }

  gST->ConOut->SetAttribute (gST->ConOut, CurrentAttribute);
  gST->ConOut->EnableCursor (gST->ConOut, TRUE);
  return EFI_SUCCESS;
}

/**
  Draw a pop up windows based on the dimension, number of lines and
  strings specified.

  @param RequestedWidth  The width of the pop-up.
  @param NumberOfLines   The number of lines.
  @param Marker          The variable argument list for the list of string to be printed.

**/
VOID
CreateSharedPopUp (
  IN  UINTN                       RequestedWidth,
  IN  UINTN                       NumberOfLines,
  IN  VA_LIST                     Marker
  )
{
  UINTN   Index;
  UINTN   Count;
  CHAR16  Character;
  UINTN   Start;
  UINTN   End;
  UINTN   Top;
  UINTN   Bottom;
  CHAR16  *String;
  UINTN   DimensionsWidth;
  UINTN   DimensionsHeight;

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

  gST->ConOut->SetAttribute (gST->ConOut, POPUP_TEXT | POPUP_BACKGROUND);

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

  //
  // Subtract the PopUp width from total Columns, allow for one space extra on
  // each end plus a border.
  //
  Start     = (DimensionsWidth - RequestedWidth - 2) / 2 + gScreenDimensions.LeftColumn + 1;
  End       = Start + RequestedWidth + 1;

  Top       = ((DimensionsHeight - NumberOfLines - 2) / 2) + gScreenDimensions.TopRow - 1;
  Bottom    = Top + NumberOfLines + 2;

  Character = BOXDRAW_DOWN_RIGHT;
  PrintCharAt (Start, Top, Character);
  Character = BOXDRAW_HORIZONTAL;
  for (Index = Start; Index + 2 < End; Index++) {
    PrintChar (Character);
  }

  Character = BOXDRAW_DOWN_LEFT;
  PrintChar (Character);
  Character = BOXDRAW_VERTICAL;

  Count = 0;
  for (Index = Top; Index + 2 < Bottom; Index++, Count++) {
    String = VA_ARG (Marker, CHAR16*);

    //
    // This will clear the background of the line - we never know who might have been
    // here before us.  This differs from the next clear in that it used the non-reverse
    // video for normal printing.
    //
    if (GetStringWidth (String) / 2 > 1) {
      ClearLines (Start, End, Index + 1, Index + 1, POPUP_TEXT | POPUP_BACKGROUND);
    }

    //
    // Passing in a space results in the assumption that this is where typing will occur
    //
    if (String[0] == L' ') {
      ClearLines (Start + 1, End - 1, Index + 1, Index + 1, POPUP_INVERSE_TEXT | POPUP_INVERSE_BACKGROUND);
    }

    //
    // Passing in a NULL results in a blank space
    //
    if (String[0] == CHAR_NULL) {
      ClearLines (Start, End, Index + 1, Index + 1, POPUP_TEXT | POPUP_BACKGROUND);
    }

    PrintStringAt (
      ((DimensionsWidth - GetStringWidth (String) / 2) / 2) + gScreenDimensions.LeftColumn + 1,
      Index + 1,
      String
      );
    gST->ConOut->SetAttribute (gST->ConOut, POPUP_TEXT | POPUP_BACKGROUND);
    PrintCharAt (Start, Index + 1, Character);
    PrintCharAt (End - 1, Index + 1, Character);
  }

  Character = BOXDRAW_UP_RIGHT;
  PrintCharAt (Start, Bottom - 1, Character);
  Character = BOXDRAW_HORIZONTAL;
  for (Index = Start; Index + 2 < End; Index++) {
    PrintChar (Character);
  }

  Character = BOXDRAW_UP_LEFT;
  PrintChar (Character);
}

/**
  Draw a pop up windows based on the dimension, number of lines and
  strings specified.

  @param RequestedWidth  The width of the pop-up.
  @param NumberOfLines   The number of lines.
  @param ...             A series of text strings that displayed in the pop-up.

**/
VOID
EFIAPI
CreateMultiStringPopUp (
  IN  UINTN                       RequestedWidth,
  IN  UINTN                       NumberOfLines,
  ...
  )
{
  VA_LIST Marker;

  VA_START (Marker, NumberOfLines);

  CreateSharedPopUp (RequestedWidth, NumberOfLines, Marker);

  VA_END (Marker);
}


/**
  Update status bar on the bottom of menu.

  @param  Selection              Current Selction info.
  @param  MessageType            The type of message to be shown.
  @param  Flags                  The flags in Question header.
  @param  State                  Set or clear.

**/
VOID
UpdateStatusBar (
  IN  UI_MENU_SELECTION           *Selection,
  IN  UINTN                       MessageType,
  IN  UINT8                       Flags,
  IN  BOOLEAN                     State
  )
{
  UINTN           Index;
  CHAR16          *NvUpdateMessage;
  CHAR16          *InputErrorMessage;
  LIST_ENTRY              *Link;
  FORM_BROWSER_FORMSET    *LocalFormSet;
  FORM_BROWSER_STATEMENT  *Question;
  
  NvUpdateMessage   = GetToken (STRING_TOKEN (NV_UPDATE_MESSAGE), gHiiHandle);
  InputErrorMessage = GetToken (STRING_TOKEN (INPUT_ERROR_MESSAGE), gHiiHandle);

  switch (MessageType) {
  case INPUT_ERROR:
    if (State) {
      gST->ConOut->SetAttribute (gST->ConOut, ERROR_TEXT);
      PrintStringAt (
        gScreenDimensions.LeftColumn + gPromptBlockWidth,
        gScreenDimensions.BottomRow - 1,
        InputErrorMessage
        );
      mInputError = TRUE;
    } else {
      gST->ConOut->SetAttribute (gST->ConOut, PcdGet8 (PcdBrowserFieldTextHighlightColor));
      for (Index = 0; Index < (GetStringWidth (InputErrorMessage) - 2) / 2; Index++) {
        PrintAt (gScreenDimensions.LeftColumn + gPromptBlockWidth + Index, gScreenDimensions.BottomRow - 1, L"  ");
      }

      mInputError = FALSE;
    }
    break;

  case NV_UPDATE_REQUIRED:
    //
    // Global setting support. Show configuration change on every form.
    //
    if (State) {
      gResetRequired    = (BOOLEAN) (gResetRequired | ((Flags & EFI_IFR_FLAG_RESET_REQUIRED) == EFI_IFR_FLAG_RESET_REQUIRED));

      if (Selection != NULL && Selection->Statement != NULL) {
        Question = Selection->Statement;
        if (Question->Storage != NULL || Question->Operand == EFI_IFR_DATE_OP || Question->Operand == EFI_IFR_TIME_OP) {
          //
          // Update only for Question value that need to be saved into Storage.
          //
          Selection->Form->NvUpdateRequired = TRUE;
        }
      }
      
      if (Selection == NULL || IsNvUpdateRequired (Selection->FormSet)) {
        gST->ConOut->SetAttribute (gST->ConOut, INFO_TEXT);
        PrintStringAt (
          gScreenDimensions.LeftColumn + gPromptBlockWidth + gOptionBlockWidth,
          gScreenDimensions.BottomRow - 1,
          NvUpdateMessage
          );
      }
    } else {
      gST->ConOut->SetAttribute (gST->ConOut, PcdGet8 (PcdBrowserFieldTextHighlightColor));
      for (Index = 0; Index < (GetStringWidth (NvUpdateMessage) - 2) / 2; Index++) {
        PrintAt (
          (gScreenDimensions.LeftColumn + gPromptBlockWidth + gOptionBlockWidth + Index),
          gScreenDimensions.BottomRow - 1,
          L"  "
          );
      }
    }
    break;

  case REFRESH_STATUS_BAR:
    if (mInputError) {
      UpdateStatusBar (Selection, INPUT_ERROR, Flags, TRUE);
    }

    switch (gBrowserSettingScope) {
    case SystemLevel:
      //
      // Check the maintain list to see whether there is any change.
      //
      Link = GetFirstNode (&gBrowserFormSetList);
      while (!IsNull (&gBrowserFormSetList, Link)) {
        LocalFormSet = FORM_BROWSER_FORMSET_FROM_LINK (Link);
        if (IsNvUpdateRequired(LocalFormSet)) {
          UpdateStatusBar (NULL, NV_UPDATE_REQUIRED, Flags, TRUE);
          break;
        }
        Link = GetNextNode (&gBrowserFormSetList, Link);
      }
      break;
    case FormSetLevel:
    case FormLevel:
      UpdateStatusBar (Selection, NV_UPDATE_REQUIRED, Flags, TRUE);
    default:
      break;
    }

    break;

  default:
    break;
  }

  FreePool (InputErrorMessage);
  FreePool (NvUpdateMessage);
  return ;
}


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

  @param  Statement              The FORM_BROWSER_STATEMENT structure passed in.
  @param  Handle                 The handle in the HII database being used

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

**/
UINT16
GetWidth (
  IN FORM_BROWSER_STATEMENT        *Statement,
  IN EFI_HII_HANDLE                 Handle
  )
{
  CHAR16  *String;
  UINTN   Size;
  UINT16  Width;

  Size = 0;

  //
  // See if the second text parameter is really NULL
  //
  if ((Statement->Operand == EFI_IFR_TEXT_OP) && (Statement->TextTwo != 0)) {
    String = GetToken (Statement->TextTwo, Handle);
    Size   = StrLen (String);
    FreePool (String);
  }

  if ((Statement->Operand == EFI_IFR_SUBTITLE_OP) ||
      (Statement->Operand == EFI_IFR_REF_OP) ||
      (Statement->Operand == EFI_IFR_PASSWORD_OP) ||
      (Statement->Operand == EFI_IFR_ACTION_OP) ||
      (Statement->Operand == EFI_IFR_RESET_BUTTON_OP) ||
      //
      // Allow a wide display if text op-code and no secondary text op-code
      //
      ((Statement->Operand == EFI_IFR_TEXT_OP) && (Size == 0))
      ) {
    Width = (UINT16) (gPromptBlockWidth + gOptionBlockWidth);
  } else {
    Width = (UINT16) gPromptBlockWidth;
  }

  if (Statement->InSubtitle) {
    Width -= SUBTITLE_INDENT;
  }

  return (UINT16) (Width - LEFT_SKIPPED_COLUMNS);
}

/**
  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 (((UINTN) (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;
}


/**
  Update display lines for a Menu Option.

  @param  Selection              The user's selection.
  @param  MenuOption             The MenuOption to be checked.

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

  Row           = 0;
  OptionString  = NULL;
  Width         = (UINT16) gOptionBlockWidth;
  OriginalRow   = 0;
  GlyphWidth    = 1;
  
  ProcessOptions (Selection, MenuOption, FALSE, &OptionString);
  if (OptionString == NULL) {
    return;
  }

  for (Index = 0; GetLineByWidth (OptionString, Width, &GlyphWidth, &Index, &OutputString) != 0x0000;) {
    //
    // If there is more string to process print on the next row and increment the Skip value
    //
    if (StrLen (&OptionString[Index]) != 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 - OriginalRow) >= MenuOption->Skip) {
        MenuOption->Skip++;
      }
    }

    FreePool (OutputString);
  }

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


/**
  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->Operand == EFI_IFR_SUBTITLE_OP) ||
      MenuOption->GrayOut || MenuOption->ReadOnly) {
    return FALSE;
  } else {
    return TRUE;
  }
}


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


/**
  Move to next selectable statement.

  This is an internal function.

  @param  Selection              Menu selection.
  @param  GoUp                   The navigation direction. TRUE: up, FALSE: down.
  @param  CurrentPosition        Current position.
  @param  GapToTop               Gap position to top or bottom.

  @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 l
                   last menu showing at current form.

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

  Distance      = 0;
  Pos           = *CurrentPosition;
  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 (Selection, NextMenuOption);
    }
    
    if (GoUp && (PreMenuOption != NextMenuOption)) {
      //
      // In this case, still can't find the selectable menu,
      // return the last one in the showing form.
      //
      if ((UINTN) Distance + NextMenuOption->Skip > GapToTop) {
        NextMenuOption = PreMenuOption;
        break;
      }

      //
      // Current Position doesn't need to be caculated when go up.
      // Caculate distanct at first when go up
      //      
      Distance += NextMenuOption->Skip;
    }

    if (IsSelectable (NextMenuOption)) {
      break;
    }

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

    if (!GoUp) {
      //
      // In this case, still can't find the selectable menu,
      // return the last one in the showing form.
      //
      if ((UINTN) Distance + NextMenuOption->Skip > GapToTop) {
        NextMenuOption = PreMenuOption;
        break;
      }

      Distance += NextMenuOption->Skip;
    }

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

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


/**
  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->Operand == EFI_IFR_DATE_OP) ||
      (MenuOption->ThisTag->Operand == 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;
}

/**
  Find HII Handle in the HII database associated with given Device Path.

  If DevicePath is NULL, then ASSERT.

  @param  DevicePath             Device Path associated with the HII package list
                                 handle.

  @retval Handle                 HII package list Handle associated with the Device
                                        Path.
  @retval NULL                   Hii Package list handle is not found.

**/
EFI_HII_HANDLE
EFIAPI
DevicePathToHiiHandle (
  IN EFI_DEVICE_PATH_PROTOCOL   *DevicePath
  )
{
  EFI_STATUS                  Status;
  EFI_DEVICE_PATH_PROTOCOL    *TmpDevicePath;
  UINTN                       BufferSize;
  UINTN                       HandleCount;
  UINTN                       Index;
  EFI_HANDLE                  Handle;
  EFI_HANDLE                  DriverHandle;
  EFI_HII_HANDLE              *HiiHandles;
  EFI_HII_HANDLE              HiiHandle;

  ASSERT (DevicePath != NULL);

  TmpDevicePath = DevicePath;
  //
  // Locate Device Path Protocol handle buffer
  //
  Status = gBS->LocateDevicePath (
                  &gEfiDevicePathProtocolGuid,
                  &TmpDevicePath,
                  &DriverHandle
                  );
  if (EFI_ERROR (Status) || !IsDevicePathEnd (TmpDevicePath)) {
    return NULL;
  }

  //
  // Retrieve all HII Handles from HII database
  //
  BufferSize = 0x1000;
  HiiHandles = AllocatePool (BufferSize);
  ASSERT (HiiHandles != NULL);
  Status = mHiiDatabase->ListPackageLists (
                           mHiiDatabase,
                           EFI_HII_PACKAGE_TYPE_ALL,
                           NULL,
                           &BufferSize,
                           HiiHandles
                           );
  if (Status == EFI_BUFFER_TOO_SMALL) {
    FreePool (HiiHandles);
    HiiHandles = AllocatePool (BufferSize);
    ASSERT (HiiHandles != NULL);

    Status = mHiiDatabase->ListPackageLists (
                             mHiiDatabase,
                             EFI_HII_PACKAGE_TYPE_ALL,
                             NULL,
                             &BufferSize,
                             HiiHandles
                             );
  }

  if (EFI_ERROR (Status)) {
    FreePool (HiiHandles);
    return NULL;
  }

  //
  // Search Hii Handle by Driver Handle
  //
  HiiHandle = NULL;
  HandleCount = BufferSize / sizeof (EFI_HII_HANDLE);
  for (Index = 0; Index < HandleCount; Index++) {
    Status = mHiiDatabase->GetPackageListHandle (
                             mHiiDatabase,
                             HiiHandles[Index],
                             &Handle
                             );
    if (!EFI_ERROR (Status) && (Handle == DriverHandle)) {
      HiiHandle = HiiHandles[Index];
      break;
    }
  }

  FreePool (HiiHandles);
  return HiiHandle;
}

/**
  Find HII Handle in the HII database associated with given form set guid.

  If FormSetGuid is NULL, then ASSERT.

  @param  ComparingGuid          FormSet Guid associated with the HII package list
                                 handle.

  @retval Handle                 HII package list Handle associated with the Device
                                        Path.
  @retval NULL                   Hii Package list handle is not found.

**/
EFI_HII_HANDLE
FormSetGuidToHiiHandle (
  EFI_GUID     *ComparingGuid
  )
{
  EFI_HII_HANDLE               *HiiHandles;
  UINTN                        Index;
  EFI_HII_PACKAGE_LIST_HEADER  *HiiPackageList;
  UINTN                        BufferSize;
  UINT32                       Offset;
  UINT32                       Offset2;
  UINT32                       PackageListLength;
  EFI_HII_PACKAGE_HEADER       PackageHeader;
  UINT8                        *Package;
  UINT8                        *OpCodeData;
  EFI_STATUS                   Status;
  EFI_HII_HANDLE               HiiHandle;

  ASSERT (ComparingGuid != NULL);

  HiiHandle  = NULL;
  //
  // Get all the Hii handles
  //
  HiiHandles = HiiGetHiiHandles (NULL);
  ASSERT (HiiHandles != NULL);

  //
  // Search for formset of each class type
  //
  for (Index = 0; HiiHandles[Index] != NULL; Index++) {
    BufferSize = 0;
    HiiPackageList = NULL;
    Status = mHiiDatabase->ExportPackageLists (mHiiDatabase, HiiHandles[Index], &BufferSize, HiiPackageList);
    if (Status == EFI_BUFFER_TOO_SMALL) {
      HiiPackageList = AllocatePool (BufferSize);
      ASSERT (HiiPackageList != NULL);

      Status = mHiiDatabase->ExportPackageLists (mHiiDatabase, HiiHandles[Index], &BufferSize, HiiPackageList);
    }
    if (EFI_ERROR (Status) || HiiPackageList == NULL) {
      return NULL;
    }

    //
    // Get Form package from this HII package List
    //
    Offset = sizeof (EFI_HII_PACKAGE_LIST_HEADER);
    Offset2 = 0;
    CopyMem (&PackageListLength, &HiiPackageList->PackageLength, sizeof (UINT32)); 

    while (Offset < PackageListLength) {
      Package = ((UINT8 *) HiiPackageList) + Offset;
      CopyMem (&PackageHeader, Package, sizeof (EFI_HII_PACKAGE_HEADER));

      if (PackageHeader.Type == EFI_HII_PACKAGE_FORMS) {
        //
        // Search FormSet in this Form Package
        //
        Offset2 = sizeof (EFI_HII_PACKAGE_HEADER);
        while (Offset2 < PackageHeader.Length) {
          OpCodeData = Package + Offset2;

          if (((EFI_IFR_OP_HEADER *) OpCodeData)->OpCode == EFI_IFR_FORM_SET_OP) {
            //
            // Try to compare against formset GUID
            //
            if (CompareGuid (ComparingGuid, (EFI_GUID *)(OpCodeData + sizeof (EFI_IFR_OP_HEADER)))) {
              HiiHandle = HiiHandles[Index];
              break;
            }
          }

          Offset2 += ((EFI_IFR_OP_HEADER *) OpCodeData)->Length;
        }
      }
      if (HiiHandle != NULL) {
        break;
      }
      Offset += PackageHeader.Length;
    }
    
    FreePool (HiiPackageList);
  	if (HiiHandle != NULL) {
  		break;
  	}
  }

  FreePool (HiiHandles);

  return HiiHandle;
}

/**
  Process the goto op code, update the info in the selection structure.

  @param Statement    The statement belong to goto op code.
  @param Selection    The selection info.
  @param Repaint      Whether need to repaint the menu.
  @param NewLine      Whether need to create new line.

  @retval EFI_SUCCESS    The menu process successfully.
  @return Other value if the process failed.
**/
EFI_STATUS
ProcessGotoOpCode (
  IN OUT   FORM_BROWSER_STATEMENT      *Statement,
  IN OUT   UI_MENU_SELECTION           *Selection,
  OUT      BOOLEAN                     *Repaint,
  OUT      BOOLEAN                     *NewLine
  )
{
  CHAR16                          *StringPtr;
  EFI_DEVICE_PATH_PROTOCOL        *DevicePath;
  FORM_BROWSER_FORM               *RefForm;
  EFI_INPUT_KEY                   Key;
  EFI_STATUS                      Status;

  Status    = EFI_SUCCESS;
  StringPtr = NULL;

  //
  // Prepare the device path check, get the device path info first.
  //
  if (Statement->HiiValue.Value.ref.DevicePath != 0) {
    StringPtr = GetToken (Statement->HiiValue.Value.ref.DevicePath, Selection->FormSet->HiiHandle);
  }

  //
  // Check whether the device path string is a valid string.
  //
  if (Statement->HiiValue.Value.ref.DevicePath != 0 && StringPtr != NULL) {
    if (Selection->Form->ModalForm) {
      return Status;
    }

    //
    // Goto another Hii Package list
    //
    if (mPathFromText != NULL) {
      DevicePath = mPathFromText->ConvertTextToDevicePath(StringPtr);
      if (DevicePath != NULL) {
        Selection->Handle = DevicePathToHiiHandle (DevicePath);
        FreePool (DevicePath);
      }
      FreePool (StringPtr);
    } else {
      //
      // Not found the EFI_DEVICE_PATH_FROM_TEXT_PROTOCOL protocol.
      //
      do {
        CreateDialog (4, TRUE, 0, NULL, &Key, gEmptyString, gProtocolNotFound, gPressEnter, gEmptyString);
      } while (Key.UnicodeChar != CHAR_CARRIAGE_RETURN);
      if (Repaint != NULL) {
        *Repaint = TRUE;
      }
      FreePool (StringPtr);
      return Status;
    }

    Selection->Action = UI_ACTION_REFRESH_FORMSET;
    if (Selection->Handle == NULL) {
      //
      // If target Hii Handle not found, exit
      //
      Selection->Action = UI_ACTION_EXIT;
      Selection->Statement = NULL;
      return Status;
    }

    CopyMem (&Selection->FormSetGuid,&Statement->HiiValue.Value.ref.FormSetGuid, sizeof (EFI_GUID));
    Selection->FormId = Statement->HiiValue.Value.ref.FormId;
    Selection->QuestionId = Statement->HiiValue.Value.ref.QuestionId;
  } else if (!CompareGuid (&Statement->HiiValue.Value.ref.FormSetGuid, &gZeroGuid)) {
    if (Selection->Form->ModalForm) {
      return Status;
    }  
    //
    // Goto another Formset, check for uncommitted data
    //
    Selection->Action = UI_ACTION_REFRESH_FORMSET;
    
    Selection->Handle = FormSetGuidToHiiHandle(&Statement->HiiValue.Value.ref.FormSetGuid);
    if (Selection->Handle == NULL) {
      //
      // If target Hii Handle not found, exit
      //
      Selection->Action = UI_ACTION_EXIT;
      Selection->Statement = NULL;
      return Status;
    } 

    CopyMem (&Selection->FormSetGuid, &Statement->HiiValue.Value.ref.FormSetGuid, sizeof (EFI_GUID));
    Selection->FormId = Statement->HiiValue.Value.ref.FormId;
    Selection->QuestionId = Statement->HiiValue.Value.ref.QuestionId;
  } else if (Statement->HiiValue.Value.ref.FormId != 0) {
    //
    // Check whether target From is suppressed.
    //
    RefForm = IdToForm (Selection->FormSet, Statement->HiiValue.Value.ref.FormId);

    if ((RefForm != NULL) && (RefForm->SuppressExpression != NULL)) {
      if (EvaluateExpressionList(RefForm->SuppressExpression, TRUE, Selection->FormSet, RefForm) != ExpressFalse) {
        //
        // Form is suppressed. 
        //
        do {
          CreateDialog (4, TRUE, 0, NULL, &Key, gEmptyString, gFormSuppress, gPressEnter, gEmptyString);
        } while (Key.UnicodeChar != CHAR_CARRIAGE_RETURN);
        if (Repaint != NULL) {
          *Repaint = TRUE;
        }
        return Status;
      }
    }

    //
    // Goto another form inside this formset,
    //
    Selection->Action = UI_ACTION_REFRESH_FORM;

    Selection->FormId = Statement->HiiValue.Value.ref.FormId;
    Selection->QuestionId = Statement->HiiValue.Value.ref.QuestionId;
  } else if (Statement->HiiValue.Value.ref.QuestionId != 0) {
    //
    // Goto another Question
    //
    Selection->QuestionId = Statement->HiiValue.Value.ref.QuestionId;

    if ((Statement->QuestionFlags & EFI_IFR_FLAG_CALLBACK) != 0) {
      Selection->Action = UI_ACTION_REFRESH_FORM;
    } else {
      if (Repaint != NULL) {
        *Repaint = TRUE;
      }
      if (NewLine != NULL) {
        *NewLine = TRUE;
      }
    }
  } else {
    if ((Statement->QuestionFlags & EFI_IFR_FLAG_CALLBACK) != 0) {
      Selection->Action = UI_ACTION_REFRESH_FORM;
    }
  }

  return Status;
}

/**
  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  Selection              Menu selection.

  @retval EFI_SUCESSS            This function always return successfully for now.

**/
EFI_STATUS
UiDisplayMenu (
  IN OUT UI_MENU_SELECTION           *Selection
  )
{
  INTN                            SkipValue;
  INTN                            Difference;
  UINTN                           DistanceValue;
  UINTN                           Row;
  UINTN                           Col;
  UINTN                           Temp;
  UINTN                           Temp2;
  UINTN                           TopRow;
  UINTN                           BottomRow;
  UINTN                           OriginalRow;
  UINTN                           Index;
  UINT16                          Width;
  CHAR16                          *StringPtr;
  CHAR16                          *OptionString;
  CHAR16                          *OutputString;
  CHAR16                          *HelpString;
  CHAR16                          *HelpHeaderString;
  CHAR16                          *HelpBottomString;
  BOOLEAN                         NewLine;
  BOOLEAN                         Repaint;
  BOOLEAN                         SavedValue;
  BOOLEAN                         UpArrow;
  BOOLEAN                         DownArrow;
  BOOLEAN                         InitializedFlag;
  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_MENU_OPTION                  *PreviousMenuOption;
  UI_CONTROL_FLAG                 ControlFlag;
  EFI_SCREEN_DESCRIPTOR           LocalScreen;
  MENU_REFRESH_ENTRY              *MenuRefreshEntry;
  MENU_REFRESH_ENTRY              *MenuUpdateEntry;  
  UI_SCREEN_OPERATION             ScreenOperation;
  UINT8                           MinRefreshInterval;
  UINT16                          DefaultId;
  FORM_BROWSER_STATEMENT          *Statement;
  UI_MENU_LIST                    *CurrentMenu;
  UINTN                           ModalSkipColumn;
  BROWSER_HOT_KEY                 *HotKey;
  UINTN                           HelpPageIndex;
  UINTN                           HelpPageCount;
  UINTN                           RowCount;
  UINTN                           HelpLine;
  UINTN                           HelpHeaderLine;
  UINTN                           HelpBottomLine;
  BOOLEAN                         MultiHelpPage;
  UINT16                          GlyphWidth;
  UINT16                          EachLineWidth;
  UINT16                          HeaderLineWidth;
  UINT16                          BottomLineWidth;

  CopyMem (&LocalScreen, &gScreenDimensions, sizeof (EFI_SCREEN_DESCRIPTOR));

  Status              = EFI_SUCCESS;
  HelpString          = NULL;
  HelpHeaderString    = NULL;
  HelpBottomString    = NULL;
  OptionString        = NULL;
  ScreenOperation     = UiNoOperation;
  NewLine             = TRUE;
  MinRefreshInterval  = 0;
  DefaultId           = 0;
  HelpPageCount       = 0;
  HelpLine            = 0;
  RowCount            = 0;
  HelpBottomLine      = 0;
  HelpHeaderLine      = 0;
  HelpPageIndex       = 0;
  MultiHelpPage       = FALSE;
  EachLineWidth       = 0;
  HeaderLineWidth     = 0;
  BottomLineWidth     = 0;
  OutputString        = NULL;
  UpArrow             = FALSE;
  DownArrow           = FALSE;
  SkipValue           = 0;
  MenuRefreshEntry    = gMenuRefreshHead;

  NextMenuOption      = NULL;
  PreviousMenuOption  = NULL;
  SavedMenuOption     = NULL;
  HotKey              = NULL;
  ModalSkipColumn     = (LocalScreen.RightColumn - LocalScreen.LeftColumn) / 6;

  ZeroMem (&Key, sizeof (EFI_INPUT_KEY));

  if ((gClassOfVfr & FORMSET_CLASS_FRONT_PAGE) == FORMSET_CLASS_FRONT_PAGE){
    TopRow  = LocalScreen.TopRow + FRONT_PAGE_HEADER_HEIGHT + SCROLL_ARROW_HEIGHT;
    Row     = LocalScreen.TopRow + FRONT_PAGE_HEADER_HEIGHT + SCROLL_ARROW_HEIGHT;
  } else {
    TopRow  = LocalScreen.TopRow + NONE_FRONT_PAGE_HEADER_HEIGHT + SCROLL_ARROW_HEIGHT;
    Row     = LocalScreen.TopRow + NONE_FRONT_PAGE_HEADER_HEIGHT + SCROLL_ARROW_HEIGHT;
  }

  if (Selection->Form->ModalForm) {
    Col = LocalScreen.LeftColumn + LEFT_SKIPPED_COLUMNS + ModalSkipColumn;
  } else {
    Col = LocalScreen.LeftColumn + LEFT_SKIPPED_COLUMNS;
  }

  BottomRow = LocalScreen.BottomRow - STATUS_BAR_HEIGHT - gFooterHeight - SCROLL_ARROW_HEIGHT - 1;

  Selection->TopRow = TopRow;
  Selection->BottomRow = BottomRow;
  Selection->PromptCol = Col;
  Selection->OptionCol = gPromptBlockWidth + 1 + LocalScreen.LeftColumn;
  Selection->Statement = NULL;

  TopOfScreen = gMenuOption.ForwardLink;
  Repaint     = TRUE;
  MenuOption  = NULL;

  //
  // Find current Menu
  //
  CurrentMenu = UiFindMenuList (Selection->Handle, &Selection->FormSetGuid, Selection->FormId);
  if (CurrentMenu == NULL) {
    //
    // Current menu not found, add it to the menu tree
    //
    CurrentMenu = UiAddMenuList (Selection->CurrentMenu, Selection->Handle, &Selection->FormSetGuid, Selection->FormId);
  }
  ASSERT (CurrentMenu != NULL);
  Selection->CurrentMenu = CurrentMenu;

  if (Selection->QuestionId == 0) {
    //
    // Highlight not specified, fetch it from cached menu
    //
    Selection->QuestionId = CurrentMenu->QuestionId;
    Selection->Sequence   = CurrentMenu->Sequence;
  }

  //
  // Init option as the current user's selection
  //
  InitializedFlag = TRUE;
  NewPos = gMenuOption.ForwardLink;

  gST->ConOut->EnableCursor (gST->ConOut, FALSE);
  UpdateStatusBar (Selection, REFRESH_STATUS_BAR, (UINT8) 0, TRUE);

  ControlFlag = CfInitialization;
  Selection->Action = UI_ACTION_NONE;
  while (TRUE) {
    switch (ControlFlag) {
    case CfInitialization:
      if (IsListEmpty (&gMenuOption)) {
        ControlFlag = CfReadKey;
      } else {
        ControlFlag = CfCheckSelection;
      }
      break;

    case CfCheckSelection:
      if (Selection->Action != UI_ACTION_NONE) {
        ControlFlag = CfExit;
      } else {
        ControlFlag = CfRepaint;
      }
      break;

    case CfRepaint:
      ControlFlag = CfRefreshHighLight;

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

        Temp            = (UINTN) SkipValue;
        Temp2           = (UINTN) SkipValue;

        //
        // 1. Clear the screen.
        //
        if (Selection->Form->ModalForm) {
          ClearLines (
            LocalScreen.LeftColumn + ModalSkipColumn,
            LocalScreen.LeftColumn + ModalSkipColumn + gPromptBlockWidth + gOptionBlockWidth,
            TopRow - SCROLL_ARROW_HEIGHT,
            BottomRow + SCROLL_ARROW_HEIGHT,
            PcdGet8 (PcdBrowserFieldTextColor) | FIELD_BACKGROUND
            );   
        } else {
          ClearLines (
            LocalScreen.LeftColumn,
            LocalScreen.RightColumn,
            TopRow - SCROLL_ARROW_HEIGHT,
            BottomRow + SCROLL_ARROW_HEIGHT,
            PcdGet8 (PcdBrowserFieldTextColor) | FIELD_BACKGROUND
            );
        }
        UiFreeRefreshList ();
        MinRefreshInterval = 0;

        //
        // 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 (Selection->Form->ModalForm) {
            MenuOption->OptCol  = gPromptBlockWidth + 1 + LocalScreen.LeftColumn + ModalSkipColumn;
          } else {
            MenuOption->OptCol  = gPromptBlockWidth + 1 + LocalScreen.LeftColumn;
          }

          Statement = MenuOption->ThisTag;
          if (Statement->InSubtitle) {
            MenuOption->Col += SUBTITLE_INDENT;
          }

          if (MenuOption->GrayOut) {
            gST->ConOut->SetAttribute (gST->ConOut, FIELD_TEXT_GRAYED | FIELD_BACKGROUND);
          } else {
            if (Statement->Operand == EFI_IFR_SUBTITLE_OP) {
              gST->ConOut->SetAttribute (gST->ConOut, PcdGet8 (PcdBrowserSubtitleTextColor) | FIELD_BACKGROUND);
            }
          }

          Width       = GetWidth (Statement, MenuOption->Handle);
          OriginalRow = Row;
          GlyphWidth  = 1;

          if (Statement->Operand == EFI_IFR_REF_OP && MenuOption->Col >= 2) {
            //
            // Print Arrow for Goto button.
            //
            PrintAt (
              MenuOption->Col - 2,
              Row,
              L"%c",
              GEOMETRICSHAPE_RIGHT_TRIANGLE
              );
          }

          //
          // 2.1. Paint the description.
          //
          for (Index = 0; GetLineByWidth (MenuOption->Description, Width, &GlyphWidth, &Index, &OutputString) != 0x0000;) {
            //
            // Temp means need to skip how many lines from the start.
            //
            if ((Temp == 0) && (Row <= BottomRow)) {
              PrintStringAt (MenuOption->Col, Row, OutputString);
            }
            //
            // 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--;
            }
          }

          Temp  = 0;
          Row   = OriginalRow;

          //
          // 2.2. Paint the option string.
          //
          Status = ProcessOptions (Selection, MenuOption, FALSE, &OptionString);
          if (EFI_ERROR (Status)) {
            //
            // Repaint to clear possible error prompt pop-up
            //
            Repaint = TRUE;
            NewLine = TRUE;
            ControlFlag = CfRepaint;
            break;
          }

          if (OptionString != NULL) {
            if (Statement->Operand == EFI_IFR_DATE_OP || Statement->Operand == EFI_IFR_TIME_OP) {
              ProcessStringForDateTime(MenuOption, OptionString, TRUE);
            }

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

            for (Index = 0; GetLineByWidth (OptionString, Width, &GlyphWidth, &Index, &OutputString) != 0x0000;) {
              if ((Temp2 == 0) && (Row <= BottomRow)) {
                PrintStringAt (MenuOption->OptCol, Row, OutputString);
              }
              //
              // 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 - OriginalRow) >= MenuOption->Skip) {
                    MenuOption->Skip++;
                  }
                }
              }

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

            Temp2 = 0;
            Row   = OriginalRow;

            FreePool (OptionString);
          }

          //
          // 2.4 Special process for Test opcode with test two.
          //
          if (!CompareGuid (&Statement->RefreshGuid, &gZeroGuid)) {
            if (gMenuEventGuidRefreshHead == NULL) {
              MenuUpdateEntry = AllocateZeroPool (sizeof (MENU_REFRESH_ENTRY));
              gMenuEventGuidRefreshHead = MenuUpdateEntry;
            } else {
              MenuUpdateEntry = gMenuEventGuidRefreshHead;
              while (MenuUpdateEntry->Next != NULL) {
                MenuUpdateEntry = MenuUpdateEntry->Next; 
              }
              MenuUpdateEntry->Next = AllocateZeroPool (sizeof (MENU_REFRESH_ENTRY));
              MenuUpdateEntry = MenuUpdateEntry->Next; 
            }
            ASSERT (MenuUpdateEntry != NULL);
            Status = gBS->CreateEventEx (EVT_NOTIFY_SIGNAL, TPL_NOTIFY, RefreshQuestionNotify, MenuUpdateEntry, &Statement->RefreshGuid, &MenuUpdateEntry->Event);
            ASSERT (!EFI_ERROR (Status));
            MenuUpdateEntry->MenuOption        = MenuOption;
            MenuUpdateEntry->Selection         = Selection;
            MenuUpdateEntry->CurrentColumn     = MenuOption->OptCol;
            MenuUpdateEntry->CurrentRow        = MenuOption->Row;
            if (MenuOption->GrayOut) {
              MenuUpdateEntry->CurrentAttribute = FIELD_TEXT_GRAYED | FIELD_BACKGROUND;
            } else {
              MenuUpdateEntry->CurrentAttribute = PcdGet8 (PcdBrowserFieldTextColor) | FIELD_BACKGROUND;
            }
          }
          
          //
          // If Question request refresh, register the op-code
          //
          if (Statement->RefreshInterval != 0) {
            //
            // Menu will be refreshed at minimal interval of all Questions
            // which have refresh request
            //
            if (MinRefreshInterval == 0 || Statement->RefreshInterval < MinRefreshInterval) {
              MinRefreshInterval = Statement->RefreshInterval;
            }
            
            if (gMenuRefreshHead == NULL) {
              MenuRefreshEntry = AllocateZeroPool (sizeof (MENU_REFRESH_ENTRY));
              gMenuRefreshHead = MenuRefreshEntry;
            } else {
              MenuRefreshEntry = gMenuRefreshHead;
              while (MenuRefreshEntry->Next != NULL) {
                MenuRefreshEntry = MenuRefreshEntry->Next; 
              }
              MenuRefreshEntry->Next = AllocateZeroPool (sizeof (MENU_REFRESH_ENTRY));
              MenuRefreshEntry = MenuRefreshEntry->Next;
            }
            ASSERT (MenuRefreshEntry != NULL);            
            MenuRefreshEntry->MenuOption        = MenuOption;
            MenuRefreshEntry->Selection         = Selection;
            MenuRefreshEntry->CurrentColumn     = MenuOption->OptCol;
            MenuRefreshEntry->CurrentRow        = MenuOption->Row;
            if (MenuOption->GrayOut) {
              MenuRefreshEntry->CurrentAttribute = FIELD_TEXT_GRAYED | FIELD_BACKGROUND;
            } else {               
              MenuRefreshEntry->CurrentAttribute = PcdGet8 (PcdBrowserFieldTextColor) | FIELD_BACKGROUND;
            }
          }
          
          //
          // If this is a text op with secondary text information
          //
          if ((Statement->Operand == EFI_IFR_TEXT_OP) && (Statement->TextTwo != 0)) {
            StringPtr   = GetToken (Statement->TextTwo, MenuOption->Handle);

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

            for (Index = 0; GetLineByWidth (StringPtr, Width, &GlyphWidth, &Index, &OutputString) != 0x0000;) {
              if ((Temp == 0) && (Row <= BottomRow)) {
                PrintStringAt (MenuOption->OptCol, Row, OutputString);
              }
              //
              // If there is more string to process print on the next row and increment the Skip value
              //
              if (StrLen (&StringPtr[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 - OriginalRow) >= MenuOption->Skip) {
                    MenuOption->Skip++;
                  }
                }
              }

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

            Row = OriginalRow;
            FreePool (StringPtr);
          }
          gST->ConOut->SetAttribute (gST->ConOut, PcdGet8 (PcdBrowserFieldTextColor) | FIELD_BACKGROUND);

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

        if (!ValueIsScroll (TRUE, TopOfScreen)) {
          UpArrow = TRUE;
        }

        if (UpArrow) {
          gST->ConOut->SetAttribute (gST->ConOut, ARROW_TEXT | ARROW_BACKGROUND);
          PrintAt (
            LocalScreen.LeftColumn + gPromptBlockWidth + gOptionBlockWidth + 1,
            TopRow - SCROLL_ARROW_HEIGHT,
            L"%c",
            ARROW_UP
            );
          gST->ConOut->SetAttribute (gST->ConOut, PcdGet8 (PcdBrowserFieldTextColor) | FIELD_BACKGROUND);
        }

        if (DownArrow) {
          gST->ConOut->SetAttribute (gST->ConOut, ARROW_TEXT | ARROW_BACKGROUND);
          PrintAt (
            LocalScreen.LeftColumn + gPromptBlockWidth + gOptionBlockWidth + 1,
            BottomRow + SCROLL_ARROW_HEIGHT,
            L"%c",
            ARROW_DOWN
            );
          gST->ConOut->SetAttribute (gST->ConOut, PcdGet8 (PcdBrowserFieldTextColor) | FIELD_BACKGROUND);
        }

        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;
      if (TopOfScreen == &MenuOption->Link) {
        Temp = SkipValue;
      } else {
        Temp = 0;
      }
      if (NewPos == TopOfScreen) {
        Temp2 = SkipValue;
      } else {
        Temp2 = 0;
      }
      if (InitializedFlag) {
        InitializedFlag = FALSE;
        MoveToNextStatement (Selection, FALSE, &NewPos, BottomRow - TopRow);
      }

      //
      // Repaint flag is normally reset when finish processing CfUpdateHelpString. Temporarily
      // reset Repaint flag because we may break halfway and skip CfUpdateHelpString processing.
      //
      SavedValue  = Repaint;
      Repaint     = FALSE;

      if (Selection->QuestionId != 0) {
        NewPos = gMenuOption.ForwardLink;
        SavedMenuOption = MENU_OPTION_FROM_LINK (NewPos);

        while ((SavedMenuOption->ThisTag->QuestionId != Selection->QuestionId ||
                SavedMenuOption->Sequence != Selection->Sequence) &&
                NewPos->ForwardLink != &gMenuOption) {
          NewPos     = NewPos->ForwardLink;
          SavedMenuOption = MENU_OPTION_FROM_LINK (NewPos);
        }
        if (SavedMenuOption->ThisTag->QuestionId == Selection->QuestionId) {
          //
          // Target Question found, find its MenuOption
          //
          Link = TopOfScreen;

          for (Index = TopRow; Index <= BottomRow && Link != NewPos;) {
            SavedMenuOption = MENU_OPTION_FROM_LINK (Link);
            Index += SavedMenuOption->Skip;
            if (Link == TopOfScreen) {
              Index -= SkipValue;
            }
            Link = Link->ForwardLink;
          }
          if (NewPos == Link) {
            SavedMenuOption = MENU_OPTION_FROM_LINK (Link);
          }

          //
          // Not find the selected menu in current show page.
          // Have two case to enter this if:
          // 1. Not find the menu at current page.
          // 2. Find the menu in current page, but the menu shows at the bottom and not all info shows.
          //    For case 2, has an exception: The menu can show more than one pages and now only this menu shows.
          //
          // Base on the selected menu will show at the bottom of the page,
          // select the menu which will show at the top of the page.
          //
          if (Link != NewPos || Index > BottomRow || 
              (Link == NewPos && (SavedMenuOption->Row + SavedMenuOption->Skip - 1 > BottomRow) && (Link != TopOfScreen))) {
            //
            // Find the MenuOption which has the skip value for Date/Time opcode. 
            //
            AdjustDateAndTimePosition(FALSE, &NewPos);
            //
            // NewPos is not in the current page, simply scroll page so that NewPos is in the end of the page
            //
            SavedMenuOption = MENU_OPTION_FROM_LINK (NewPos);
            //
            // SavedMenuOption->Row == 0 means the menu not show yet.
            //
            if (SavedMenuOption->Row == 0) {
              UpdateOptionSkipLines (Selection, SavedMenuOption);
            }

            //
            // Base on the selected menu will show at the bottome of next page, 
            // select the menu show at the top of the next page. 
            //
            Link    = NewPos;
            for (Index = TopRow + SavedMenuOption->Skip; Index <= BottomRow + 1; ) {            
              Link = Link->BackLink;
              SavedMenuOption = MENU_OPTION_FROM_LINK (Link);
              if (SavedMenuOption->Row == 0) {
                UpdateOptionSkipLines (Selection, SavedMenuOption);
              }
              Index += SavedMenuOption->Skip;
            }

            //
            // Found the menu which will show at the top of the page.
            //
            if (Link == NewPos) {
              //
              // The menu can show more than one pages, just show the menu at the top of the page.
              //
              SkipValue    = 0;
              TopOfScreen  = Link;
            } else {
              //
              // Check whether need to skip some line for menu shows at the top of the page.
              //
              SkipValue = Index - BottomRow - 1;
              if (SkipValue > 0 && SkipValue < (INTN) SavedMenuOption->Skip) {
                TopOfScreen     = Link;
              } else {
                SkipValue       = 0;
                TopOfScreen     = Link->ForwardLink;
              }
            }

            Repaint = TRUE;
            NewLine = TRUE;
            ControlFlag = CfRepaint;
            break;
          }
        } else {
          //
          // Target Question not found, highlight the default menu option
          //
          NewPos = TopOfScreen;
        }

        Selection->QuestionId = 0;
      }

      if (NewPos != NULL && (MenuOption == NULL || NewPos != &MenuOption->Link)) {
        if (MenuOption != NULL) {
          //
          // Remove highlight on last Menu Option
          //
          gST->ConOut->SetCursorPosition (gST->ConOut, MenuOption->Col, MenuOption->Row);
          ProcessOptions (Selection, MenuOption, FALSE, &OptionString);
          gST->ConOut->SetAttribute (gST->ConOut, PcdGet8 (PcdBrowserFieldTextColor) | FIELD_BACKGROUND);
          if (OptionString != NULL) {
            if ((MenuOption->ThisTag->Operand == EFI_IFR_DATE_OP) ||
                (MenuOption->ThisTag->Operand == EFI_IFR_TIME_OP)
               ) {
              ProcessStringForDateTime(MenuOption, OptionString, FALSE);
            }

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

            for (Index = 0; GetLineByWidth (OptionString, Width, &GlyphWidth, &Index, &OutputString) != 0x0000;) {
              if ((Temp == 0) && (MenuOption->Row >= TopRow) && (MenuOption->Row <= BottomRow)) {
                PrintStringAt (MenuOption->OptCol, MenuOption->Row, OutputString);
              }
              //
              // If there is more string to process print on the next row and increment the Skip value
              //
              if (StrLen (&OptionString[Index]) != 0) {
                if (Temp == 0) {
                  MenuOption->Row++;
                }
              }

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

            MenuOption->Row = OriginalRow;

            FreePool (OptionString);
          } else {
            if (NewLine) {
              if (MenuOption->GrayOut) {
                gST->ConOut->SetAttribute (gST->ConOut, FIELD_TEXT_GRAYED | FIELD_BACKGROUND);
              } else if (MenuOption->ThisTag->Operand == EFI_IFR_SUBTITLE_OP) {
                gST->ConOut->SetAttribute (gST->ConOut, PcdGet8 (PcdBrowserSubtitleTextColor) | FIELD_BACKGROUND);
              }

              OriginalRow = MenuOption->Row;
              Width       = GetWidth (MenuOption->ThisTag, MenuOption->Handle);
              GlyphWidth  = 1;

              for (Index = 0; GetLineByWidth (MenuOption->Description, Width, &GlyphWidth, &Index, &OutputString) != 0x0000;) {
                if ((Temp == 0) && (MenuOption->Row >= TopRow) && (MenuOption->Row <= BottomRow)) {
                  PrintStringAt (MenuOption->Col, MenuOption->Row, OutputString);
                }
                //
                // 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) {
                    MenuOption->Row++;
                  }
                }

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

              MenuOption->Row = OriginalRow;
              gST->ConOut->SetAttribute (gST->ConOut, PcdGet8 (PcdBrowserFieldTextColor) | FIELD_BACKGROUND);
            }
          }
        }

        //
        // This is the current selected statement
        //
        MenuOption = MENU_OPTION_FROM_LINK (NewPos);
        Statement = MenuOption->ThisTag;
        Selection->Statement = Statement;
        if (!IsSelectable (MenuOption)) {
          Repaint = SavedValue;
          UpdateKeyHelp (Selection, MenuOption, FALSE);
          break;
        }

        //
        // Record highlight for current menu
        //
        CurrentMenu->QuestionId = Statement->QuestionId;
        CurrentMenu->Sequence   = MenuOption->Sequence;

        //
        // Set reverse attribute
        //
        gST->ConOut->SetAttribute (gST->ConOut, PcdGet8 (PcdBrowserFieldTextHighlightColor) | PcdGet8 (PcdBrowserFieldBackgroundHighlightColor));
        gST->ConOut->SetCursorPosition (gST->ConOut, MenuOption->Col, MenuOption->Row);

        //
        // Assuming that we have a refresh linked-list created, lets annotate the
        // appropriate entry that we are highlighting with its new attribute.  Just prior to this
        // lets reset all of the entries' attribute so we do not get multiple highlights in he refresh
        //
        if (gMenuRefreshHead != NULL) {
          for (MenuRefreshEntry = gMenuRefreshHead; MenuRefreshEntry != NULL; MenuRefreshEntry = MenuRefreshEntry->Next) {
            if (MenuRefreshEntry->MenuOption->GrayOut) {
              MenuRefreshEntry->CurrentAttribute = FIELD_TEXT_GRAYED | FIELD_BACKGROUND;
            } else {               
              MenuRefreshEntry->CurrentAttribute = PcdGet8 (PcdBrowserFieldTextColor) | FIELD_BACKGROUND;
            }
            if (MenuRefreshEntry->MenuOption == MenuOption) {
              MenuRefreshEntry->CurrentAttribute = PcdGet8 (PcdBrowserFieldTextHighlightColor) | PcdGet8 (PcdBrowserFieldBackgroundHighlightColor);
            }
          }
        }

        ProcessOptions (Selection, MenuOption, FALSE, &OptionString);
        if (OptionString != NULL) {
          if (Statement->Operand == EFI_IFR_DATE_OP || Statement->Operand == EFI_IFR_TIME_OP) {
            ProcessStringForDateTime(MenuOption, OptionString, FALSE);
          }
          Width               = (UINT16) gOptionBlockWidth;

          OriginalRow         = MenuOption->Row;
          GlyphWidth          = 1;

          for (Index = 0; GetLineByWidth (OptionString, Width, &GlyphWidth, &Index, &OutputString) != 0x0000;) {
            if ((Temp2 == 0) && (MenuOption->Row >= TopRow) && (MenuOption->Row <= BottomRow) ) {
              PrintStringAt (MenuOption->OptCol, MenuOption->Row, OutputString);
            }
            //
            // 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) {
              MenuOption->Row++;
              }
            }

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

          MenuOption->Row = OriginalRow;

          FreePool (OptionString);
        } else {
          if (NewLine) {
            OriginalRow = MenuOption->Row;

            Width       = GetWidth (Statement, MenuOption->Handle);
            GlyphWidth          = 1;

            for (Index = 0; GetLineByWidth (MenuOption->Description, Width, &GlyphWidth, &Index, &OutputString) != 0x0000;) {
              if ((Temp2 == 0) && (MenuOption->Row >= TopRow) && (MenuOption->Row <= BottomRow) ) {
                PrintStringAt (MenuOption->Col, MenuOption->Row, OutputString);
              }
              //
              // If there is more string to process print on the next row and increment the Skip value
              //
              if (StrLen (&MenuOption->Description[Index]) != 0) {
                if (Temp2 == 0) {
                  MenuOption->Row++;
                }
              }

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

            MenuOption->Row = OriginalRow;

          }
        }

        UpdateKeyHelp (Selection, MenuOption, FALSE);

        //
        // Clear reverse attribute
        //
        gST->ConOut->SetAttribute (gST->ConOut, PcdGet8 (PcdBrowserFieldTextColor) | FIELD_BACKGROUND);
      }
      //
      // Repaint flag will be used when process CfUpdateHelpString, so restore its value
      // if we didn't break halfway when process CfRefreshHighLight.
      //
      Repaint = SavedValue;
      break;

    case CfUpdateHelpString:
      ControlFlag = CfPrepareToReadKey;
      if (Selection->Form->ModalForm) {
        break;
      }

      if (Repaint || NewLine) {
        //
        // Don't print anything if it is a NULL help token
        //
        ASSERT(MenuOption != NULL);
        if (MenuOption->ThisTag->Help == 0 || !IsSelectable (MenuOption)) {
          StringPtr = L"\0";
        } else {
          StringPtr = GetToken (MenuOption->ThisTag->Help, MenuOption->Handle);
        }

        RowCount      = BottomRow - TopRow;
        HelpPageIndex = 0;
        //
        // 1.Calculate how many line the help string need to print.
        //
        if (HelpString != NULL) {
          FreePool (HelpString);
        }
        HelpLine = ProcessHelpString (StringPtr, &HelpString, &EachLineWidth, RowCount);
        if (HelpLine > RowCount) {
          MultiHelpPage   = TRUE;
          StringPtr       = GetToken (STRING_TOKEN(ADJUST_HELP_PAGE_UP), gHiiHandle);
          if (HelpHeaderString != NULL) {
            FreePool (HelpHeaderString);
          }
          HelpHeaderLine  = ProcessHelpString (StringPtr, &HelpHeaderString, &HeaderLineWidth, RowCount);
          StringPtr       = GetToken (STRING_TOKEN(ADJUST_HELP_PAGE_DOWN), gHiiHandle);
          if (HelpBottomString != NULL) {
            FreePool (HelpBottomString);
          }
          HelpBottomLine  = ProcessHelpString (StringPtr, &HelpBottomString, &BottomLineWidth, RowCount);
          //
          // Calculate the help page count.
          //
          if (HelpLine > 2 * RowCount - 2) {
            HelpPageCount = (HelpLine - RowCount + 1) / (RowCount - 2) + 1;
            if ((HelpLine - RowCount + 1) % (RowCount - 2) > 1) {
              HelpPageCount += 1;
            }
          } else {
            HelpPageCount = 2;
          }
        } else {
          MultiHelpPage = FALSE;
        }
      }

      //
      // Clean the help field first.
      //
      ClearLines (
        LocalScreen.RightColumn - gHelpBlockWidth,
        LocalScreen.RightColumn,
        TopRow,
        BottomRow,
        PcdGet8 (PcdBrowserFieldTextColor) | FIELD_BACKGROUND
        );

      //
      // 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, INFO_TEXT | FIELD_BACKGROUND);
        for (Index = 0; Index < HelpHeaderLine; Index++) {
          ASSERT (HelpHeaderLine == 1);
          ASSERT (GetStringWidth (HelpHeaderString) / 2 < (UINTN) (gHelpBlockWidth - 1));
          PrintStringAt (
            LocalScreen.RightColumn - GetStringWidth (HelpHeaderString) / 2 - 1,
            Index + TopRow,
            &HelpHeaderString[Index * HeaderLineWidth]
            );
        }
      }

      gST->ConOut->SetAttribute (gST->ConOut, HELP_TEXT | FIELD_BACKGROUND);
      //
      // Print the help string info.
      //
      if (!MultiHelpPage) {
        for (Index = 0; Index < HelpLine; Index++) {
          PrintStringAt (
            LocalScreen.RightColumn - gHelpBlockWidth,
            Index + TopRow,
            &HelpString[Index * EachLineWidth]
            );
        }
        gST->ConOut->SetCursorPosition(gST->ConOut, LocalScreen.RightColumn-1, BottomRow);
      } else  {
        if (HelpPageIndex == 0) {
          for (Index = 0; Index < RowCount - HelpBottomLine; Index++) {
            PrintStringAt (
              LocalScreen.RightColumn - gHelpBlockWidth,
              Index + TopRow,
              &HelpString[Index * EachLineWidth]
              );
          }
        } else {
          for (Index = 0; (Index < RowCount - HelpBottomLine - HelpHeaderLine) && 
              (Index + HelpPageIndex * (RowCount - 2) + 1 < HelpLine); Index++) {
            PrintStringAt (
              LocalScreen.RightColumn - gHelpBlockWidth,
              Index + TopRow + HelpHeaderLine,
              &HelpString[(Index + HelpPageIndex * (RowCount - 2) + 1)* EachLineWidth]
              );
          }
          if (HelpPageIndex == HelpPageCount - 1) {
            gST->ConOut->SetCursorPosition(gST->ConOut, LocalScreen.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, INFO_TEXT | FIELD_BACKGROUND);
        for (Index = 0; Index < HelpBottomLine; Index++) {
          ASSERT (HelpBottomLine == 1);
          ASSERT (GetStringWidth (HelpBottomString) / 2 < (UINTN) (gHelpBlockWidth - 1)); 
          PrintStringAt (
            LocalScreen.RightColumn - GetStringWidth (HelpBottomString) / 2 - 1,
            Index + BottomRow - HelpBottomLine,
            &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)) {
          break;
        }

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

        Status = UiWaitForSingleEvent (gST->ConIn->WaitForKey, 0, MinRefreshInterval);
        ASSERT_EFI_ERROR (Status);

        if (Selection->Action == UI_ACTION_REFRESH_FORMSET) {
          //
          // IFR is updated in Callback of refresh opcode, re-parse it
          //
          ControlFlag = CfCheckSelection;
          Selection->Statement = NULL;
          break;
        }
      }

      if (ControlFlag == CfCheckSelection) {
        break;
      }

      switch (Key.UnicodeChar) {
      case CHAR_CARRIAGE_RETURN:
        if(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.
        //
        if(IsListEmpty (&gMenuOption) || MenuOption->GrayOut || MenuOption->ReadOnly) {
          ControlFlag = CfReadKey;
          break;
        }

        ASSERT(MenuOption != NULL);
        Statement = MenuOption->ThisTag;
        if ((Statement->Operand == EFI_IFR_DATE_OP)
          || (Statement->Operand == EFI_IFR_TIME_OP)
          || ((Statement->Operand == EFI_IFR_NUMERIC_OP) && (Statement->Step != 0))
        ){
          if (Key.UnicodeChar == '+') {
            gDirection = SCAN_RIGHT;
          } else {
            gDirection = SCAN_LEFT;
          }
          Status = ProcessOptions (Selection, MenuOption, TRUE, &OptionString);
          if (EFI_ERROR (Status)) {
            //
            // Repaint to clear possible error prompt pop-up
            //
            Repaint = TRUE;
            NewLine = TRUE;
          } else {
            Selection->Action = UI_ACTION_REFRESH_FORM;
          }
          if (OptionString != NULL) {
            FreePool (OptionString);
          }
        }
        break;

      case '^':
        ScreenOperation = UiUp;
        break;

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

      case ' ':
        if ((gClassOfVfr & FORMSET_CLASS_FRONT_PAGE) != FORMSET_CLASS_FRONT_PAGE) {
          //
          // If the screen has no menu items, and the user didn't select UiReset
          // ignore the selection and go back to reading keys.
          //
          if(IsListEmpty (&gMenuOption)) {
            ControlFlag = CfReadKey;
            break;
          }
          
          ASSERT(MenuOption != NULL);
          if (MenuOption->ThisTag->Operand == 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 (Selection->Form->ModalForm && (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;
          if ((gBrowserSettingScope == SystemLevel) || 
              (Selection->FormEditable && gFunctionKeySetting != NONE_FUNCTION_KEY_SETTING)) {
            HotKey = GetHotKeyFromRegisterList (&Key);
          }
          if (HotKey != NULL) {
            ScreenOperation = UiHotKey;
          }
        }
        break;
      }
      break;

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

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

    case CfUiSelect:
      ControlFlag = CfCheckSelection;

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

      //
      // Keep highlight on current MenuOption
      //
      Selection->QuestionId = Statement->QuestionId;

      switch (Statement->Operand) {
      case EFI_IFR_REF_OP:
        ProcessGotoOpCode(Statement, Selection, &Repaint, &NewLine);
        break;

      case EFI_IFR_ACTION_OP:
        //
        // Process the Config string <ConfigResp>
        //
        Status = ProcessQuestionConfig (Selection, Statement);

        if (EFI_ERROR (Status)) {
          break;
        }

        //
        // The action button may change some Question value, so refresh the form
        //
        Selection->Action = UI_ACTION_REFRESH_FORM;
        break;

      case EFI_IFR_RESET_BUTTON_OP:
        //
        // Reset Question to default value specified by DefaultId
        //
        ControlFlag = CfUiDefault;
        DefaultId = Statement->DefaultId;
        break;

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

        if (EFI_ERROR (Status)) {
          Repaint = TRUE;
          NewLine = TRUE;
          UpdateKeyHelp (Selection, MenuOption, FALSE);
        } else {
          Selection->Action = UI_ACTION_REFRESH_FORM;
        }

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

    case CfUiReset:
      //
      // We come here when someone press ESC
      //
      ControlFlag = CfCheckSelection;
      FindNextMenu (Selection, &Repaint, &NewLine);
      break;

    case CfUiLeft:
      ControlFlag = CfCheckSelection;
      ASSERT(MenuOption != NULL);
      if ((MenuOption->ThisTag->Operand == EFI_IFR_DATE_OP) || (MenuOption->ThisTag->Operand == 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 = CfCheckSelection;
      ASSERT(MenuOption != NULL);
      if ((MenuOption->ThisTag->Operand == EFI_IFR_DATE_OP) || (MenuOption->ThisTag->Operand == 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 = CfCheckSelection;

      SavedListEntry = NewPos;

      ASSERT(NewPos != NULL);
      //
      // Adjust Date/Time position before we advance forward.
      //
      AdjustDateAndTimePosition (TRUE, &NewPos);
      if (NewPos->BackLink != &gMenuOption) {
        MenuOption = MENU_OPTION_FROM_LINK (NewPos);
        ASSERT (MenuOption != NULL);
        NewLine    = TRUE;
        NewPos     = NewPos->BackLink;

        PreviousMenuOption = MENU_OPTION_FROM_LINK (NewPos);
        if (PreviousMenuOption->Row == 0) {
          UpdateOptionSkipLines (Selection, PreviousMenuOption);
        }
        DistanceValue = PreviousMenuOption->Skip;
        Difference    = 0;
        if (MenuOption->Row >= DistanceValue + TopRow) {
          Difference = MoveToNextStatement (Selection, TRUE, &NewPos, MenuOption->Row - TopRow - DistanceValue);
        }
        NextMenuOption = MENU_OPTION_FROM_LINK (NewPos);
       
        if (Difference < 0) {
          //
          // We hit the begining MenuOption that can be focused
          // so we simply scroll to the top.
          //
          if (TopOfScreen != gMenuOption.ForwardLink) {
            TopOfScreen = gMenuOption.ForwardLink;
            Repaint     = TRUE;
          } else {
            //
            // Scroll up to the last page when we have arrived at top page.
            //
            NewPos          = &gMenuOption;
            TopOfScreen     = &gMenuOption;
            MenuOption      = MENU_OPTION_FROM_LINK (SavedListEntry);
            ScreenOperation = UiPageUp;
            ControlFlag     = CfScreenOperation;
            break;
          }
        } else if (MenuOption->Row < TopRow + DistanceValue + Difference) {
          //
          // Previous focus MenuOption is above the TopOfScreen, so we need to scroll
          //
          TopOfScreen = NewPos;
          Repaint     = TRUE;
          SkipValue = 0;
        } else if (!IsSelectable (NextMenuOption)) {
          //
          // Continue to go up until scroll to next page or the selectable option is found.
          //
          ScreenOperation = UiUp;
          ControlFlag     = CfScreenOperation;
        }

        //
        // If we encounter a Date/Time op-code set, rewind to the first op-code of the set.
        //
        AdjustDateAndTimePosition (TRUE, &TopOfScreen);
        AdjustDateAndTimePosition (TRUE, &NewPos);
        MenuOption = MENU_OPTION_FROM_LINK (SavedListEntry);
        UpdateStatusBar (Selection, INPUT_ERROR, MenuOption->ThisTag->QuestionFlags, FALSE);
      } else {
        //
        // Scroll up to the last page.
        //
        NewPos          = &gMenuOption;
        TopOfScreen     = &gMenuOption;
        MenuOption      = MENU_OPTION_FROM_LINK (SavedListEntry);
        ScreenOperation = UiPageUp;
        ControlFlag     = CfScreenOperation;
      }
      break;

    case CfUiPageUp:
      //
      // SkipValue means lines is skipped when show the top menu option.
      //
      ControlFlag     = CfCheckSelection;

      ASSERT(NewPos != NULL);
      //
      // Already at the first menu option, so do nothing.
      //
      if (NewPos->BackLink == &gMenuOption) {
        NewLine = FALSE;
        Repaint = FALSE;
        break;
      }

      NewLine   = TRUE;
      Repaint   = TRUE;

      //
      // 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.
      //
      if (SkipValue > (INTN) (BottomRow - TopRow + 1)) {
        SkipValue -= BottomRow - TopRow + 1;
        break;
      }

      Link      = TopOfScreen;
      //
      // First minus the menu of the top screen, it's value is SkipValue.
      //
      Index     = (BottomRow + 1) - SkipValue;
      while ((Index >= TopRow) && (Link->BackLink != &gMenuOption)) {
        Link = Link->BackLink;
        PreviousMenuOption = MENU_OPTION_FROM_LINK (Link);
        if (PreviousMenuOption->Row == 0) {
          UpdateOptionSkipLines (Selection, PreviousMenuOption);
        }        
        if (Index < PreviousMenuOption->Skip) {
          break;
        }
        Index = Index - PreviousMenuOption->Skip;
      }
      
      if ((Link->BackLink == &gMenuOption) && (Index >= TopRow)) {
        SkipValue = 0;
        if (TopOfScreen == &gMenuOption) {
          TopOfScreen = gMenuOption.ForwardLink;
          NewPos      = gMenuOption.BackLink;
          MoveToNextStatement (Selection, TRUE, &NewPos, BottomRow - TopRow);
          Repaint = FALSE;
        } else if (TopOfScreen != Link) {
          TopOfScreen = Link;
          NewPos      = Link;
          MoveToNextStatement (Selection, FALSE, &NewPos, BottomRow - TopRow);
        } else {
          //
          // Finally we know that NewPos is the last MenuOption can be focused.
          //
          Repaint = FALSE;
          NewPos  = Link;
          MoveToNextStatement (Selection, FALSE, &NewPos, BottomRow - TopRow);
        }
      } else {
        if (Index >= TopRow) {
          //
          // At here, only case "Index < PreviousMenuOption->Skip" can reach here.
          //
          SkipValue = PreviousMenuOption->Skip - (Index - TopRow);
        } else {
          SkipValue = PreviousMenuOption->Skip - (TopRow - Index);
          Link = Link->ForwardLink;
        }

        //
        // Move to the option in Next page.
        //
        if (TopOfScreen == &gMenuOption) {
          NewPos = gMenuOption.BackLink;
          MoveToNextStatement (Selection, TRUE, &NewPos, BottomRow - TopRow);
        } else {
          NewPos = Link;
          MoveToNextStatement (Selection, FALSE, &NewPos, BottomRow - TopRow);
        }

        //
        // There are more MenuOption needing scrolling up.
        //
        TopOfScreen = Link;
        MenuOption  = NULL;
      }

      //
      // 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);
      break;

    case CfUiPageDown:
      //
      // SkipValue means lines is skipped when show the top menu option.
      //
      ControlFlag     = CfCheckSelection;

      ASSERT (NewPos != NULL);
      if (NewPos->ForwardLink == &gMenuOption) {
        NewLine = FALSE;
        Repaint = FALSE;
        break;
      }

      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)) {
        //
        // Finally we know that NewPos is the last MenuOption can be focused.
        //
        Repaint = FALSE;
        MoveToNextStatement (Selection, TRUE, &Link, Index - TopRow);
        SkipValue = 0;
      } 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 (Selection, FALSE, &Link, BottomRow - TopRow);
      }

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

      //
      // 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);
      break;

    case CfUiDown:
      //
      // SkipValue means lines is skipped when show the top menu option.
      // NewPos  points to the menu which is highlighted now.
      //
      ControlFlag = CfCheckSelection;
      //
      // 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.
      //
      SavedListEntry = NewPos;
      AdjustDateAndTimePosition (FALSE, &NewPos);

      if (NewPos->ForwardLink != &gMenuOption) {
        MenuOption      = MENU_OPTION_FROM_LINK (NewPos);
        NewLine         = TRUE;
        NewPos          = NewPos->ForwardLink;

        Difference      = 0;
        //
        // Current menu not at the bottom of the form.
        //
        if (BottomRow >= MenuOption->Row + MenuOption->Skip) {
          //
          // Find the next selectable menu.
          //
          Difference = MoveToNextStatement (Selection, FALSE, &NewPos, BottomRow - MenuOption->Row - MenuOption->Skip);
          //
          // We hit the end of MenuOption that can be focused
          // so we simply scroll to the first page.
          //
          if (Difference < 0) {
            //
            // Scroll to the first page.
            //
            if (TopOfScreen != gMenuOption.ForwardLink) {
              TopOfScreen = gMenuOption.ForwardLink;
              Repaint     = TRUE;
              MenuOption  = NULL;
            } else {
              MenuOption = MENU_OPTION_FROM_LINK (SavedListEntry);
            }
            NewPos        = gMenuOption.ForwardLink;
            MoveToNextStatement (Selection, FALSE, &NewPos, BottomRow - TopRow);

            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);
            break;
          }
        }
        NextMenuOption  = MENU_OPTION_FROM_LINK (NewPos);
        if (NextMenuOption->Row == 0) {
          UpdateOptionSkipLines (Selection, NextMenuOption);
        }
        DistanceValue  = Difference + NextMenuOption->Skip;

        Temp = MenuOption->Row + MenuOption->Skip + DistanceValue - 1;
        if ((MenuOption->Row + MenuOption->Skip == BottomRow + 1) &&
            (NextMenuOption->ThisTag->Operand == EFI_IFR_DATE_OP ||
             NextMenuOption->ThisTag->Operand == EFI_IFR_TIME_OP)
            ) {
          Temp ++;
        }

        //
        // If we are going to scroll, update TopOfScreen
        //
        if (Temp > BottomRow) {
          do {
            //
            // Is the current top of screen a zero-advance op-code?
            // If so, keep moving forward till we hit a >0 advance op-code
            //
            SavedMenuOption = MENU_OPTION_FROM_LINK (TopOfScreen);

            //
            // If bottom op-code is more than one line or top op-code is more than one line
            //
            if ((DistanceValue > 1) || (SavedMenuOption->Skip > 1)) {
              //
              // Is the bottom op-code greater than or equal in size to the top op-code?
              //
              if ((Temp - BottomRow) >= (SavedMenuOption->Skip - SkipValue)) {
                //
                // Skip the top op-code
                //
                TopOfScreen     = TopOfScreen->ForwardLink;
                Difference      = (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
                //
                while (Difference >= (INTN) SavedMenuOption->Skip) {
                  //
                  // Since the Difference is greater than or equal to this op-code's skip value, skip it
                  //
                  Difference      = Difference - (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.
                //
                SkipValue = Difference - 1;
              } 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) - 1;
              }
            } else {
              if ((SkipValue + 1) == (INTN) SavedMenuOption->Skip) {
                TopOfScreen = TopOfScreen->ForwardLink;
                break;
              }
            }
            //
            // If the op-code at the top of the screen is more than one line, let's not skip it yet
            // Let's set a skip flag to smoothly scroll the top of the screen.
            //
            if (SavedMenuOption->Skip > 1) {
              if (SavedMenuOption == NextMenuOption) {
                SkipValue = 0;
              } else {
                SkipValue++;
              }
            } else if (SavedMenuOption->Skip == 1) {
              SkipValue   = 0;
            } else {
              SkipValue   = 0;
              TopOfScreen = TopOfScreen->ForwardLink;
            }
          } while (SavedMenuOption->Skip == 0);

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

        MenuOption = MENU_OPTION_FROM_LINK (SavedListEntry);

        UpdateStatusBar (Selection, INPUT_ERROR, MenuOption->ThisTag->QuestionFlags, FALSE);

      } else {
        //
        // Scroll to the first page.
        //
        if (TopOfScreen != gMenuOption.ForwardLink) {
          TopOfScreen = gMenuOption.ForwardLink;
          Repaint     = TRUE;
          MenuOption  = NULL;
        } else {
          //
          // Need to remove the current highlight menu.
          // MenuOption saved the last highlight menu info.
          //
          MenuOption = MENU_OPTION_FROM_LINK (SavedListEntry);
        }

        SkipValue     = 0;
        NewLine       = TRUE;
        //
        // Get the next highlight menu.
        //
        NewPos        = gMenuOption.ForwardLink;
        MoveToNextStatement (Selection, FALSE, &NewPos, BottomRow - TopRow);
      }

      //
      // 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);
      break;

    case CfUiHotKey:
      ControlFlag = CfCheckSelection;
      
      Status = EFI_SUCCESS;
      //
      // Discard changes. After it, no NV flag is showed.
      //
      if ((HotKey->Action & BROWSER_ACTION_DISCARD) == BROWSER_ACTION_DISCARD) {
        Status = DiscardForm (Selection->FormSet, Selection->Form, gBrowserSettingScope);
        if (!EFI_ERROR (Status)) {
          Selection->Action = UI_ACTION_REFRESH_FORM;
          Selection->Statement = NULL;
          gResetRequired = FALSE;
        } else {
          do {
            CreateDialog (4, TRUE, 0, NULL, &Key, HotKey->HelpString, gDiscardFailed, gPressEnter, gEmptyString);
          } while (Key.UnicodeChar != CHAR_CARRIAGE_RETURN);
          //
          // Still show current page.
          //
          Selection->Action = UI_ACTION_NONE;
          Repaint = TRUE;
          NewLine = TRUE;
          break;
        }
      }

      //
      // Reterieve default setting. After it. NV flag will be showed.
      //
      if ((HotKey->Action & BROWSER_ACTION_DEFAULT) == BROWSER_ACTION_DEFAULT) {
        Status = ExtractDefault (Selection->FormSet, Selection->Form, HotKey->DefaultId, gBrowserSettingScope, GetDefaultForAll, NULL, FALSE);
        if (!EFI_ERROR (Status)) {
          Selection->Action = UI_ACTION_REFRESH_FORM;
          Selection->Statement = NULL;
          gResetRequired = TRUE;
        } else {
          do {
            CreateDialog (4, TRUE, 0, NULL, &Key, HotKey->HelpString, gDefaultFailed, gPressEnter, gEmptyString);
          } while (Key.UnicodeChar != CHAR_CARRIAGE_RETURN);
          //
          // Still show current page.
          //
          Selection->Action = UI_ACTION_NONE;
          Repaint = TRUE;
          NewLine = TRUE;
          break;
        }
      }

      //
      // Save changes. After it, no NV flag is showed.
      //
      if ((HotKey->Action & BROWSER_ACTION_SUBMIT) == BROWSER_ACTION_SUBMIT) {
        Status = SubmitForm (Selection->FormSet, Selection->Form, gBrowserSettingScope);
        if (!EFI_ERROR (Status)) {
          ASSERT(MenuOption != NULL);
          UpdateStatusBar (Selection, INPUT_ERROR, MenuOption->ThisTag->QuestionFlags, FALSE);
          UpdateStatusBar (Selection, NV_UPDATE_REQUIRED, MenuOption->ThisTag->QuestionFlags, FALSE);
        } else {
          do {
            CreateDialog (4, TRUE, 0, NULL, &Key, HotKey->HelpString, gSaveFailed, gPressEnter, gEmptyString);
          } while (Key.UnicodeChar != CHAR_CARRIAGE_RETURN);
          //
          // Still show current page.
          //
          Selection->Action = UI_ACTION_NONE;
          Repaint = TRUE;
          NewLine = TRUE;
          break;
        }
      }
      
      //
      // Set Reset required Flag
      //
      if ((HotKey->Action & BROWSER_ACTION_RESET) == BROWSER_ACTION_RESET) {
        gResetRequired = TRUE;
      }
      
      //
      // Exit Action
      //
      if ((HotKey->Action & BROWSER_ACTION_EXIT) == BROWSER_ACTION_EXIT) {
        //
        // Form Exit without saving, Similar to ESC Key.
        // FormSet Exit without saving, Exit SendForm.
        // System Exit without saving, CallExitHandler and Exit SendForm.
        //
        DiscardForm (Selection->FormSet, Selection->Form, gBrowserSettingScope);
        if (gBrowserSettingScope == FormLevel) {
          ControlFlag = CfUiReset;
        } else if (gBrowserSettingScope == FormSetLevel) {
          Selection->Action = UI_ACTION_EXIT;
        } else if (gBrowserSettingScope == SystemLevel) {
          if (ExitHandlerFunction != NULL) {
            ExitHandlerFunction ();
          }
          Selection->Action = UI_ACTION_EXIT;
        }
        Selection->Statement = NULL;
      }
      break;

    case CfUiDefault:
      ControlFlag = CfCheckSelection;
      //
      // Reset to default value for all forms in the whole system.
      //
      Status = ExtractDefault (Selection->FormSet, NULL, DefaultId, FormSetLevel, GetDefaultForAll, NULL, FALSE);

      if (!EFI_ERROR (Status)) {
        Selection->Action = UI_ACTION_REFRESH_FORM;
        Selection->Statement = NULL;
        gResetRequired = TRUE;
      }
      break;

    case CfUiNoOperation:
      ControlFlag = CfCheckSelection;
      break;

    case CfExit:
      UiFreeRefreshList ();

      gST->ConOut->SetAttribute (gST->ConOut, EFI_TEXT_ATTR (EFI_LIGHTGRAY, EFI_BLACK));
      gST->ConOut->SetCursorPosition (gST->ConOut, 0, Row + 4);
      gST->ConOut->EnableCursor (gST->ConOut, TRUE);
      gST->ConOut->OutputString (gST->ConOut, L"\n");
      if (HelpString != NULL) {
        FreePool (HelpString);
      }
      if (HelpHeaderString != NULL) {
        FreePool (HelpHeaderString);
      }
      if (HelpBottomString != NULL) {
        FreePool (HelpBottomString);
      }

      return EFI_SUCCESS;

    default:
      break;
    }
  }
}
