/**
  Show all registered HotKey help strings on bottom Rows.

  @param FormData          The curent input form data info.
  @param SetState          Set HotKey or Clear HotKey

**/
VOID
PrintHotKeyHelpString (
  IN FORM_DISPLAY_ENGINE_FORM      *FormData,
  IN BOOLEAN                       SetState
  )
{
  UINTN                  CurrentCol;
  UINTN                  CurrentRow;
  UINTN                  BottomRowOfHotKeyHelp;
  UINTN                  ColumnIndexWidth;
  UINTN                  ColumnWidth;
  UINTN                  ColumnIndex;
  UINTN                  Index;
  EFI_SCREEN_DESCRIPTOR  LocalScreen;
  LIST_ENTRY             *Link;
  BROWSER_HOT_KEY        *HotKey;
  CHAR16                 BakChar;
  CHAR16                 *ColumnStr;

  CopyMem (&LocalScreen, &gScreenDimensions, sizeof (EFI_SCREEN_DESCRIPTOR));
  ColumnWidth            = (LocalScreen.RightColumn - LocalScreen.LeftColumn) / 3;
  BottomRowOfHotKeyHelp  = LocalScreen.BottomRow - STATUS_BAR_HEIGHT - 3;
  ColumnStr              = gLibEmptyString;

  //
  // Calculate total number of Register HotKeys. 
  //
  Index = 0;
  Link  = GetFirstNode (&FormData->HotKeyListHead);
  while (!IsNull (&FormData->HotKeyListHead, Link)) {
    HotKey = BROWSER_HOT_KEY_FROM_LINK (Link);
    //
    // Calculate help information Column and Row.
    //
    ColumnIndex = Index % 3;
    if (ColumnIndex == 0) {
      CurrentCol       = LocalScreen.LeftColumn + 2 * ColumnWidth;
      ColumnIndexWidth = ColumnWidth - 1;
    } else if (ColumnIndex == 1) {
      CurrentCol       = LocalScreen.LeftColumn + ColumnWidth;
      ColumnIndexWidth = ColumnWidth;
    } else {
      CurrentCol       = LocalScreen.LeftColumn + 2;
      ColumnIndexWidth = ColumnWidth - 2;
    }
    CurrentRow = BottomRowOfHotKeyHelp - Index / 3;

    //
    // Help string can't exceed ColumnWidth. One Row will show three Help information. 
    //
    BakChar = L'\0';
    if (StrLen (HotKey->HelpString) > ColumnIndexWidth) {
      BakChar = HotKey->HelpString[ColumnIndexWidth];
      HotKey->HelpString[ColumnIndexWidth] = L'\0';
    }

    //
    // Print HotKey help string on bottom Row.
    //
    if (SetState) {
      ColumnStr = HotKey->HelpString;
    }
    PrintStringAtWithWidth (CurrentCol, CurrentRow, ColumnStr, ColumnIndexWidth);

    if (BakChar != L'\0') {
      HotKey->HelpString[ColumnIndexWidth] = BakChar;
    }
    //
    // Get Next Hot Key.
    //
    Link = GetNextNode (&FormData->HotKeyListHead, Link);
    Index ++;
  }
  
  if (SetState) {
    //
    // Clear KeyHelp
    //
    CurrentRow  = BottomRowOfHotKeyHelp - Index / 3;
    ColumnIndex = Index % 3;
    if (ColumnIndex == 0) {
      CurrentCol       = LocalScreen.LeftColumn + 2 * ColumnWidth;
      ColumnIndexWidth = ColumnWidth - 1;
      ColumnIndex ++;
      PrintStringAtWithWidth (CurrentCol, CurrentRow, gLibEmptyString, ColumnIndexWidth);
    }
    if (ColumnIndex == 1) {
      CurrentCol       = LocalScreen.LeftColumn + ColumnWidth;
      ColumnIndexWidth = ColumnWidth;
      PrintStringAtWithWidth (CurrentCol, CurrentRow, gLibEmptyString, ColumnIndexWidth);
    }
  }
  
  return;
}
/**
  This function updates customized key panel's help information.
  The library will prepare those Strings for the basic key, ESC, Enter, Up/Down/Left/Right, +/-.
  and arrange them in Footer panel.
  
  @param[in]  FormData       Form Data to be shown in Page. FormData has the highlighted statement. 
  @param[in]  Statement      The statement current selected.
  @param[in]  Selected       Whether or not a tag be selected. TRUE means Enter has hit this question.
**/
VOID
EFIAPI
RefreshKeyHelp (
  IN FORM_DISPLAY_ENGINE_FORM      *FormData,
  IN FORM_DISPLAY_ENGINE_STATEMENT *Statement,
  IN  BOOLEAN                      Selected
  )
{
  UINTN                  SecCol;
  UINTN                  ThdCol;
  UINTN                  RightColumnOfHelp;
  UINTN                  TopRowOfHelp;
  UINTN                  BottomRowOfHelp;
  UINTN                  StartColumnOfHelp;
  EFI_IFR_NUMERIC        *NumericOp;
  EFI_IFR_DATE           *DateOp;
  EFI_IFR_TIME           *TimeOp;
  BOOLEAN                HexDisplay;
  UINTN                  ColumnWidth1;
  UINTN                  ColumnWidth2;
  UINTN                  ColumnWidth3;
  CHAR16                 *ColumnStr1;
  CHAR16                 *ColumnStr2;
  CHAR16                 *ColumnStr3;

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

  gST->ConOut->SetAttribute (gST->ConOut, KEYHELP_TEXT | KEYHELP_BACKGROUND);

  if ((FormData->Attribute & HII_DISPLAY_MODAL) != 0) {
    return;
  }

  SecCol            = gScreenDimensions.LeftColumn + (gScreenDimensions.RightColumn - gScreenDimensions.LeftColumn) / 3;
  ThdCol            = gScreenDimensions.LeftColumn + (gScreenDimensions.RightColumn - gScreenDimensions.LeftColumn) / 3 * 2;

  //
  // + 2 means leave 1 space before the first hotkey info.
  //
  StartColumnOfHelp = gScreenDimensions.LeftColumn + 2;
  RightColumnOfHelp = gScreenDimensions.RightColumn - 1;
  TopRowOfHelp      = gScreenDimensions.BottomRow - STATUS_BAR_HEIGHT - gFooterHeight + 1;
  BottomRowOfHelp   = gScreenDimensions.BottomRow - STATUS_BAR_HEIGHT - 2;

  ColumnWidth1      = SecCol - StartColumnOfHelp;
  ColumnWidth2      = ThdCol - SecCol;
  ColumnWidth3      = RightColumnOfHelp - ThdCol;
  ColumnStr1        = gLibEmptyString;
  ColumnStr2        = gLibEmptyString;
  ColumnStr3        = gLibEmptyString;

  //
  // Clean the space at gScreenDimensions.LeftColumn + 1.
  //
  PrintStringAtWithWidth (StartColumnOfHelp - 1, BottomRowOfHelp, gLibEmptyString, 1);
  PrintStringAtWithWidth (StartColumnOfHelp - 1, TopRowOfHelp, gLibEmptyString, 1);

  if (Statement == NULL) {
    //
    // Print Key for Form without showable statement.
    //
    PrintHotKeyHelpString (FormData, TRUE);
    PrintStringAtWithWidth (StartColumnOfHelp, BottomRowOfHelp, gLibEmptyString, ColumnWidth1);
    PrintStringAtWithWidth (SecCol, BottomRowOfHelp, gLibEmptyString, ColumnWidth2);
    PrintStringAtWithWidth (StartColumnOfHelp, TopRowOfHelp, gLibEmptyString, ColumnWidth1);
    if (gClassOfVfr == FORMSET_CLASS_PLATFORM_SETUP) {
      ColumnStr3 = gEscapeString;
    }
    PrintStringAtWithWidth (ThdCol, BottomRowOfHelp, ColumnStr3, ColumnWidth3);

    return;
  }

  HexDisplay = FALSE;
  NumericOp = NULL;
  DateOp    = NULL;
  TimeOp    = NULL;
  if (Statement->OpCode->OpCode == EFI_IFR_NUMERIC_OP) {
    NumericOp = (EFI_IFR_NUMERIC *) Statement->OpCode;
    HexDisplay = (NumericOp->Flags & EFI_IFR_DISPLAY_UINT_HEX) == EFI_IFR_DISPLAY_UINT_HEX;
  } else if (Statement->OpCode->OpCode == EFI_IFR_DATE_OP) {
    DateOp   = (EFI_IFR_DATE *) Statement->OpCode;
    HexDisplay = (DateOp->Flags & EFI_IFR_DISPLAY_UINT_HEX) == EFI_IFR_DISPLAY_UINT_HEX;
  } else if (Statement->OpCode->OpCode == EFI_IFR_TIME_OP) {
    TimeOp  = (EFI_IFR_TIME *) Statement->OpCode;
    HexDisplay = (TimeOp->Flags & EFI_IFR_DISPLAY_UINT_HEX) == EFI_IFR_DISPLAY_UINT_HEX;
  }  
  switch (Statement->OpCode->OpCode) {
  case EFI_IFR_ORDERED_LIST_OP:
  case EFI_IFR_ONE_OF_OP:
  case EFI_IFR_NUMERIC_OP:
  case EFI_IFR_TIME_OP:
  case EFI_IFR_DATE_OP:
    if (!Selected) {
      PrintHotKeyHelpString (FormData, TRUE);

      if (gClassOfVfr == FORMSET_CLASS_PLATFORM_SETUP) {
        ColumnStr3 = gEscapeString;
      }
      PrintStringAtWithWidth (ThdCol, BottomRowOfHelp, ColumnStr3, ColumnWidth3);

      if ((Statement->OpCode->OpCode == EFI_IFR_DATE_OP) ||
          (Statement->OpCode->OpCode == EFI_IFR_TIME_OP)) {
        PrintAt (
          ColumnWidth1, 
          StartColumnOfHelp,
          BottomRowOfHelp,
          L"%c%c%c%c%s",
          ARROW_UP,
          ARROW_DOWN,
          ARROW_RIGHT,
          ARROW_LEFT,
          gMoveHighlight
          );
        PrintStringAtWithWidth (SecCol, BottomRowOfHelp, gEnterString, ColumnWidth2);
        PrintStringAtWithWidth (StartColumnOfHelp, TopRowOfHelp, gAdjustNumber, ColumnWidth1);
      } else {
        PrintAt (ColumnWidth1, StartColumnOfHelp, BottomRowOfHelp, L"%c%c%s", ARROW_UP, ARROW_DOWN, gMoveHighlight);
        if (Statement->OpCode->OpCode == EFI_IFR_NUMERIC_OP && NumericOp != NULL && LibGetFieldFromNum(Statement->OpCode) != 0) {
          ColumnStr1 = gAdjustNumber;
        }
        PrintStringAtWithWidth (StartColumnOfHelp, TopRowOfHelp, ColumnStr1, ColumnWidth1);
        PrintStringAtWithWidth (SecCol, BottomRowOfHelp, gEnterString, ColumnWidth2);
      }
    } else {
      PrintHotKeyHelpString (FormData, FALSE);
      PrintStringAtWithWidth (SecCol, BottomRowOfHelp, gEnterCommitString, ColumnWidth2);

      //
      // If it is a selected numeric with manual input, display different message
      //
      if ((Statement->OpCode->OpCode == EFI_IFR_NUMERIC_OP) || 
          (Statement->OpCode->OpCode == EFI_IFR_DATE_OP) ||
          (Statement->OpCode->OpCode == EFI_IFR_TIME_OP)) {
        ColumnStr2 = HexDisplay ? gHexNumericInput : gDecNumericInput;
        PrintStringAtWithWidth (StartColumnOfHelp, BottomRowOfHelp, gLibEmptyString, ColumnWidth1);
      } else {
        PrintAt (ColumnWidth1, StartColumnOfHelp, BottomRowOfHelp, L"%c%c%s", ARROW_UP, ARROW_DOWN, gMoveHighlight);
      }

      if (Statement->OpCode->OpCode == EFI_IFR_ORDERED_LIST_OP) {
        ColumnStr1 = gPlusString;
        ColumnStr3 = gMinusString;
      }
      PrintStringAtWithWidth (StartColumnOfHelp, TopRowOfHelp, ColumnStr1, ColumnWidth1);
      PrintStringAtWithWidth (ThdCol, TopRowOfHelp, ColumnStr3, ColumnWidth3);
      PrintStringAtWithWidth (SecCol, TopRowOfHelp, ColumnStr2, ColumnWidth2);

      PrintStringAtWithWidth (ThdCol, BottomRowOfHelp, gEnterEscapeString, ColumnWidth3);
    }
    break;

  case EFI_IFR_CHECKBOX_OP:
    PrintHotKeyHelpString (FormData, TRUE);

    if (gClassOfVfr == FORMSET_CLASS_PLATFORM_SETUP) {
      ColumnStr3 = gEscapeString;
    }
    PrintStringAtWithWidth (ThdCol, BottomRowOfHelp, ColumnStr3, ColumnWidth3);

    PrintAt (ColumnWidth1, StartColumnOfHelp, BottomRowOfHelp, L"%c%c%s", ARROW_UP, ARROW_DOWN, gMoveHighlight);
    PrintStringAtWithWidth (SecCol, BottomRowOfHelp, gToggleCheckBox, ColumnWidth2);
    PrintStringAtWithWidth (StartColumnOfHelp, TopRowOfHelp, gLibEmptyString, ColumnWidth1);
    break;

  case EFI_IFR_REF_OP:
  case EFI_IFR_PASSWORD_OP:
  case EFI_IFR_STRING_OP:
  case EFI_IFR_TEXT_OP:
  case EFI_IFR_ACTION_OP:
  case EFI_IFR_RESET_BUTTON_OP:
  case EFI_IFR_SUBTITLE_OP:
     if (!Selected) {
      PrintHotKeyHelpString (FormData, TRUE);

      if (gClassOfVfr == FORMSET_CLASS_PLATFORM_SETUP) {
        ColumnStr3 = gEscapeString;
      }
      PrintStringAtWithWidth (ThdCol, BottomRowOfHelp, ColumnStr3, ColumnWidth3);

      PrintAt (ColumnWidth1, StartColumnOfHelp, BottomRowOfHelp, L"%c%c%s", ARROW_UP, ARROW_DOWN, gMoveHighlight);
      if (Statement->OpCode->OpCode != EFI_IFR_TEXT_OP && Statement->OpCode->OpCode != EFI_IFR_SUBTITLE_OP) {
        ColumnStr2 = gEnterString;
      }
      PrintStringAtWithWidth (SecCol, BottomRowOfHelp, ColumnStr2, ColumnWidth2);
      PrintStringAtWithWidth (StartColumnOfHelp, TopRowOfHelp, ColumnStr1, ColumnWidth1);
    } else {
      PrintHotKeyHelpString (FormData, FALSE);
      if (Statement->OpCode->OpCode != EFI_IFR_REF_OP) {
        ColumnStr2 = gEnterCommitString;
        ColumnStr3 = gEnterEscapeString;
      }
      PrintStringAtWithWidth (StartColumnOfHelp, TopRowOfHelp, ColumnStr1, ColumnWidth1);
      PrintStringAtWithWidth (StartColumnOfHelp, BottomRowOfHelp, ColumnStr1, ColumnWidth1);
      PrintStringAtWithWidth (SecCol, BottomRowOfHelp, ColumnStr2, ColumnWidth2);
      PrintStringAtWithWidth (ThdCol, BottomRowOfHelp, ColumnStr3, ColumnWidth3);
    }
    break;

  default:
    break;
  }  
}
/**
  Print framework and form title for a page.

  @param[in]  FormData             Form Data to be shown in Page
**/
VOID
PrintFramework (
  IN FORM_DISPLAY_ENGINE_FORM       *FormData
  )
{
  UINTN                  Index;
  CHAR16                 Character;
  CHAR16                 *Buffer;
  UINTN                  Row;
  CHAR16                 *TitleStr;
  UINTN                  TitleColumn;

  if (gClassOfVfr != FORMSET_CLASS_PLATFORM_SETUP) {
    //
    // Only Setup page needs Framework
    //
    ClearLines (
      gScreenDimensions.LeftColumn,
      gScreenDimensions.RightColumn,
      gScreenDimensions.BottomRow - STATUS_BAR_HEIGHT - gFooterHeight,
      gScreenDimensions.BottomRow - STATUS_BAR_HEIGHT - 1,
      KEYHELP_TEXT | KEYHELP_BACKGROUND
      );
    return;
  }
    
  Buffer = AllocateZeroPool (0x10000);
  ASSERT (Buffer != NULL);
  Character = BOXDRAW_HORIZONTAL;
  for (Index = 0; Index + 2 < (gScreenDimensions.RightColumn - gScreenDimensions.LeftColumn); Index++) {
    Buffer[Index] = Character;
  }

  //
  // Print Top border line
  // +------------------------------------------------------------------------------+
  // ?                                                                             ?
  // +------------------------------------------------------------------------------+
  //
  gST->ConOut->SetAttribute (gST->ConOut, TITLE_TEXT | TITLE_BACKGROUND);
  Character = BOXDRAW_DOWN_RIGHT;

  PrintCharAt (gScreenDimensions.LeftColumn, gScreenDimensions.TopRow, Character);
  PrintStringAt ((UINTN) -1, (UINTN) -1, Buffer);

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

  Character = BOXDRAW_VERTICAL;
  for (Row = gScreenDimensions.TopRow + 1; Row <= gScreenDimensions.TopRow + NONE_FRONT_PAGE_HEADER_HEIGHT - 2; Row++) {
    PrintCharAt (gScreenDimensions.LeftColumn, Row, Character);
    PrintCharAt (gScreenDimensions.RightColumn - 1, Row, Character);
  }
  
  //
  // Print Form Title
  //
  TitleStr = LibGetToken (FormData->FormTitle, FormData->HiiHandle);
  ASSERT (TitleStr != NULL);
  TitleColumn = (gScreenDimensions.RightColumn + gScreenDimensions.LeftColumn - LibGetStringWidth (TitleStr) / 2) / 2;
  PrintStringAtWithWidth (gScreenDimensions.LeftColumn + 1, gScreenDimensions.TopRow + 1, gLibEmptyString, TitleColumn - gScreenDimensions.LeftColumn - 1);
  PrintStringAtWithWidth (
    TitleColumn,
    gScreenDimensions.TopRow + 1,
    TitleStr,
    gScreenDimensions.RightColumn - 1 - TitleColumn
    );
  FreePool (TitleStr);

  Character = BOXDRAW_UP_RIGHT;
  PrintCharAt (gScreenDimensions.LeftColumn, gScreenDimensions.TopRow + NONE_FRONT_PAGE_HEADER_HEIGHT - 1, Character);
  PrintStringAt ((UINTN) -1, (UINTN) -1, Buffer);

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

  //
  // Print Bottom border line
  // +------------------------------------------------------------------------------+
  // ?                                                                             ?
  // +------------------------------------------------------------------------------+
  //
  Character = BOXDRAW_DOWN_RIGHT;
  PrintCharAt (gScreenDimensions.LeftColumn, gScreenDimensions.BottomRow - STATUS_BAR_HEIGHT - gFooterHeight, Character);

  PrintStringAt ((UINTN) -1, (UINTN) -1, Buffer);

  Character = BOXDRAW_DOWN_LEFT;
  PrintCharAt ((UINTN) -1, (UINTN) -1, Character);
  Character = BOXDRAW_VERTICAL;
  for (Row = gScreenDimensions.BottomRow - STATUS_BAR_HEIGHT - gFooterHeight + 1;
       Row <= gScreenDimensions.BottomRow - STATUS_BAR_HEIGHT - 2;
       Row++
      ) {
    PrintCharAt (gScreenDimensions.LeftColumn, Row, Character);
    PrintCharAt (gScreenDimensions.RightColumn - 1, Row, Character);
  }

  Character = BOXDRAW_UP_RIGHT;
  PrintCharAt (gScreenDimensions.LeftColumn, gScreenDimensions.BottomRow - STATUS_BAR_HEIGHT - 1, Character);

  PrintStringAt ((UINTN) -1, (UINTN) -1, Buffer);

  Character = BOXDRAW_UP_LEFT;
  PrintCharAt ((UINTN) -1, (UINTN) -1, Character);
  
  FreePool (Buffer);
}