/** @file
Dynamically update the pages.

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

**/

#include "BootMaintenanceManager.h"

/**
 Create the global UpdateData structure.

**/
VOID
CreateUpdateData (
  VOID
  )
{
  //
  // Init OpCode Handle and Allocate space for creation of Buffer
  //
  mStartOpCodeHandle = HiiAllocateOpCodeHandle ();
  ASSERT (mStartOpCodeHandle != NULL);

  mEndOpCodeHandle = HiiAllocateOpCodeHandle ();
  ASSERT (mEndOpCodeHandle != NULL);

  //
  // Create Hii Extend Label OpCode as the start opcode
  //
  mStartLabel               = (EFI_IFR_GUID_LABEL *)HiiCreateGuidOpCode (mStartOpCodeHandle, &gEfiIfrTianoGuid, NULL, sizeof (EFI_IFR_GUID_LABEL));
  mStartLabel->ExtendOpCode = EFI_IFR_EXTEND_OP_LABEL;

  //
  // Create Hii Extend Label OpCode as the end opcode
  //
  mEndLabel               = (EFI_IFR_GUID_LABEL *)HiiCreateGuidOpCode (mEndOpCodeHandle, &gEfiIfrTianoGuid, NULL, sizeof (EFI_IFR_GUID_LABEL));
  mEndLabel->ExtendOpCode = EFI_IFR_EXTEND_OP_LABEL;
  mEndLabel->Number       = LABEL_END;
}

/**
  Refresh the global UpdateData structure.

**/
VOID
RefreshUpdateData (
  VOID
  )
{
  //
  // Free current updated date
  //
  if (mStartOpCodeHandle != NULL) {
    HiiFreeOpCodeHandle (mStartOpCodeHandle);
  }

  //
  // Create new OpCode Handle
  //
  mStartOpCodeHandle = HiiAllocateOpCodeHandle ();

  //
  // Create Hii Extend Label OpCode as the start opcode
  //
  mStartLabel               = (EFI_IFR_GUID_LABEL *)HiiCreateGuidOpCode (mStartOpCodeHandle, &gEfiIfrTianoGuid, NULL, sizeof (EFI_IFR_GUID_LABEL));
  mStartLabel->ExtendOpCode = EFI_IFR_EXTEND_OP_LABEL;
}

/**
  Add a "Go back to main page" tag in front of the form when there are no
  "Apply changes" and "Discard changes" tags in the end of the form.

  @param CallbackData    The BMM context data.

**/
VOID
UpdatePageStart (
  IN BMM_CALLBACK_DATA  *CallbackData
  )
{
  RefreshUpdateData ();
  mStartLabel->Number = CallbackData->BmmCurrentPageId;

  if (!(CallbackData->BmmAskSaveOrNot)) {
    //
    // Add a "Go back to main page" tag in front of the form when there are no
    // "Apply changes" and "Discard changes" tags in the end of the form.
    //
    HiiCreateGotoOpCode (
      mStartOpCodeHandle,
      FORM_MAIN_ID,
      STRING_TOKEN (STR_FORM_GOTO_MAIN),
      STRING_TOKEN (STR_FORM_GOTO_MAIN),
      0,
      FORM_MAIN_ID
      );
  }
}

/**
  Create the "Apply changes" and "Discard changes" tags. And
  ensure user can return to the main page.

  @param CallbackData    The BMM context data.

**/
VOID
UpdatePageEnd (
  IN BMM_CALLBACK_DATA  *CallbackData
  )
{
  //
  // Create the "Apply changes" and "Discard changes" tags.
  //
  if (CallbackData->BmmAskSaveOrNot) {
    HiiCreateSubTitleOpCode (
      mStartOpCodeHandle,
      STRING_TOKEN (STR_NULL_STRING),
      0,
      0,
      0
      );

    HiiCreateActionOpCode (
      mStartOpCodeHandle,
      KEY_VALUE_SAVE_AND_EXIT,
      STRING_TOKEN (STR_SAVE_AND_EXIT),
      STRING_TOKEN (STR_NULL_STRING),
      EFI_IFR_FLAG_CALLBACK,
      0
      );
  }

  //
  // Ensure user can return to the main page.
  //
  HiiCreateActionOpCode (
    mStartOpCodeHandle,
    KEY_VALUE_NO_SAVE_AND_EXIT,
    STRING_TOKEN (STR_NO_SAVE_AND_EXIT),
    STRING_TOKEN (STR_NULL_STRING),
    EFI_IFR_FLAG_CALLBACK,
    0
    );

  HiiUpdateForm (
    CallbackData->BmmHiiHandle,
    &mBootMaintGuid,
    CallbackData->BmmCurrentPageId,
    mStartOpCodeHandle, // Label CallbackData->BmmCurrentPageId
    mEndOpCodeHandle    // LABEL_END
    );
}

/**
  Clean up the dynamic opcode at label and form specified by both LabelId.

  @param LabelId         It is both the Form ID and Label ID for opcode deletion.
  @param CallbackData    The BMM context data.

**/
VOID
CleanUpPage (
  IN UINT16             LabelId,
  IN BMM_CALLBACK_DATA  *CallbackData
  )
{
  RefreshUpdateData ();

  //
  // Remove all op-codes from dynamic page
  //
  mStartLabel->Number = LabelId;
  HiiUpdateForm (
    CallbackData->BmmHiiHandle,
    &mBootMaintGuid,
    LabelId,
    mStartOpCodeHandle, // Label LabelId
    mEndOpCodeHandle    // LABEL_END
    );
}

/**
  Create a list of Goto Opcode for all terminal devices logged
  by TerminaMenu. This list will be inserted to form FORM_CON_COM_SETUP_ID.

  @param CallbackData    The BMM context data.
**/
VOID
UpdateConCOMPage (
  IN BMM_CALLBACK_DATA  *CallbackData
  )
{
  BM_MENU_ENTRY  *NewMenuEntry;
  UINT16         Index;

  CallbackData->BmmAskSaveOrNot = TRUE;

  UpdatePageStart (CallbackData);

  for (Index = 0; Index < TerminalMenu.MenuNumber; Index++) {
    NewMenuEntry = BOpt_GetMenuEntry (&TerminalMenu, Index);

    HiiCreateGotoOpCode (
      mStartOpCodeHandle,
      FORM_CON_COM_SETUP_ID,
      NewMenuEntry->DisplayStringToken,
      STRING_TOKEN (STR_NULL_STRING),
      EFI_IFR_FLAG_CALLBACK,
      (UINT16)(TERMINAL_OPTION_OFFSET + Index)
      );
  }

  UpdatePageEnd (CallbackData);
}

/**
  Create a list of boot option from global BootOptionMenu. It
  allow user to delete the boot option.

  @param CallbackData    The BMM context data.

**/
VOID
UpdateBootDelPage (
  IN BMM_CALLBACK_DATA  *CallbackData
  )
{
  BM_MENU_ENTRY    *NewMenuEntry;
  BM_LOAD_CONTEXT  *NewLoadContext;
  UINT16           Index;

  CallbackData->BmmAskSaveOrNot = TRUE;

  UpdatePageStart (CallbackData);

  ASSERT (BootOptionMenu.MenuNumber <= (sizeof (CallbackData->BmmFakeNvData.BootOptionDel) / sizeof (CallbackData->BmmFakeNvData.BootOptionDel[0])));
  for (Index = 0; Index < BootOptionMenu.MenuNumber; Index++) {
    NewMenuEntry   = BOpt_GetMenuEntry (&BootOptionMenu, Index);
    NewLoadContext = (BM_LOAD_CONTEXT *)NewMenuEntry->VariableContext;
    if (NewLoadContext->IsLegacy) {
      continue;
    }

    NewLoadContext->Deleted = FALSE;

    if (CallbackData->BmmFakeNvData.BootOptionDel[Index] && !CallbackData->BmmFakeNvData.BootOptionDelMark[Index]) {
      //
      // CallbackData->BmmFakeNvData.BootOptionDel[Index] == TRUE means browser knows this boot option is selected
      // CallbackData->BmmFakeNvData.BootOptionDelMark[Index] = FALSE means BDS knows the selected boot option has
      // deleted, browser maintains old useless info. So clear this info here, and later update this info to browser
      // through HiiSetBrowserData function.
      //
      CallbackData->BmmFakeNvData.BootOptionDel[Index]    = FALSE;
      CallbackData->BmmOldFakeNVData.BootOptionDel[Index] = FALSE;
    }

    HiiCreateCheckBoxOpCode (
      mStartOpCodeHandle,
      (EFI_QUESTION_ID)(BOOT_OPTION_DEL_QUESTION_ID + Index),
      VARSTORE_ID_BOOT_MAINT,
      (UINT16)(BOOT_OPTION_DEL_VAR_OFFSET + Index),
      NewMenuEntry->DisplayStringToken,
      NewMenuEntry->HelpStringToken,
      EFI_IFR_FLAG_CALLBACK,
      0,
      NULL
      );
  }

  UpdatePageEnd (CallbackData);
}

/**
  Create a lit of driver option from global DriverMenu.

  @param CallbackData    The BMM context data.

**/
VOID
UpdateDrvAddHandlePage (
  IN BMM_CALLBACK_DATA  *CallbackData
  )
{
  BM_MENU_ENTRY  *NewMenuEntry;
  UINT16         Index;

  CallbackData->BmmAskSaveOrNot = FALSE;

  UpdatePageStart (CallbackData);

  for (Index = 0; Index < DriverMenu.MenuNumber; Index++) {
    NewMenuEntry = BOpt_GetMenuEntry (&DriverMenu, Index);

    HiiCreateGotoOpCode (
      mStartOpCodeHandle,
      FORM_DRV_ADD_HANDLE_DESC_ID,
      NewMenuEntry->DisplayStringToken,
      STRING_TOKEN (STR_NULL_STRING),
      EFI_IFR_FLAG_CALLBACK,
      (UINT16)(HANDLE_OPTION_OFFSET + Index)
      );
  }

  UpdatePageEnd (CallbackData);
}

/**
  Create a lit of driver option from global DriverOptionMenu. It
  allow user to delete the driver option.

  @param CallbackData    The BMM context data.

**/
VOID
UpdateDrvDelPage (
  IN BMM_CALLBACK_DATA  *CallbackData
  )
{
  BM_MENU_ENTRY    *NewMenuEntry;
  BM_LOAD_CONTEXT  *NewLoadContext;
  UINT16           Index;

  CallbackData->BmmAskSaveOrNot = TRUE;

  UpdatePageStart (CallbackData);

  ASSERT (DriverOptionMenu.MenuNumber <= (sizeof (CallbackData->BmmFakeNvData.DriverOptionDel) / sizeof (CallbackData->BmmFakeNvData.DriverOptionDel[0])));
  for (Index = 0; Index < DriverOptionMenu.MenuNumber; Index++) {
    NewMenuEntry = BOpt_GetMenuEntry (&DriverOptionMenu, Index);

    NewLoadContext          = (BM_LOAD_CONTEXT *)NewMenuEntry->VariableContext;
    NewLoadContext->Deleted = FALSE;

    if (CallbackData->BmmFakeNvData.DriverOptionDel[Index] && !CallbackData->BmmFakeNvData.DriverOptionDelMark[Index]) {
      //
      // CallbackData->BmmFakeNvData.BootOptionDel[Index] == TRUE means browser knows this boot option is selected
      // CallbackData->BmmFakeNvData.BootOptionDelMark[Index] = FALSE means BDS knows the selected boot option has
      // deleted, browser maintains old useless info. So clear this info here, and later update this info to browser
      // through HiiSetBrowserData function.
      //
      CallbackData->BmmFakeNvData.DriverOptionDel[Index]    = FALSE;
      CallbackData->BmmOldFakeNVData.DriverOptionDel[Index] = FALSE;
    }

    HiiCreateCheckBoxOpCode (
      mStartOpCodeHandle,
      (EFI_QUESTION_ID)(DRIVER_OPTION_DEL_QUESTION_ID + Index),
      VARSTORE_ID_BOOT_MAINT,
      (UINT16)(DRIVER_OPTION_DEL_VAR_OFFSET + Index),
      NewMenuEntry->DisplayStringToken,
      NewMenuEntry->HelpStringToken,
      EFI_IFR_FLAG_CALLBACK,
      0,
      NULL
      );
  }

  UpdatePageEnd (CallbackData);
}

/**
  Prepare the page to allow user to add description for
  a Driver Option.

  @param CallbackData    The BMM context data.

**/
VOID
UpdateDriverAddHandleDescPage (
  IN BMM_CALLBACK_DATA  *CallbackData
  )
{
  BM_MENU_ENTRY  *NewMenuEntry;

  CallbackData->BmmFakeNvData.DriverAddActive         = 0x01;
  CallbackData->BmmFakeNvData.DriverAddForceReconnect = 0x00;
  CallbackData->BmmAskSaveOrNot                       = TRUE;
  NewMenuEntry                                        = CallbackData->MenuEntry;

  UpdatePageStart (CallbackData);

  HiiCreateSubTitleOpCode (
    mStartOpCodeHandle,
    NewMenuEntry->DisplayStringToken,
    0,
    0,
    0
    );

  HiiCreateStringOpCode (
    mStartOpCodeHandle,
    (EFI_QUESTION_ID)DRV_ADD_HANDLE_DESC_QUESTION_ID,
    VARSTORE_ID_BOOT_MAINT,
    DRV_ADD_HANDLE_DESC_VAR_OFFSET,
    STRING_TOKEN (STR_LOAD_OPTION_DESC),
    STRING_TOKEN (STR_NULL_STRING),
    0,
    0,
    6,
    75,
    NULL
    );

  HiiCreateCheckBoxOpCode (
    mStartOpCodeHandle,
    (EFI_QUESTION_ID)DRV_ADD_RECON_QUESTION_ID,
    VARSTORE_ID_BOOT_MAINT,
    DRV_ADD_RECON_VAR_OFFSET,
    STRING_TOKEN (STR_LOAD_OPTION_FORCE_RECON),
    STRING_TOKEN (STR_LOAD_OPTION_FORCE_RECON),
    0,
    0,
    NULL
    );

  HiiCreateStringOpCode (
    mStartOpCodeHandle,
    (EFI_QUESTION_ID)DRIVER_ADD_OPTION_QUESTION_ID,
    VARSTORE_ID_BOOT_MAINT,
    DRIVER_ADD_OPTION_VAR_OFFSET,
    STRING_TOKEN (STR_OPTIONAL_DATA),
    STRING_TOKEN (STR_NULL_STRING),
    0,
    0,
    6,
    75,
    NULL
    );

  UpdatePageEnd (CallbackData);
}

/**
  Update console page.

  @param UpdatePageId    The form ID to be updated.
  @param ConsoleMenu     The console menu list.
  @param CallbackData    The BMM context data.

**/
VOID
UpdateConsolePage (
  IN UINT16             UpdatePageId,
  IN BM_MENU_OPTION     *ConsoleMenu,
  IN BMM_CALLBACK_DATA  *CallbackData
  )
{
  BM_MENU_ENTRY        *NewMenuEntry;
  BM_CONSOLE_CONTEXT   *NewConsoleContext;
  BM_TERMINAL_CONTEXT  *NewTerminalContext;
  UINT16               Index;
  UINT16               Index2;
  UINT8                CheckFlags;
  UINT8                *ConsoleCheck;
  EFI_QUESTION_ID      QuestionIdBase;
  UINT16               VariableOffsetBase;

  CallbackData->BmmAskSaveOrNot = TRUE;

  UpdatePageStart (CallbackData);

  ConsoleCheck       = NULL;
  QuestionIdBase     = 0;
  VariableOffsetBase = 0;

  switch (UpdatePageId) {
    case FORM_CON_IN_ID:
      ConsoleCheck       = &CallbackData->BmmFakeNvData.ConsoleInCheck[0];
      QuestionIdBase     = CON_IN_DEVICE_QUESTION_ID;
      VariableOffsetBase = CON_IN_DEVICE_VAR_OFFSET;
      break;

    case FORM_CON_OUT_ID:
      ConsoleCheck       = &CallbackData->BmmFakeNvData.ConsoleOutCheck[0];
      QuestionIdBase     = CON_OUT_DEVICE_QUESTION_ID;
      VariableOffsetBase = CON_OUT_DEVICE_VAR_OFFSET;
      break;

    case FORM_CON_ERR_ID:
      ConsoleCheck       = &CallbackData->BmmFakeNvData.ConsoleErrCheck[0];
      QuestionIdBase     = CON_ERR_DEVICE_QUESTION_ID;
      VariableOffsetBase = CON_ERR_DEVICE_VAR_OFFSET;
      break;
  }

  ASSERT (ConsoleCheck != NULL);

  for (Index = 0; ((Index < ConsoleMenu->MenuNumber) && \
                   (Index < MAX_MENU_NUMBER)); Index++)
  {
    CheckFlags        = 0;
    NewMenuEntry      = BOpt_GetMenuEntry (ConsoleMenu, Index);
    NewConsoleContext = (BM_CONSOLE_CONTEXT *)NewMenuEntry->VariableContext;
    if (NewConsoleContext->IsActive) {
      CheckFlags         |= EFI_IFR_CHECKBOX_DEFAULT;
      ConsoleCheck[Index] = TRUE;
    } else {
      ConsoleCheck[Index] = FALSE;
    }

    HiiCreateCheckBoxOpCode (
      mStartOpCodeHandle,
      (EFI_QUESTION_ID)(QuestionIdBase + Index),
      VARSTORE_ID_BOOT_MAINT,
      (UINT16)(VariableOffsetBase + Index),
      NewMenuEntry->DisplayStringToken,
      NewMenuEntry->HelpStringToken,
      EFI_IFR_FLAG_CALLBACK,
      CheckFlags,
      NULL
      );
  }

  for (Index2 = 0; ((Index2 < TerminalMenu.MenuNumber) && \
                    (Index2 < MAX_MENU_NUMBER)); Index2++)
  {
    CheckFlags         = 0;
    NewMenuEntry       = BOpt_GetMenuEntry (&TerminalMenu, Index2);
    NewTerminalContext = (BM_TERMINAL_CONTEXT *)NewMenuEntry->VariableContext;

    ASSERT (Index < MAX_MENU_NUMBER);
    if (((NewTerminalContext->IsConIn != 0) && (UpdatePageId == FORM_CON_IN_ID)) ||
        ((NewTerminalContext->IsConOut != 0) && (UpdatePageId == FORM_CON_OUT_ID)) ||
        ((NewTerminalContext->IsStdErr != 0) && (UpdatePageId == FORM_CON_ERR_ID))
        )
    {
      CheckFlags         |= EFI_IFR_CHECKBOX_DEFAULT;
      ConsoleCheck[Index] = TRUE;
    } else {
      ConsoleCheck[Index] = FALSE;
    }

    HiiCreateCheckBoxOpCode (
      mStartOpCodeHandle,
      (EFI_QUESTION_ID)(QuestionIdBase + Index),
      VARSTORE_ID_BOOT_MAINT,
      (UINT16)(VariableOffsetBase + Index),
      NewMenuEntry->DisplayStringToken,
      NewMenuEntry->HelpStringToken,
      EFI_IFR_FLAG_CALLBACK,
      CheckFlags,
      NULL
      );

    Index++;
  }

  UpdatePageEnd (CallbackData);
}

/**
  Update the page's NV Map if user has changed the order
  a list. This list can be Boot Order or Driver Order.

  @param UpdatePageId    The form ID to be updated.
  @param OptionMenu      The new list.
  @param CallbackData    The BMM context data.

**/
VOID
UpdateOrderPage (
  IN UINT16             UpdatePageId,
  IN BM_MENU_OPTION     *OptionMenu,
  IN BMM_CALLBACK_DATA  *CallbackData
  )
{
  BM_MENU_ENTRY    *NewMenuEntry;
  UINT16           Index;
  UINT16           OptionIndex;
  VOID             *OptionsOpCodeHandle;
  BOOLEAN          BootOptionFound;
  UINT32           *OptionOrder;
  EFI_QUESTION_ID  QuestionId;
  UINT16           VarOffset;

  CallbackData->BmmAskSaveOrNot = TRUE;
  UpdatePageStart (CallbackData);

  OptionOrder = NULL;
  QuestionId  = 0;
  VarOffset   = 0;
  switch (UpdatePageId) {
    case FORM_BOOT_CHG_ID:
      //
      // If the BootOptionOrder in the BmmFakeNvData are same with the date in the BmmOldFakeNVData,
      // means all Boot Options has been save in BootOptionMenu, we can get the date from the menu.
      // else means browser maintains some uncommitted date which are not saved in BootOptionMenu,
      // so we should not get the data from BootOptionMenu to show it.
      //
      if (CompareMem (CallbackData->BmmFakeNvData.BootOptionOrder, CallbackData->BmmOldFakeNVData.BootOptionOrder, sizeof (CallbackData->BmmFakeNvData.BootOptionOrder)) == 0) {
        GetBootOrder (CallbackData);
      }

      OptionOrder = CallbackData->BmmFakeNvData.BootOptionOrder;
      QuestionId  = BOOT_OPTION_ORDER_QUESTION_ID;
      VarOffset   = BOOT_OPTION_ORDER_VAR_OFFSET;
      break;

    case FORM_DRV_CHG_ID:
      //
      // If the DriverOptionOrder in the BmmFakeNvData are same with the date in the BmmOldFakeNVData,
      // means all Driver Options has been save in DriverOptionMenu, we can get the DriverOptionOrder from the menu.
      // else means browser maintains some uncommitted date which are not saved in DriverOptionMenu,
      // so we should not get the data from DriverOptionMenu to show it.
      //
      if (CompareMem (CallbackData->BmmFakeNvData.DriverOptionOrder, CallbackData->BmmOldFakeNVData.DriverOptionOrder, sizeof (CallbackData->BmmFakeNvData.DriverOptionOrder)) == 0) {
        GetDriverOrder (CallbackData);
      }

      OptionOrder = CallbackData->BmmFakeNvData.DriverOptionOrder;
      QuestionId  = DRIVER_OPTION_ORDER_QUESTION_ID;
      VarOffset   = DRIVER_OPTION_ORDER_VAR_OFFSET;
      break;
  }

  ASSERT (OptionOrder != NULL);

  OptionsOpCodeHandle = HiiAllocateOpCodeHandle ();
  ASSERT (OptionsOpCodeHandle != NULL);

  NewMenuEntry = NULL;
  for (OptionIndex = 0; (OptionOrder[OptionIndex] != 0 && OptionIndex < MAX_MENU_NUMBER); OptionIndex++) {
    BootOptionFound = FALSE;
    for (Index = 0; Index < OptionMenu->MenuNumber; Index++) {
      NewMenuEntry = BOpt_GetMenuEntry (OptionMenu, Index);
      if ((UINT32)(NewMenuEntry->OptionNumber + 1) == OptionOrder[OptionIndex]) {
        BootOptionFound = TRUE;
        break;
      }
    }

    if (BootOptionFound) {
      HiiCreateOneOfOptionOpCode (
        OptionsOpCodeHandle,
        NewMenuEntry->DisplayStringToken,
        0,
        EFI_IFR_TYPE_NUM_SIZE_32,
        OptionOrder[OptionIndex]
        );
    }
  }

  if (OptionMenu->MenuNumber > 0) {
    HiiCreateOrderedListOpCode (
      mStartOpCodeHandle,                          // Container for dynamic created opcodes
      QuestionId,                                  // Question ID
      VARSTORE_ID_BOOT_MAINT,                      // VarStore ID
      VarOffset,                                   // Offset in Buffer Storage
      STRING_TOKEN (STR_CHANGE_ORDER),             // Question prompt text
      STRING_TOKEN (STR_CHANGE_ORDER),             // Question help text
      0,                                           // Question flag
      0,                                           // Ordered list flag, e.g. EFI_IFR_UNIQUE_SET
      EFI_IFR_TYPE_NUM_SIZE_32,                    // Data type of Question value
      100,                                         // Maximum container
      OptionsOpCodeHandle,                         // Option Opcode list
      NULL                                         // Default Opcode is NULL
      );
  }

  HiiFreeOpCodeHandle (OptionsOpCodeHandle);

  UpdatePageEnd (CallbackData);
}

/**
  Refresh the text mode page.

  @param CallbackData    The BMM context data.

**/
VOID
UpdateConModePage (
  IN BMM_CALLBACK_DATA  *CallbackData
  )
{
  UINTN                            Mode;
  UINTN                            Index;
  UINTN                            Col;
  UINTN                            Row;
  CHAR16                           ModeString[50];
  CHAR16                           *PStr;
  UINTN                            MaxMode;
  UINTN                            ValidMode;
  EFI_STRING_ID                    *ModeToken;
  EFI_STATUS                       Status;
  VOID                             *OptionsOpCodeHandle;
  EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL  *ConOut;

  ConOut    = gST->ConOut;
  Index     = 0;
  ValidMode = 0;
  MaxMode   = (UINTN)(ConOut->Mode->MaxMode);

  CallbackData->BmmAskSaveOrNot = TRUE;

  UpdatePageStart (CallbackData);

  //
  // Check valid mode
  //
  for (Mode = 0; Mode < MaxMode; Mode++) {
    Status = ConOut->QueryMode (ConOut, Mode, &Col, &Row);
    if (EFI_ERROR (Status)) {
      continue;
    }

    ValidMode++;
  }

  if (ValidMode == 0) {
    return;
  }

  OptionsOpCodeHandle = HiiAllocateOpCodeHandle ();
  ASSERT (OptionsOpCodeHandle != NULL);

  ModeToken = AllocateZeroPool (sizeof (EFI_STRING_ID) * ValidMode);
  ASSERT (ModeToken != NULL);

  //
  // Determin which mode should be the first entry in menu
  //
  GetConsoleOutMode (CallbackData);

  //
  // Build text mode options
  //
  for (Mode = 0; Mode < MaxMode; Mode++) {
    Status = ConOut->QueryMode (ConOut, Mode, &Col, &Row);
    if (EFI_ERROR (Status)) {
      continue;
    }

    //
    // Build mode string Column x Row
    //
    UnicodeValueToStringS (ModeString, sizeof (ModeString), 0, Col, 0);
    PStr = &ModeString[0];
    StrnCatS (PStr, ARRAY_SIZE (ModeString), L" x ", StrLen (L" x ") + 1);
    PStr = PStr + StrLen (PStr);
    UnicodeValueToStringS (
      PStr,
      sizeof (ModeString) - ((UINTN)PStr - (UINTN)&ModeString[0]),
      0,
      Row,
      0
      );

    ModeToken[Index] = HiiSetString (CallbackData->BmmHiiHandle, 0, ModeString, NULL);

    if (Mode == CallbackData->BmmFakeNvData.ConsoleOutMode) {
      HiiCreateOneOfOptionOpCode (
        OptionsOpCodeHandle,
        ModeToken[Index],
        EFI_IFR_OPTION_DEFAULT,
        EFI_IFR_TYPE_NUM_SIZE_16,
        (UINT16)Mode
        );
    } else {
      HiiCreateOneOfOptionOpCode (
        OptionsOpCodeHandle,
        ModeToken[Index],
        0,
        EFI_IFR_TYPE_NUM_SIZE_16,
        (UINT16)Mode
        );
    }

    Index++;
  }

  HiiCreateOneOfOpCode (
    mStartOpCodeHandle,
    (EFI_QUESTION_ID)CON_MODE_QUESTION_ID,
    VARSTORE_ID_BOOT_MAINT,
    CON_MODE_VAR_OFFSET,
    STRING_TOKEN (STR_CON_MODE_SETUP),
    STRING_TOKEN (STR_CON_MODE_SETUP),
    EFI_IFR_FLAG_RESET_REQUIRED,
    EFI_IFR_NUMERIC_SIZE_2,
    OptionsOpCodeHandle,
    NULL
    );

  HiiFreeOpCodeHandle (OptionsOpCodeHandle);
  FreePool (ModeToken);

  UpdatePageEnd (CallbackData);
}

/**
 Create the dynamic page which allows user to set the property such as Baud Rate, Data Bits,
 Parity, Stop Bits, Terminal Type.

 @param CallbackData    The BMM context data.

**/
VOID
UpdateTerminalPage (
  IN BMM_CALLBACK_DATA  *CallbackData
  )
{
  UINT8          Index;
  UINT8          CheckFlags;
  BM_MENU_ENTRY  *NewMenuEntry;
  VOID           *OptionsOpCodeHandle;
  UINTN          CurrentTerminal;

  CallbackData->BmmAskSaveOrNot = TRUE;

  UpdatePageStart (CallbackData);

  CurrentTerminal = CallbackData->CurrentTerminal;
  NewMenuEntry    = BOpt_GetMenuEntry (
                      &TerminalMenu,
                      CurrentTerminal
                      );

  if (NewMenuEntry == NULL) {
    return;
  }

  OptionsOpCodeHandle = HiiAllocateOpCodeHandle ();
  ASSERT (OptionsOpCodeHandle != NULL);

  for (Index = 0; Index < sizeof (BaudRateList) / sizeof (BaudRateList[0]); Index++) {
    CheckFlags = 0;
    if (BaudRateList[Index].Value == 115200) {
      CheckFlags |= EFI_IFR_OPTION_DEFAULT;
    }

    HiiCreateOneOfOptionOpCode (
      OptionsOpCodeHandle,
      BaudRateList[Index].StringToken,
      CheckFlags,
      EFI_IFR_TYPE_NUM_SIZE_8,
      Index
      );
  }

  HiiCreateOneOfOpCode (
    mStartOpCodeHandle,
    (EFI_QUESTION_ID)(COM_BAUD_RATE_QUESTION_ID + CurrentTerminal),
    VARSTORE_ID_BOOT_MAINT,
    (UINT16)(COM_BAUD_RATE_VAR_OFFSET + CurrentTerminal),
    STRING_TOKEN (STR_COM_BAUD_RATE),
    STRING_TOKEN (STR_COM_BAUD_RATE),
    EFI_IFR_FLAG_CALLBACK,
    EFI_IFR_NUMERIC_SIZE_1,
    OptionsOpCodeHandle,
    NULL
    );

  HiiFreeOpCodeHandle (OptionsOpCodeHandle);
  OptionsOpCodeHandle = HiiAllocateOpCodeHandle ();
  ASSERT (OptionsOpCodeHandle != NULL);

  for (Index = 0; Index < ARRAY_SIZE (DataBitsList); Index++) {
    CheckFlags = 0;

    if (DataBitsList[Index].Value == 8) {
      CheckFlags |= EFI_IFR_OPTION_DEFAULT;
    }

    HiiCreateOneOfOptionOpCode (
      OptionsOpCodeHandle,
      DataBitsList[Index].StringToken,
      CheckFlags,
      EFI_IFR_TYPE_NUM_SIZE_8,
      Index
      );
  }

  HiiCreateOneOfOpCode (
    mStartOpCodeHandle,
    (EFI_QUESTION_ID)(COM_DATA_RATE_QUESTION_ID + CurrentTerminal),
    VARSTORE_ID_BOOT_MAINT,
    (UINT16)(COM_DATA_RATE_VAR_OFFSET + CurrentTerminal),
    STRING_TOKEN (STR_COM_DATA_BITS),
    STRING_TOKEN (STR_COM_DATA_BITS),
    EFI_IFR_FLAG_CALLBACK,
    EFI_IFR_NUMERIC_SIZE_1,
    OptionsOpCodeHandle,
    NULL
    );

  HiiFreeOpCodeHandle (OptionsOpCodeHandle);
  OptionsOpCodeHandle = HiiAllocateOpCodeHandle ();
  ASSERT (OptionsOpCodeHandle != NULL);

  for (Index = 0; Index < ARRAY_SIZE (ParityList); Index++) {
    CheckFlags = 0;
    if (ParityList[Index].Value ==  NoParity) {
      CheckFlags |= EFI_IFR_OPTION_DEFAULT;
    }

    HiiCreateOneOfOptionOpCode (
      OptionsOpCodeHandle,
      ParityList[Index].StringToken,
      CheckFlags,
      EFI_IFR_TYPE_NUM_SIZE_8,
      Index
      );
  }

  HiiCreateOneOfOpCode (
    mStartOpCodeHandle,
    (EFI_QUESTION_ID)(COM_PARITY_QUESTION_ID + CurrentTerminal),
    VARSTORE_ID_BOOT_MAINT,
    (UINT16)(COM_PARITY_VAR_OFFSET + CurrentTerminal),
    STRING_TOKEN (STR_COM_PARITY),
    STRING_TOKEN (STR_COM_PARITY),
    EFI_IFR_FLAG_CALLBACK,
    EFI_IFR_NUMERIC_SIZE_1,
    OptionsOpCodeHandle,
    NULL
    );

  HiiFreeOpCodeHandle (OptionsOpCodeHandle);
  OptionsOpCodeHandle = HiiAllocateOpCodeHandle ();
  ASSERT (OptionsOpCodeHandle != NULL);

  for (Index = 0; Index < ARRAY_SIZE (StopBitsList); Index++) {
    CheckFlags = 0;
    if (StopBitsList[Index].Value == OneStopBit) {
      CheckFlags |= EFI_IFR_OPTION_DEFAULT;
    }

    HiiCreateOneOfOptionOpCode (
      OptionsOpCodeHandle,
      StopBitsList[Index].StringToken,
      CheckFlags,
      EFI_IFR_TYPE_NUM_SIZE_8,
      Index
      );
  }

  HiiCreateOneOfOpCode (
    mStartOpCodeHandle,
    (EFI_QUESTION_ID)(COM_STOP_BITS_QUESTION_ID + CurrentTerminal),
    VARSTORE_ID_BOOT_MAINT,
    (UINT16)(COM_STOP_BITS_VAR_OFFSET + CurrentTerminal),
    STRING_TOKEN (STR_COM_STOP_BITS),
    STRING_TOKEN (STR_COM_STOP_BITS),
    EFI_IFR_FLAG_CALLBACK,
    EFI_IFR_NUMERIC_SIZE_1,
    OptionsOpCodeHandle,
    NULL
    );

  HiiFreeOpCodeHandle (OptionsOpCodeHandle);
  OptionsOpCodeHandle = HiiAllocateOpCodeHandle ();
  ASSERT (OptionsOpCodeHandle != NULL);

  for (Index = 0; Index < ARRAY_SIZE (TerminalType); Index++) {
    CheckFlags = 0;
    if (Index == 0) {
      CheckFlags |= EFI_IFR_OPTION_DEFAULT;
    }

    HiiCreateOneOfOptionOpCode (
      OptionsOpCodeHandle,
      (EFI_STRING_ID)TerminalType[Index],
      CheckFlags,
      EFI_IFR_TYPE_NUM_SIZE_8,
      Index
      );
  }

  HiiCreateOneOfOpCode (
    mStartOpCodeHandle,
    (EFI_QUESTION_ID)(COM_TERMINAL_QUESTION_ID + CurrentTerminal),
    VARSTORE_ID_BOOT_MAINT,
    (UINT16)(COM_TERMINAL_VAR_OFFSET + CurrentTerminal),
    STRING_TOKEN (STR_COM_TERMI_TYPE),
    STRING_TOKEN (STR_COM_TERMI_TYPE),
    EFI_IFR_FLAG_CALLBACK,
    EFI_IFR_NUMERIC_SIZE_1,
    OptionsOpCodeHandle,
    NULL
    );

  HiiFreeOpCodeHandle (OptionsOpCodeHandle);
  OptionsOpCodeHandle = HiiAllocateOpCodeHandle ();
  ASSERT (OptionsOpCodeHandle != NULL);

  for (Index = 0; Index < ARRAY_SIZE (mFlowControlType); Index++) {
    CheckFlags = 0;
    if (Index == 0) {
      CheckFlags |= EFI_IFR_OPTION_DEFAULT;
    }

    HiiCreateOneOfOptionOpCode (
      OptionsOpCodeHandle,
      (EFI_STRING_ID)mFlowControlType[Index],
      CheckFlags,
      EFI_IFR_TYPE_NUM_SIZE_8,
      mFlowControlValue[Index]
      );
  }

  HiiCreateOneOfOpCode (
    mStartOpCodeHandle,
    (EFI_QUESTION_ID)(COM_FLOWCONTROL_QUESTION_ID + CurrentTerminal),
    VARSTORE_ID_BOOT_MAINT,
    (UINT16)(COM_FLOWCONTROL_VAR_OFFSET + CurrentTerminal),
    STRING_TOKEN (STR_COM_FLOW_CONTROL),
    STRING_TOKEN (STR_COM_FLOW_CONTROL),
    EFI_IFR_FLAG_CALLBACK,
    EFI_IFR_NUMERIC_SIZE_1,
    OptionsOpCodeHandle,
    NULL
    );

  HiiFreeOpCodeHandle (OptionsOpCodeHandle);

  UpdatePageEnd (CallbackData);
}

/**
Update add boot/driver option page.

@param CallbackData    The BMM context data.
@param FormId             The form ID to be updated.
@param DevicePath       Device path.

**/
VOID
UpdateOptionPage (
  IN   BMM_CALLBACK_DATA         *CallbackData,
  IN   EFI_FORM_ID               FormId,
  IN   EFI_DEVICE_PATH_PROTOCOL  *DevicePath
  )
{
  CHAR16         *String;
  EFI_STRING_ID  StringToken;

  String = NULL;

  if (DevicePath != NULL) {
    String = ExtractFileNameFromDevicePath (DevicePath);
  }

  if (String == NULL) {
    String = HiiGetString (CallbackData->BmmHiiHandle, STRING_TOKEN (STR_NULL_STRING), NULL);
    ASSERT (String != NULL);
  }

  StringToken = HiiSetString (CallbackData->BmmHiiHandle, 0, String, NULL);
  FreePool (String);

  if (FormId == FORM_BOOT_ADD_ID) {
    if (!CallbackData->BmmFakeNvData.BootOptionChanged) {
      ZeroMem (CallbackData->BmmFakeNvData.BootOptionalData, sizeof (CallbackData->BmmFakeNvData.BootOptionalData));
      ZeroMem (CallbackData->BmmFakeNvData.BootDescriptionData, sizeof (CallbackData->BmmFakeNvData.BootDescriptionData));
      ZeroMem (CallbackData->BmmOldFakeNVData.BootOptionalData, sizeof (CallbackData->BmmOldFakeNVData.BootOptionalData));
      ZeroMem (CallbackData->BmmOldFakeNVData.BootDescriptionData, sizeof (CallbackData->BmmOldFakeNVData.BootDescriptionData));
    }
  } else if (FormId == FORM_DRV_ADD_FILE_ID) {
    if (!CallbackData->BmmFakeNvData.DriverOptionChanged) {
      ZeroMem (CallbackData->BmmFakeNvData.DriverOptionalData, sizeof (CallbackData->BmmFakeNvData.DriverOptionalData));
      ZeroMem (CallbackData->BmmFakeNvData.DriverDescriptionData, sizeof (CallbackData->BmmFakeNvData.DriverDescriptionData));
      ZeroMem (CallbackData->BmmOldFakeNVData.DriverOptionalData, sizeof (CallbackData->BmmOldFakeNVData.DriverOptionalData));
      ZeroMem (CallbackData->BmmOldFakeNVData.DriverDescriptionData, sizeof (CallbackData->BmmOldFakeNVData.DriverDescriptionData));
    }
  }

  RefreshUpdateData ();
  mStartLabel->Number = FormId;

  HiiCreateSubTitleOpCode (
    mStartOpCodeHandle,
    StringToken,
    0,
    0,
    0
    );

  HiiUpdateForm (
    CallbackData->BmmHiiHandle,
    &mBootMaintGuid,
    FormId,
    mStartOpCodeHandle, // Label FormId
    mEndOpCodeHandle    // LABEL_END
    );
}

/**
  Dispatch the correct update page function to call based on
  the UpdatePageId.

  @param UpdatePageId    The form ID.
  @param CallbackData    The BMM context data.

**/
VOID
UpdatePageBody (
  IN UINT16             UpdatePageId,
  IN BMM_CALLBACK_DATA  *CallbackData
  )
{
  CleanUpPage (UpdatePageId, CallbackData);
  switch (UpdatePageId) {
    case FORM_CON_IN_ID:
      UpdateConsolePage (UpdatePageId, &ConsoleInpMenu, CallbackData);
      break;

    case FORM_CON_OUT_ID:
      UpdateConsolePage (UpdatePageId, &ConsoleOutMenu, CallbackData);
      break;

    case FORM_CON_ERR_ID:
      UpdateConsolePage (UpdatePageId, &ConsoleErrMenu, CallbackData);
      break;

    case FORM_BOOT_CHG_ID:
      UpdateOrderPage (UpdatePageId, &BootOptionMenu, CallbackData);
      break;

    case FORM_DRV_CHG_ID:
      UpdateOrderPage (UpdatePageId, &DriverOptionMenu, CallbackData);
      break;

    default:
      break;
  }
}

/**
  Dispatch the display to the next page based on NewPageId.

  @param Private         The BMM context data.
  @param NewPageId       The original page ID.

**/
VOID
UpdatePageId (
  BMM_CALLBACK_DATA  *Private,
  UINT16             NewPageId
  )
{
  if ((NewPageId < FILE_OPTION_OFFSET) && (NewPageId >= HANDLE_OPTION_OFFSET)) {
    //
    // If we select a handle to add driver option, advance to the add handle description page.
    //
    NewPageId = FORM_DRV_ADD_HANDLE_DESC_ID;
  } else if ((NewPageId == KEY_VALUE_SAVE_AND_EXIT) || (NewPageId == KEY_VALUE_NO_SAVE_AND_EXIT)) {
    //
    // Return to main page after "Save Changes" or "Discard Changes".
    //
    NewPageId = FORM_MAIN_ID;
  } else if ((NewPageId >= TERMINAL_OPTION_OFFSET) && (NewPageId < CONSOLE_OPTION_OFFSET)) {
    NewPageId = FORM_CON_COM_SETUP_ID;
  }

  if ((NewPageId > 0) && (NewPageId < MAXIMUM_FORM_ID)) {
    Private->BmmPreviousPageId = Private->BmmCurrentPageId;
    Private->BmmCurrentPageId  = NewPageId;
  }
}
