/** @file
  The application to show the Boot Manager Menu.

Copyright (c) 2011 - 2021, Intel Corporation. All rights reserved.<BR>
SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include "BootManagerMenu.h"

EFI_HII_HANDLE  gStringPackHandle;

BOOLEAN  mModeInitialized = FALSE;

//
// Boot video resolution and text mode.
//
UINT32  mBootHorizontalResolution = 0;
UINT32  mBootVerticalResolution   = 0;
UINT32  mBootTextModeColumn       = 0;
UINT32  mBootTextModeRow          = 0;
//
// BIOS setup video resolution and text mode.
//
UINT32  mSetupTextModeColumn       = 0;
UINT32  mSetupTextModeRow          = 0;
UINT32  mSetupHorizontalResolution = 0;
UINT32  mSetupVerticalResolution   = 0;

/**
  Prints a unicode string to the default console, at
  the supplied cursor position, using L"%s" format.

  @param  Column     The cursor position to print the string at.
  @param  Row        The cursor position to print the string at
  @param  String     String pointer.

  @return Length of string printed to the console

**/
UINTN
PrintStringAt (
  IN UINTN   Column,
  IN UINTN   Row,
  IN CHAR16  *String
  )
{
  UINTN       ScreenWidth;
  UINTN       ScreenRows;
  CHAR16      *TurncateString;
  EFI_STATUS  Status;
  UINTN       ShowingLength;

  gST->ConOut->SetCursorPosition (gST->ConOut, Column, Row);

  gST->ConOut->QueryMode (
                 gST->ConOut,
                 gST->ConOut->Mode->Mode,
                 &ScreenWidth,
                 &ScreenRows
                 );

  if ((Column > (ScreenWidth - 1)) || (Row > (ScreenRows - 1))) {
    return 0;
  }

  if ((StrLen (String) + Column) > (ScreenWidth - 1)) {
    //
    // |      - ScreenWidth -       |
    // ...Column.....................
    // TurncateString length should leave one character for draw box and
    // require one character for string end.
    //
    ShowingLength  = ScreenWidth - Column - 1;
    TurncateString = AllocatePool ((ShowingLength + 1) * sizeof (CHAR16));

    if (TurncateString == NULL) {
      return 0;
    }

    Status = StrnCpyS (TurncateString, ShowingLength + 1, String, ShowingLength - 3);

    if (EFI_ERROR (Status)) {
      FreePool (TurncateString);
      return 0;
    }

    *(TurncateString + ShowingLength - 3) = L'.';
    *(TurncateString + ShowingLength - 2) = L'.';
    *(TurncateString + ShowingLength - 1) = L'.';
    *(TurncateString + ShowingLength)     = L'\0';
    ShowingLength                         = Print (L"%s", TurncateString);
    FreePool (TurncateString);
    return ShowingLength;
  } else {
    return Print (L"%s", String);
  }
}

/**
  Prints a character to the default console, at
  the supplied cursor position, using L"%c" format.

  @param  Column     The cursor position to print the string at.
  @param  Row        The cursor position to print the string at.
  @param  Character  Character to print.

  @return Length of string printed to the console.

**/
UINTN
PrintCharAt (
  IN UINTN  Column,
  IN UINTN  Row,
  CHAR16    Character
  )
{
  UINTN  ScreenWidth;
  UINTN  ScreenRows;

  gST->ConOut->SetCursorPosition (gST->ConOut, Column, Row);

  gST->ConOut->QueryMode (
                 gST->ConOut,
                 gST->ConOut->Mode->Mode,
                 &ScreenWidth,
                 &ScreenRows
                 );

  if ((Column > (ScreenWidth - 1)) || (Row > (ScreenRows - 1))) {
    return 0;
  }

  return Print (L"%c", Character);
}

/**
  Count the storage space of a Unicode string which uses current language to get
  from input string ID.

  @param StringId          The input string to be counted.

  @return Storage space for the input string.

**/
UINTN
GetLineWidth (
  IN EFI_STRING_ID  StringId
  )
{
  UINTN       Index;
  UINTN       IncrementValue;
  EFI_STRING  String;
  UINTN       LineWidth;

  LineWidth = 0;
  String    = HiiGetString (gStringPackHandle, StringId, NULL);

  if (String != NULL) {
    Index          = 0;
    IncrementValue = 1;

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

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

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

    FreePool (String);
  }

  return LineWidth;
}

/**
  This function uses calculate the boot menu location, size and scroll bar information.

  @param  BootMenuData            The boot menu data to be processed.

  @return EFI_SUCCESS             calculate boot menu information successful.
  @retval EFI_INVALID_PARAMETER   Input parameter is invalid

**/
EFI_STATUS
InitializeBootMenuScreen (
  IN OUT  BOOT_MENU_POPUP_DATA  *BootMenuData
  )
{
  UINTN  MaxStrWidth;
  UINTN  StrWidth;
  UINTN  Index;
  UINTN  Column;
  UINTN  Row;
  UINTN  MaxPrintRows;
  UINTN  UnSelectableItmes;

  if (BootMenuData == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  //
  // Get maximum string width
  //
  MaxStrWidth = 0;
  for (Index = 0; Index < TITLE_TOKEN_COUNT; Index++) {
    StrWidth    = GetLineWidth (BootMenuData->TitleToken[Index]);
    MaxStrWidth = MaxStrWidth > StrWidth ? MaxStrWidth : StrWidth;
  }

  for (Index = 0; Index < BootMenuData->ItemCount; Index++) {
    StrWidth    = GetLineWidth (BootMenuData->PtrTokens[Index]);
    MaxStrWidth = MaxStrWidth > StrWidth ? MaxStrWidth : StrWidth;
  }

  for (Index = 0; Index < HELP_TOKEN_COUNT; Index++) {
    StrWidth    = GetLineWidth (BootMenuData->HelpToken[Index]);
    MaxStrWidth = MaxStrWidth > StrWidth ? MaxStrWidth : StrWidth;
  }

  //
  // query current row and column to calculate boot menu location
  //
  gST->ConOut->QueryMode (
                 gST->ConOut,
                 gST->ConOut->Mode->Mode,
                 &Column,
                 &Row
                 );

  MaxPrintRows      = Row - 6;
  UnSelectableItmes = TITLE_TOKEN_COUNT + 2 + HELP_TOKEN_COUNT + 2;
  if (MaxStrWidth + 8 > Column) {
    BootMenuData->MenuScreen.Width = Column;
  } else {
    BootMenuData->MenuScreen.Width = MaxStrWidth + 8;
  }

  if (BootMenuData->ItemCount + UnSelectableItmes > MaxPrintRows) {
    BootMenuData->MenuScreen.Height                   = MaxPrintRows;
    BootMenuData->ScrollBarControl.HasScrollBar       = TRUE;
    BootMenuData->ScrollBarControl.ItemCountPerScreen = MaxPrintRows - UnSelectableItmes;
    BootMenuData->ScrollBarControl.FirstItem          = 0;
    BootMenuData->ScrollBarControl.LastItem           = MaxPrintRows - UnSelectableItmes - 1;
  } else {
    BootMenuData->MenuScreen.Height                   = BootMenuData->ItemCount + UnSelectableItmes;
    BootMenuData->ScrollBarControl.HasScrollBar       = FALSE;
    BootMenuData->ScrollBarControl.ItemCountPerScreen = BootMenuData->ItemCount;
    BootMenuData->ScrollBarControl.FirstItem          = 0;
    BootMenuData->ScrollBarControl.LastItem           = BootMenuData->ItemCount - 1;
  }

  BootMenuData->MenuScreen.StartCol = (Column -  BootMenuData->MenuScreen.Width) / 2;
  BootMenuData->MenuScreen.StartRow = (Row -  BootMenuData->MenuScreen.Height) / 2;

  return EFI_SUCCESS;
}

/**
  This function uses check boot option is wheher setup application or no

  @param   BootOption   Pointer to EFI_BOOT_MANAGER_LOAD_OPTION array.

  @retval  TRUE         This boot option is setup application.
  @retval  FALSE        This boot options isn't setup application

**/
BOOLEAN
IsBootManagerMenu (
  IN  EFI_BOOT_MANAGER_LOAD_OPTION  *BootOption
  )
{
  EFI_STATUS                    Status;
  EFI_BOOT_MANAGER_LOAD_OPTION  BootManagerMenu;

  Status = EfiBootManagerGetBootManagerMenu (&BootManagerMenu);
  if (!EFI_ERROR (Status)) {
    EfiBootManagerFreeLoadOption (&BootManagerMenu);
  }

  return (BOOLEAN)(!EFI_ERROR (Status) && (BootOption->OptionNumber == BootManagerMenu.OptionNumber));
}

/**
  Return whether to ignore the boot option.

  @param BootOption  Pointer to EFI_BOOT_MANAGER_LOAD_OPTION to check.

  @retval TRUE  Ignore the boot option.
  @retval FALSE Do not ignore the boot option.
**/
BOOLEAN
IgnoreBootOption (
  IN   EFI_BOOT_MANAGER_LOAD_OPTION  *BootOption
  )
{
  EFI_STATUS                Status;
  EFI_DEVICE_PATH_PROTOCOL  *ImageDevicePath;

  //
  // Ignore myself.
  //
  Status = gBS->HandleProtocol (gImageHandle, &gEfiLoadedImageDevicePathProtocolGuid, (VOID **)&ImageDevicePath);
  ASSERT_EFI_ERROR (Status);
  if (CompareMem (BootOption->FilePath, ImageDevicePath, GetDevicePathSize (ImageDevicePath)) == 0) {
    return TRUE;
  }

  //
  // Do not ignore Boot Manager Menu.
  //
  if (IsBootManagerMenu (BootOption)) {
    return FALSE;
  }

  //
  // Ignore the hidden/inactive boot option.
  //
  if (((BootOption->Attributes & LOAD_OPTION_HIDDEN) != 0) || ((BootOption->Attributes & LOAD_OPTION_ACTIVE) == 0)) {
    return TRUE;
  }

  return FALSE;
}

/**
  This function uses to initialize boot menu data

  @param   BootOption             Pointer to EFI_BOOT_MANAGER_LOAD_OPTION array.
  @param   BootOptionCount        Number of boot option.
  @param   BootMenuData           The Input BootMenuData to be initialized.

  @retval  EFI_SUCCESS            Initialize boot menu data successful.
  @retval  EFI_INVALID_PARAMETER  Input parameter is invalid.

**/
EFI_STATUS
InitializeBootMenuData (
  IN   EFI_BOOT_MANAGER_LOAD_OPTION  *BootOption,
  IN   UINTN                         BootOptionCount,
  OUT  BOOT_MENU_POPUP_DATA          *BootMenuData
  )
{
  UINTN  Index;
  UINTN  StrIndex;

  if ((BootOption == NULL) || (BootMenuData == NULL)) {
    return EFI_INVALID_PARAMETER;
  }

  BootMenuData->TitleToken[0] = STRING_TOKEN (STR_BOOT_POPUP_MENU_TITLE_STRING);
  BootMenuData->PtrTokens     = AllocateZeroPool (BootOptionCount * sizeof (EFI_STRING_ID));
  ASSERT (BootMenuData->PtrTokens != NULL);

  //
  // Skip boot option which created by BootNext Variable
  //
  for (StrIndex = 0, Index = 0; Index < BootOptionCount; Index++) {
    if (IgnoreBootOption (&BootOption[Index])) {
      continue;
    }

    ASSERT (BootOption[Index].Description != NULL);
    BootMenuData->PtrTokens[StrIndex++] = HiiSetString (
                                            gStringPackHandle,
                                            0,
                                            BootOption[Index].Description,
                                            NULL
                                            );
  }

  BootMenuData->ItemCount    = StrIndex;
  BootMenuData->HelpToken[0] = STRING_TOKEN (STR_BOOT_POPUP_MENU_HELP1_STRING);
  BootMenuData->HelpToken[1] = STRING_TOKEN (STR_BOOT_POPUP_MENU_HELP2_STRING);
  BootMenuData->HelpToken[2] = STRING_TOKEN (STR_BOOT_POPUP_MENU_HELP3_STRING);
  InitializeBootMenuScreen (BootMenuData);
  BootMenuData->SelectItem = 0;
  return EFI_SUCCESS;
}

/**
  This function uses input select item to highlight selected item
  and set current selected item in BootMenuData

  @param  WantSelectItem          The user wants to select item.
  @param  BootMenuData            The boot menu data to be processed

  @return EFI_SUCCESS             Highlight selected item and update current selected
                                  item successful
  @retval EFI_INVALID_PARAMETER   Input parameter is invalid
**/
EFI_STATUS
BootMenuSelectItem (
  IN     UINTN                 WantSelectItem,
  IN OUT BOOT_MENU_POPUP_DATA  *BootMenuData
  )
{
  INT32       SavedAttribute;
  EFI_STRING  String;
  UINTN       StartCol;
  UINTN       StartRow;
  UINTN       PrintCol;
  UINTN       PrintRow;
  UINTN       TopShadeNum;
  UINTN       LowShadeNum;
  UINTN       FirstItem;
  UINTN       LastItem;
  UINTN       ItemCountPerScreen;
  UINTN       Index;
  BOOLEAN     RePaintItems;

  if ((BootMenuData == NULL) || (WantSelectItem >= BootMenuData->ItemCount)) {
    return EFI_INVALID_PARAMETER;
  }

  ASSERT (BootMenuData->ItemCount != 0);
  SavedAttribute = gST->ConOut->Mode->Attribute;
  RePaintItems   = FALSE;
  StartCol       = BootMenuData->MenuScreen.StartCol;
  StartRow       = BootMenuData->MenuScreen.StartRow;
  //
  // print selectable items again and adjust scroll bar if need
  //
  if (BootMenuData->ScrollBarControl.HasScrollBar &&
      ((WantSelectItem < BootMenuData->ScrollBarControl.FirstItem) ||
       (WantSelectItem > BootMenuData->ScrollBarControl.LastItem) ||
       (WantSelectItem == BootMenuData->SelectItem)))
  {
    ItemCountPerScreen = BootMenuData->ScrollBarControl.ItemCountPerScreen;
    //
    // Set first item and last item
    //
    if (WantSelectItem < BootMenuData->ScrollBarControl.FirstItem) {
      BootMenuData->ScrollBarControl.FirstItem = WantSelectItem;
      BootMenuData->ScrollBarControl.LastItem  = WantSelectItem + ItemCountPerScreen - 1;
    } else if (WantSelectItem > BootMenuData->ScrollBarControl.LastItem) {
      BootMenuData->ScrollBarControl.FirstItem = WantSelectItem - ItemCountPerScreen + 1;
      BootMenuData->ScrollBarControl.LastItem  = WantSelectItem;
    }

    gST->ConOut->SetAttribute (gST->ConOut, EFI_WHITE | EFI_BACKGROUND_BLUE);
    FirstItem   = BootMenuData->ScrollBarControl.FirstItem;
    LastItem    = BootMenuData->ScrollBarControl.LastItem;
    TopShadeNum = 0;
    if (FirstItem != 0) {
      TopShadeNum = (FirstItem * ItemCountPerScreen) / BootMenuData->ItemCount;
      if ((FirstItem * ItemCountPerScreen) % BootMenuData->ItemCount != 0) {
        TopShadeNum++;
      }

      PrintCol = StartCol  + BootMenuData->MenuScreen.Width - 2;
      PrintRow = StartRow + TITLE_TOKEN_COUNT + 2;
      for (Index = 0; Index < TopShadeNum; Index++, PrintRow++) {
        PrintCharAt (PrintCol, PrintRow, BLOCKELEMENT_LIGHT_SHADE);
      }
    }

    LowShadeNum = 0;
    if (LastItem != BootMenuData->ItemCount - 1) {
      LowShadeNum = ((BootMenuData->ItemCount - 1 - LastItem) * ItemCountPerScreen) / BootMenuData->ItemCount;
      if (((BootMenuData->ItemCount - 1 - LastItem) * ItemCountPerScreen) % BootMenuData->ItemCount != 0) {
        LowShadeNum++;
      }

      PrintCol = StartCol  + BootMenuData->MenuScreen.Width - 2;
      PrintRow = StartRow + TITLE_TOKEN_COUNT + 2 + ItemCountPerScreen - LowShadeNum;
      for (Index = 0; Index < LowShadeNum; Index++, PrintRow++) {
        PrintCharAt (PrintCol, PrintRow, BLOCKELEMENT_LIGHT_SHADE);
      }
    }

    PrintCol = StartCol  + BootMenuData->MenuScreen.Width - 2;
    PrintRow = StartRow + TITLE_TOKEN_COUNT + 2 + TopShadeNum;
    for (Index = TopShadeNum; Index < ItemCountPerScreen - LowShadeNum; Index++, PrintRow++) {
      PrintCharAt (PrintCol, PrintRow, BLOCKELEMENT_FULL_BLOCK);
    }

    //
    // Clear selectable items first
    //
    PrintCol = StartCol  + 1;
    PrintRow = StartRow + TITLE_TOKEN_COUNT + 2;
    String   = AllocateZeroPool ((BootMenuData->MenuScreen.Width - 2) * sizeof (CHAR16));
    ASSERT (String != NULL);
    for (Index = 0; Index < BootMenuData->MenuScreen.Width - 3; Index++) {
      String[Index] = 0x20;
    }

    for (Index = 0; Index < ItemCountPerScreen; Index++) {
      PrintStringAt (PrintCol, PrintRow + Index, String);
    }

    FreePool (String);
    //
    // print selectable items
    //
    for (Index = 0; Index < ItemCountPerScreen; Index++, PrintRow++) {
      String = HiiGetString (gStringPackHandle, BootMenuData->PtrTokens[Index + FirstItem], NULL);
      PrintStringAt (PrintCol, PrintRow, String);
      FreePool (String);
    }

    RePaintItems = TRUE;
  }

  //
  // if Want Select and selected item isn't the same and doesn't re-draw selectable
  // items, clear select item
  //
  FirstItem = BootMenuData->ScrollBarControl.FirstItem;
  if ((WantSelectItem != BootMenuData->SelectItem) && !RePaintItems) {
    gST->ConOut->SetAttribute (gST->ConOut, EFI_WHITE | EFI_BACKGROUND_BLUE);
    String   = HiiGetString (gStringPackHandle, BootMenuData->PtrTokens[BootMenuData->SelectItem], NULL);
    PrintCol = StartCol  + 1;
    PrintRow = StartRow + 3 + BootMenuData->SelectItem - FirstItem;
    PrintStringAt (PrintCol, PrintRow, String);
    FreePool (String);
  }

  //
  // Print want to select item
  //
  gST->ConOut->SetAttribute (gST->ConOut, EFI_WHITE | EFI_BACKGROUND_BLACK);
  String   = HiiGetString (gStringPackHandle, BootMenuData->PtrTokens[WantSelectItem], NULL);
  PrintCol = StartCol  + 1;
  PrintRow = StartRow + TITLE_TOKEN_COUNT + 2 + WantSelectItem - FirstItem;
  PrintStringAt (PrintCol, PrintRow, String);
  FreePool (String);

  gST->ConOut->SetAttribute (gST->ConOut, SavedAttribute);
  BootMenuData->SelectItem = WantSelectItem;
  return EFI_SUCCESS;
}

/**
  This function uses to draw boot popup menu

  @param   BootMenuData           The Input BootMenuData to be processed.

  @retval  EFI_SUCCESS            Draw boot popup menu successful.

**/
EFI_STATUS
DrawBootPopupMenu (
  IN  BOOT_MENU_POPUP_DATA  *BootMenuData
  )
{
  EFI_STRING  String;
  UINTN       Index;
  UINTN       Width;
  UINTN       StartCol;
  UINTN       StartRow;
  UINTN       PrintRow;
  UINTN       PrintCol;
  UINTN       LineWidth;
  INT32       SavedAttribute;
  UINTN       ItemCountPerScreen;

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

  SavedAttribute = gST->ConOut->Mode->Attribute;
  gST->ConOut->SetAttribute (gST->ConOut, EFI_WHITE | EFI_BACKGROUND_BLUE);
  Width              = BootMenuData->MenuScreen.Width;
  StartCol           = BootMenuData->MenuScreen.StartCol;
  StartRow           = BootMenuData->MenuScreen.StartRow;
  ItemCountPerScreen = BootMenuData->ScrollBarControl.ItemCountPerScreen;
  PrintRow           = StartRow;

  gST->ConOut->EnableCursor (gST->ConOut, FALSE);
  //
  // Draw Boot popup menu screen
  //
  PrintCharAt (StartCol, PrintRow, BOXDRAW_DOWN_RIGHT);
  for (Index = 1; Index < Width - 1; Index++) {
    PrintCharAt (StartCol + Index, PrintRow, BOXDRAW_HORIZONTAL);
  }

  PrintCharAt (StartCol + Width - 1, PrintRow, BOXDRAW_DOWN_LEFT);

  //
  // Draw the screen for title
  //
  String = AllocateZeroPool ((Width - 1) * sizeof (CHAR16));
  ASSERT (String != NULL);
  for (Index = 0; Index < Width - 2; Index++) {
    String[Index] = 0x20;
  }

  for (Index = 0; Index < TITLE_TOKEN_COUNT; Index++) {
    PrintRow++;
    PrintCharAt (StartCol, PrintRow, BOXDRAW_VERTICAL);
    PrintStringAt (StartCol + 1, PrintRow, String);
    PrintCharAt (StartCol + Width - 1, PrintRow, BOXDRAW_VERTICAL);
  }

  PrintRow++;
  PrintCharAt (StartCol, PrintRow, BOXDRAW_VERTICAL_RIGHT);
  for (Index = 1; Index < Width - 1; Index++) {
    PrintCharAt (StartCol + Index, PrintRow, BOXDRAW_HORIZONTAL);
  }

  PrintCharAt (StartCol + Width - 1, PrintRow, BOXDRAW_VERTICAL_LEFT);

  //
  // Draw screen for selectable items
  //
  for (Index = 0; Index < ItemCountPerScreen; Index++) {
    PrintRow++;
    PrintCharAt (StartCol, PrintRow, BOXDRAW_VERTICAL);
    PrintStringAt (StartCol + 1, PrintRow, String);
    PrintCharAt (StartCol + Width - 1, PrintRow, BOXDRAW_VERTICAL);
  }

  PrintRow++;
  PrintCharAt (StartCol, PrintRow, BOXDRAW_VERTICAL_RIGHT);
  for (Index = 1; Index < Width - 1; Index++) {
    PrintCharAt (StartCol + Index, PrintRow, BOXDRAW_HORIZONTAL);
  }

  PrintCharAt (StartCol + Width - 1, PrintRow, BOXDRAW_VERTICAL_LEFT);

  //
  // Draw screen for Help
  //
  for (Index = 0; Index < HELP_TOKEN_COUNT; Index++) {
    PrintRow++;
    PrintCharAt (StartCol, PrintRow, BOXDRAW_VERTICAL);
    PrintStringAt (StartCol + 1, PrintRow, String);
    PrintCharAt (StartCol + Width - 1, PrintRow, BOXDRAW_VERTICAL);
  }

  FreePool (String);

  PrintRow++;
  PrintCharAt (StartCol, PrintRow, BOXDRAW_UP_RIGHT);
  for (Index = 1; Index < Width - 1; Index++) {
    PrintCharAt (StartCol + Index, PrintRow, BOXDRAW_HORIZONTAL);
  }

  PrintCharAt (StartCol + Width - 1, PrintRow, BOXDRAW_UP_LEFT);

  //
  // print title strings
  //
  PrintRow = StartRow + 1;
  for (Index = 0; Index < TITLE_TOKEN_COUNT; Index++, PrintRow++) {
    String    = HiiGetString (gStringPackHandle, BootMenuData->TitleToken[Index], NULL);
    LineWidth = GetLineWidth (BootMenuData->TitleToken[Index]);
    PrintCol  = StartCol + (Width - LineWidth) / 2;
    PrintStringAt (PrintCol, PrintRow, String);
    FreePool (String);
  }

  //
  // print selectable items
  //
  PrintCol = StartCol + 1;
  PrintRow = StartRow + TITLE_TOKEN_COUNT + 2;
  for (Index = 0; Index < ItemCountPerScreen; Index++, PrintRow++) {
    String = HiiGetString (gStringPackHandle, BootMenuData->PtrTokens[Index], NULL);
    PrintStringAt (PrintCol, PrintRow, String);
    FreePool (String);
  }

  //
  // Print Help strings
  //
  PrintRow++;
  for (Index = 0; Index < HELP_TOKEN_COUNT; Index++, PrintRow++) {
    String    = HiiGetString (gStringPackHandle, BootMenuData->HelpToken[Index], NULL);
    LineWidth = GetLineWidth (BootMenuData->HelpToken[Index]);
    PrintCol  = StartCol + (Width - LineWidth) / 2;
    PrintStringAt (PrintCol, PrintRow, String);
    FreePool (String);
  }

  //
  // Print scroll bar if has scroll bar
  //
  if (BootMenuData->ScrollBarControl.HasScrollBar) {
    PrintCol = StartCol + Width - 2;
    PrintRow = StartRow + 2;
    PrintCharAt (PrintCol, PrintRow, GEOMETRICSHAPE_UP_TRIANGLE);
    PrintCharAt (PrintCol + 1, PrintRow, BOXDRAW_VERTICAL);
    PrintRow += (ItemCountPerScreen + 1);
    PrintCharAt (PrintCol, PrintRow, GEOMETRICSHAPE_DOWN_TRIANGLE);
    PrintCharAt (PrintCol + 1, PrintRow, BOXDRAW_VERTICAL);
  }

  gST->ConOut->SetAttribute (gST->ConOut, SavedAttribute);
  //
  // Print Selected item
  //
  BootMenuSelectItem (BootMenuData->SelectItem, BootMenuData);
  return EFI_SUCCESS;
}

/**
  This function uses to boot from selected item

  @param   BootOptions            Pointer to EFI_BOOT_MANAGER_LOAD_OPTION array.
  @param   BootOptionCount        Number of boot option.
  @param   SelectItem             Current selected item.
**/
VOID
BootFromSelectOption (
  IN   EFI_BOOT_MANAGER_LOAD_OPTION  *BootOptions,
  IN   UINTN                         BootOptionCount,
  IN   UINTN                         SelectItem
  )
{
  UINTN  ItemNum;
  UINTN  Index;

  ASSERT (BootOptions != NULL);

  for (ItemNum = 0, Index = 0; Index < BootOptionCount; Index++) {
    if (IgnoreBootOption (&BootOptions[Index])) {
      continue;
    }

    if (ItemNum++ == SelectItem) {
      EfiBootManagerBoot (&BootOptions[Index]);
      break;
    }
  }
}

/**
  This function will change video resolution and text mode
  according to defined setup mode or defined boot mode

  @param  IsSetupMode   Indicate mode is changed to setup mode or boot mode.

  @retval  EFI_SUCCESS  Mode is changed successfully.
  @retval  Others             Mode failed to be changed.

**/
EFI_STATUS
EFIAPI
BdsSetConsoleMode (
  BOOLEAN  IsSetupMode
  )
{
  EFI_GRAPHICS_OUTPUT_PROTOCOL          *GraphicsOutput;
  EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL       *SimpleTextOut;
  UINTN                                 SizeOfInfo;
  EFI_GRAPHICS_OUTPUT_MODE_INFORMATION  *Info;
  UINT32                                MaxGopMode;
  UINT32                                MaxTextMode;
  UINT32                                ModeNumber;
  UINT32                                NewHorizontalResolution;
  UINT32                                NewVerticalResolution;
  UINT32                                NewColumns;
  UINT32                                NewRows;
  UINTN                                 HandleCount;
  EFI_HANDLE                            *HandleBuffer;
  EFI_STATUS                            Status;
  UINTN                                 Index;
  UINTN                                 CurrentColumn;
  UINTN                                 CurrentRow;

  MaxGopMode  = 0;
  MaxTextMode = 0;

  //
  // Get current video resolution and text mode
  //
  Status = gBS->HandleProtocol (
                  gST->ConsoleOutHandle,
                  &gEfiGraphicsOutputProtocolGuid,
                  (VOID **)&GraphicsOutput
                  );
  if (EFI_ERROR (Status)) {
    GraphicsOutput = NULL;
  }

  Status = gBS->HandleProtocol (
                  gST->ConsoleOutHandle,
                  &gEfiSimpleTextOutProtocolGuid,
                  (VOID **)&SimpleTextOut
                  );
  if (EFI_ERROR (Status)) {
    SimpleTextOut = NULL;
  }

  if ((GraphicsOutput == NULL) || (SimpleTextOut == NULL)) {
    return EFI_UNSUPPORTED;
  }

  if (IsSetupMode) {
    //
    // The required resolution and text mode is setup mode.
    //
    NewHorizontalResolution = mSetupHorizontalResolution;
    NewVerticalResolution   = mSetupVerticalResolution;
    NewColumns              = mSetupTextModeColumn;
    NewRows                 = mSetupTextModeRow;
  } else {
    //
    // The required resolution and text mode is boot mode.
    //
    NewHorizontalResolution = mBootHorizontalResolution;
    NewVerticalResolution   = mBootVerticalResolution;
    NewColumns              = mBootTextModeColumn;
    NewRows                 = mBootTextModeRow;
  }

  if (GraphicsOutput != NULL) {
    MaxGopMode = GraphicsOutput->Mode->MaxMode;
  }

  if (SimpleTextOut != NULL) {
    MaxTextMode = SimpleTextOut->Mode->MaxMode;
  }

  //
  // 1. If current video resolution is same with required video resolution,
  //    video resolution need not be changed.
  //    1.1. If current text mode is same with required text mode, text mode need not be changed.
  //    1.2. If current text mode is different from required text mode, text mode need be changed.
  // 2. If current video resolution is different from required video resolution, we need restart whole console drivers.
  //
  for (ModeNumber = 0; ModeNumber < MaxGopMode; ModeNumber++) {
    Status = GraphicsOutput->QueryMode (
                               GraphicsOutput,
                               ModeNumber,
                               &SizeOfInfo,
                               &Info
                               );
    if (!EFI_ERROR (Status)) {
      if ((Info->HorizontalResolution == NewHorizontalResolution) &&
          (Info->VerticalResolution == NewVerticalResolution))
      {
        if ((GraphicsOutput->Mode->Info->HorizontalResolution == NewHorizontalResolution) &&
            (GraphicsOutput->Mode->Info->VerticalResolution == NewVerticalResolution))
        {
          //
          // Current resolution is same with required resolution, check if text mode need be set
          //
          Status = SimpleTextOut->QueryMode (SimpleTextOut, SimpleTextOut->Mode->Mode, &CurrentColumn, &CurrentRow);
          ASSERT_EFI_ERROR (Status);
          if ((CurrentColumn == NewColumns) && (CurrentRow == NewRows)) {
            //
            // If current text mode is same with required text mode. Do nothing
            //
            FreePool (Info);
            return EFI_SUCCESS;
          } else {
            //
            // If current text mode is different from required text mode.  Set new video mode
            //
            for (Index = 0; Index < MaxTextMode; Index++) {
              Status = SimpleTextOut->QueryMode (SimpleTextOut, Index, &CurrentColumn, &CurrentRow);
              if (!EFI_ERROR (Status)) {
                if ((CurrentColumn == NewColumns) && (CurrentRow == NewRows)) {
                  //
                  // Required text mode is supported, set it.
                  //
                  Status = SimpleTextOut->SetMode (SimpleTextOut, Index);
                  ASSERT_EFI_ERROR (Status);
                  //
                  // Update text mode PCD.
                  //
                  Status = PcdSet32S (PcdConOutColumn, mSetupTextModeColumn);
                  ASSERT_EFI_ERROR (Status);
                  Status = PcdSet32S (PcdConOutRow, mSetupTextModeRow);
                  ASSERT_EFI_ERROR (Status);
                  FreePool (Info);
                  return EFI_SUCCESS;
                }
              }
            }

            if (Index == MaxTextMode) {
              //
              // If required text mode is not supported, return error.
              //
              FreePool (Info);
              return EFI_UNSUPPORTED;
            }
          }
        } else {
          //
          // If current video resolution is not same with the new one, set new video resolution.
          // In this case, the driver which produces simple text out need be restarted.
          //
          Status = GraphicsOutput->SetMode (GraphicsOutput, ModeNumber);
          if (!EFI_ERROR (Status)) {
            FreePool (Info);
            break;
          }
        }
      }

      FreePool (Info);
    }
  }

  if (ModeNumber == MaxGopMode) {
    //
    // If the resolution is not supported, return error.
    //
    return EFI_UNSUPPORTED;
  }

  //
  // Set PCD to Inform GraphicsConsole to change video resolution.
  // Set PCD to Inform Consplitter to change text mode.
  //
  Status = PcdSet32S (PcdVideoHorizontalResolution, NewHorizontalResolution);
  ASSERT_EFI_ERROR (Status);
  Status = PcdSet32S (PcdVideoVerticalResolution, NewVerticalResolution);
  ASSERT_EFI_ERROR (Status);
  Status = PcdSet32S (PcdConOutColumn, NewColumns);
  ASSERT_EFI_ERROR (Status);
  Status = PcdSet32S (PcdConOutRow, NewRows);
  ASSERT_EFI_ERROR (Status);

  //
  // Video mode is changed, so restart graphics console driver and higher level driver.
  // Reconnect graphics console driver and higher level driver.
  // Locate all the handles with GOP protocol and reconnect it.
  //
  Status = gBS->LocateHandleBuffer (
                  ByProtocol,
                  &gEfiSimpleTextOutProtocolGuid,
                  NULL,
                  &HandleCount,
                  &HandleBuffer
                  );
  if (!EFI_ERROR (Status)) {
    for (Index = 0; Index < HandleCount; Index++) {
      gBS->DisconnectController (HandleBuffer[Index], NULL, NULL);
    }

    for (Index = 0; Index < HandleCount; Index++) {
      gBS->ConnectController (HandleBuffer[Index], NULL, NULL, TRUE);
    }

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

  return EFI_SUCCESS;
}

/**
  Display the boot popup menu and allow user select boot item.

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

  @retval  EFI_SUCCESS          Boot from selected boot option, and return success from boot option
  @retval  EFI_NOT_FOUND        User select to enter setup or can not find boot option

**/
EFI_STATUS
EFIAPI
BootManagerMenuEntry (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_BOOT_MANAGER_LOAD_OPTION     *BootOption;
  UINTN                            BootOptionCount;
  EFI_STATUS                       Status;
  BOOT_MENU_POPUP_DATA             BootMenuData;
  UINTN                            Index;
  EFI_INPUT_KEY                    Key;
  BOOLEAN                          ExitApplication;
  UINTN                            SelectItem;
  EFI_BOOT_LOGO_PROTOCOL           *BootLogo;
  EFI_GRAPHICS_OUTPUT_PROTOCOL     *GraphicsOutput;
  EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL  *SimpleTextOut;
  UINTN                            BootTextColumn;
  UINTN                            BootTextRow;

  //
  // Set Logo status invalid when boot manager menu is launched
  //
  BootLogo = NULL;
  Status   = gBS->LocateProtocol (&gEfiBootLogoProtocolGuid, NULL, (VOID **)&BootLogo);
  if (!EFI_ERROR (Status) && (BootLogo != NULL)) {
    Status = BootLogo->SetBootLogo (BootLogo, NULL, 0, 0, 0, 0);
    ASSERT_EFI_ERROR (Status);
  }

  gBS->SetWatchdogTimer (0x0000, 0x0000, 0x0000, NULL);

  gStringPackHandle = HiiAddPackages (
                        &gEfiCallerIdGuid,
                        gImageHandle,
                        BootManagerMenuAppStrings,
                        NULL
                        );
  ASSERT (gStringPackHandle != NULL);

  //
  // Connect all prior to entering the platform setup menu.
  //
  EfiBootManagerConnectAll ();
  EfiBootManagerRefreshAllBootOption ();

  BootOption = EfiBootManagerGetLoadOptions (&BootOptionCount, LoadOptionTypeBoot);

  if (!mModeInitialized) {
    //
    // After the console is ready, get current video resolution
    // and text mode before launching setup at first time.
    //
    Status = gBS->HandleProtocol (
                    gST->ConsoleOutHandle,
                    &gEfiGraphicsOutputProtocolGuid,
                    (VOID **)&GraphicsOutput
                    );
    if (EFI_ERROR (Status)) {
      GraphicsOutput = NULL;
    }

    Status = gBS->HandleProtocol (
                    gST->ConsoleOutHandle,
                    &gEfiSimpleTextOutProtocolGuid,
                    (VOID **)&SimpleTextOut
                    );
    if (EFI_ERROR (Status)) {
      SimpleTextOut = NULL;
    }

    if (GraphicsOutput != NULL) {
      //
      // Get current video resolution and text mode.
      //
      mBootHorizontalResolution = GraphicsOutput->Mode->Info->HorizontalResolution;
      mBootVerticalResolution   = GraphicsOutput->Mode->Info->VerticalResolution;
    }

    if (SimpleTextOut != NULL) {
      Status = SimpleTextOut->QueryMode (
                                SimpleTextOut,
                                SimpleTextOut->Mode->Mode,
                                &BootTextColumn,
                                &BootTextRow
                                );
      mBootTextModeColumn = (UINT32)BootTextColumn;
      mBootTextModeRow    = (UINT32)BootTextRow;
    }

    //
    // Get user defined text mode for setup.
    //
    mSetupHorizontalResolution = PcdGet32 (PcdSetupVideoHorizontalResolution);
    mSetupVerticalResolution   = PcdGet32 (PcdSetupVideoVerticalResolution);
    mSetupTextModeColumn       = PcdGet32 (PcdSetupConOutColumn);
    mSetupTextModeRow          = PcdGet32 (PcdSetupConOutRow);
    mModeInitialized           = TRUE;
  }

  //
  // Set back to conventional setup resolution
  //
  BdsSetConsoleMode (TRUE);

  //
  // Initialize Boot menu data
  //
  Status = InitializeBootMenuData (BootOption, BootOptionCount, &BootMenuData);
  //
  // According to boot menu data to draw boot popup menu
  //
  DrawBootPopupMenu (&BootMenuData);

  //
  // check user input to determine want to re-draw or boot from user selected item
  //
  ExitApplication = FALSE;
  while (!ExitApplication) {
    gBS->WaitForEvent (1, &gST->ConIn->WaitForKey, &Index);
    Status = gST->ConIn->ReadKeyStroke (gST->ConIn, &Key);
    if (!EFI_ERROR (Status)) {
      switch (Key.UnicodeChar) {
        case CHAR_NULL:
          switch (Key.ScanCode) {
            case SCAN_UP:
              SelectItem = BootMenuData.SelectItem == 0 ? BootMenuData.ItemCount - 1 : BootMenuData.SelectItem - 1;
              BootMenuSelectItem (SelectItem, &BootMenuData);
              break;

            case SCAN_DOWN:
              SelectItem = BootMenuData.SelectItem == BootMenuData.ItemCount - 1 ? 0 : BootMenuData.SelectItem + 1;
              BootMenuSelectItem (SelectItem, &BootMenuData);
              break;

            case SCAN_ESC:
              gST->ConOut->ClearScreen (gST->ConOut);
              ExitApplication = TRUE;
              //
              // Set boot resolution for normal boot
              //
              BdsSetConsoleMode (FALSE);
              break;

            default:
              break;
          }

          break;

        case CHAR_CARRIAGE_RETURN:
          gST->ConOut->ClearScreen (gST->ConOut);
          //
          // Set boot resolution for normal boot
          //
          BdsSetConsoleMode (FALSE);
          BootFromSelectOption (BootOption, BootOptionCount, BootMenuData.SelectItem);
          //
          // Back to boot manager menu again, set back to setup resolution
          //
          BdsSetConsoleMode (TRUE);
          DrawBootPopupMenu (&BootMenuData);
          break;

        default:
          break;
      }
    }
  }

  EfiBootManagerFreeLoadOptions (BootOption, BootOptionCount);
  FreePool (BootMenuData.PtrTokens);

  HiiRemovePackages (gStringPackHandle);

  return Status;
}