/**@file
Framework to UEFI 2.1 Setup Browser Thunk. The file consume EFI_FORM_BROWSER2_PROTOCOL
to produce a EFI_FORM_BROWSER_PROTOCOL.

Copyright (c) 2008, Intel Corporation
All rights reserved. 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 "HiiDatabase.h"
#include "SetupBrowser.h"

EFI_GUID gFrameworkBdsFrontPageFormsetGuid = FRAMEWORK_BDS_FRONTPAGE_FORMSET_GUID;
EFI_HII_HANDLE gStringPackHandle = NULL;
BOOLEAN mFrontPageDisplayed = FALSE;
//
// 106F3545-B788-4cb5-9D2A-CE0CDB208DF5
//
EFI_GUID gEfiHiiThunkProducerGuid = { 0x106f3545, 0xb788, 0x4cb5, { 0x9d, 0x2a, 0xce, 0xc, 0xdb, 0x20, 0x8d, 0xf5 } }; 


/**
  Get string by string id from HII Interface


  @param Id              String ID.

  @retval  CHAR16 *  String from ID.
  @retval  NULL      If error occurs.

**/
CHAR16 *
GetStringById (
  IN  EFI_STRING_ID   Id
  )
{
  CHAR16 *String;

  String = NULL;
  HiiLibGetStringFromHandle (gStringPackHandle, Id, &String);

  return String;
}
/**

  Show progress bar with title above it. It only works in Graphics mode.


  @param TitleForeground Foreground color for Title.
  @param TitleBackground Background color for Title.
  @param Title           Title above progress bar.
  @param ProgressColor   Progress bar color.
  @param Progress        Progress (0-100)
  @param PreviousValue   The previous value of the progress.

  @retval  EFI_STATUS       Success update the progress bar

**/
EFI_STATUS
PlatformBdsShowProgress (
  IN EFI_GRAPHICS_OUTPUT_BLT_PIXEL TitleForeground,
  IN EFI_GRAPHICS_OUTPUT_BLT_PIXEL TitleBackground,
  IN CHAR16                        *Title,
  IN EFI_GRAPHICS_OUTPUT_BLT_PIXEL ProgressColor,
  IN UINTN                         Progress,
  IN UINTN                         PreviousValue
  )
{
  EFI_STATUS                     Status;
  EFI_GRAPHICS_OUTPUT_PROTOCOL   *GraphicsOutput;
  EFI_UGA_DRAW_PROTOCOL          *UgaDraw;
  UINT32                         SizeOfX;
  UINT32                         SizeOfY;
  UINT32                         ColorDepth;
  UINT32                         RefreshRate;
  EFI_GRAPHICS_OUTPUT_BLT_PIXEL  Color;
  UINTN                          BlockHeight;
  UINTN                          BlockWidth;
  UINTN                          BlockNum;
  UINTN                          PosX;
  UINTN                          PosY;
  UINTN                          Index;

  if (Progress > 100) {
    return EFI_INVALID_PARAMETER;
  }

  UgaDraw = NULL;
  Status = gBS->HandleProtocol (
                  gST->ConsoleOutHandle,
                  &gEfiGraphicsOutputProtocolGuid,
                  (VOID **) &GraphicsOutput
                  );
  if (EFI_ERROR (Status)) {
    GraphicsOutput = NULL;

    Status = gBS->HandleProtocol (
                    gST->ConsoleOutHandle,
                    &gEfiUgaDrawProtocolGuid,
                    (VOID **) &UgaDraw
                    );
  }
  if (EFI_ERROR (Status)) {
    return EFI_UNSUPPORTED;
  }

  SizeOfX = 0;
  SizeOfY = 0;
  if (GraphicsOutput != NULL) {
    SizeOfX = GraphicsOutput->Mode->Info->HorizontalResolution;
    SizeOfY = GraphicsOutput->Mode->Info->VerticalResolution;
  } else {
    Status = UgaDraw->GetMode (
                        UgaDraw,
                        &SizeOfX,
                        &SizeOfY,
                        &ColorDepth,
                        &RefreshRate
                        );
    if (EFI_ERROR (Status)) {
      return EFI_UNSUPPORTED;
    }
  }

  BlockWidth  = SizeOfX / 100;
  BlockHeight = SizeOfY / 50;

  BlockNum    = Progress;

  PosX        = 0;
  PosY        = SizeOfY * 48 / 50;

  if (BlockNum == 0) {
    //
    // Clear progress area
    //
    SetMem (&Color, sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL), 0x0);

    if (GraphicsOutput != NULL) {
      Status = GraphicsOutput->Blt (
                          GraphicsOutput,
                          &Color,
                          EfiBltVideoFill,
                          0,
                          0,
                          0,
                          PosY - EFI_GLYPH_HEIGHT - 1,
                          SizeOfX,
                          SizeOfY - (PosY - EFI_GLYPH_HEIGHT - 1),
                          SizeOfX * sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL)
                          );
    } else {
      Status = UgaDraw->Blt (
                          UgaDraw,
                          (EFI_UGA_PIXEL *) &Color,
                          EfiUgaVideoFill,
                          0,
                          0,
                          0,
                          PosY - EFI_GLYPH_HEIGHT - 1,
                          SizeOfX,
                          SizeOfY - (PosY - EFI_GLYPH_HEIGHT - 1),
                          SizeOfX * sizeof (EFI_UGA_PIXEL)
                          );
    }
  }
  //
  // Show progress by drawing blocks
  //
  for (Index = PreviousValue; Index < BlockNum; Index++) {
    PosX = Index * BlockWidth;
    if (GraphicsOutput != NULL) {
      Status = GraphicsOutput->Blt (
                          GraphicsOutput,
                          &ProgressColor,
                          EfiBltVideoFill,
                          0,
                          0,
                          PosX,
                          PosY,
                          BlockWidth - 1,
                          BlockHeight,
                          (BlockWidth) * sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL)
                          );
    } else {
      Status = UgaDraw->Blt (
                          UgaDraw,
                          (EFI_UGA_PIXEL *) &ProgressColor,
                          EfiUgaVideoFill,
                          0,
                          0,
                          PosX,
                          PosY,
                          BlockWidth - 1,
                          BlockHeight,
                          (BlockWidth) * sizeof (EFI_UGA_PIXEL)
                          );
    }
  }

  PrintXY (
    (SizeOfX - StrLen (Title) * EFI_GLYPH_WIDTH) / 2,
    PosY - EFI_GLYPH_HEIGHT - 1,
    &TitleForeground,
    &TitleBackground,
    Title
    );

  return EFI_SUCCESS;
}

/**
  Function waits 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.

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

**/
EFI_STATUS
WaitForSingleEvent (
  IN EFI_EVENT                  Event,
  IN UINT64                     Timeout 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 {
    //
    // No timeout... just wait on the event
    //
    Status = gBS->WaitForEvent (1, &Event, &Index);
    ASSERT (!EFI_ERROR (Status));
    ASSERT (Index == 0);
  }

  return Status;
}

/**
  Function show progress bar to wait for user input.


  @param TimeoutDefault  - The fault time out value before the system
                         continue to boot.

  @retval  EFI_SUCCESS       User pressed some key except "Enter"
  @retval  EFI_TIME_OUT      Timout expired or user press "Enter"

**/
EFI_STATUS
ShowProgress (
  IN UINT16                       TimeoutDefault
  )
{
  EFI_STATUS                    Status;
  CHAR16                        *TmpStr;
  EFI_GRAPHICS_OUTPUT_BLT_PIXEL Foreground;
  EFI_GRAPHICS_OUTPUT_BLT_PIXEL Background;
  EFI_GRAPHICS_OUTPUT_BLT_PIXEL Color;
  EFI_INPUT_KEY                 Key;
  UINT16                        TimeoutRemain;

  if (TimeoutDefault == 0) {
    return EFI_TIMEOUT;
  }

  DEBUG ((EFI_D_INFO, "\n\nStart showing progress bar... Press any key to stop it! ...Zzz....\n"));
  
  SetMem (&Foreground, sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL), 0xff);
  SetMem (&Background, sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL), 0x0);
  SetMem (&Color, sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL), 0xff);

  //
  // Clear the progress status bar first
  //
  TmpStr = GetStringById (STRING_TOKEN (STR_START_BOOT_OPTION));
  if (TmpStr != NULL) {
    PlatformBdsShowProgress (Foreground, Background, TmpStr, Color, 0, 0);
  }

  TimeoutRemain = TimeoutDefault;
  while (TimeoutRemain != 0) {
    DEBUG ((EFI_D_INFO, "Showing progress bar...Remaining %d second!\n", TimeoutRemain));
    
    Status = WaitForSingleEvent (gST->ConIn->WaitForKey, ONE_SECOND);
    if (Status != EFI_TIMEOUT) {
      break;
    }
    TimeoutRemain--;

    //
    // Show progress
    //
    if (TmpStr != NULL) {
      PlatformBdsShowProgress (
        Foreground,
        Background,
        TmpStr,
        Color,
        ((TimeoutDefault - TimeoutRemain) * 100 / TimeoutDefault),
        0
        );
    }
  }
  gBS->FreePool (TmpStr);

  //
  // Timeout expired
  //
  if (TimeoutRemain == 0) {
    return EFI_TIMEOUT;
  }

  //
  // User pressed some key
  //
  Status = gST->ConIn->ReadKeyStroke (gST->ConIn, &Key);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  if (Key.UnicodeChar == CHAR_CARRIAGE_RETURN) {
    //
    // User pressed enter, equivalent to select "continue"
    //
    return EFI_TIMEOUT;
  }

  return EFI_SUCCESS;
}

/**
  Return the default value for system Timeout variable.

  @return Timeout value.

**/
UINT16
EFIAPI
GetTimeout (
  VOID
  )
{
  UINT16      Timeout;
  UINTN       Size;
  EFI_STATUS  Status;

  //
  // Return Timeout variable or 0xffff if no valid
  // Timeout variable exists.
  //
  Size    = sizeof (UINT16);
  Status  = gRT->GetVariable (L"Timeout", &gEfiGlobalVariableGuid, NULL, &Size, &Timeout);
  if (EFI_ERROR (Status)) {
    //
    // According to UEFI 2.0 spec, it should treat the Timeout value as 0xffff
    // (default value PcdPlatformBootTimeOutDefault) when L"Timeout" variable is not present.
    // To make the current EFI Automatic-Test activity possible, platform can choose other value
    // for automatic boot when the variable is not present.
    //
    Timeout = PcdGet16 (PcdPlatformBootTimeOutDefault);
  }

  return Timeout;
}


/**
  This is the Framework Setup Browser interface which displays a FormSet.

  @param This           The EFI_FORM_BROWSER_PROTOCOL context.
  @param UseDatabase    TRUE if the FormSet is from HII database. The Thunk implementation
                        only support UseDatabase is TRUE.
  @param Handle         The Handle buffer.
  @param HandleCount    The number of Handle in the Handle Buffer. It must be 1 for this implementation.
  @param Packet         The pointer to data buffer containing IFR and String package. Not supported.
  @param CallbackHandle Not supported.
  @param NvMapOverride  The buffer is used only when there is no NV variable to define the 
                        current settings and the caller needs to provide to the browser the
                        current settings for the the "fake" NV variable. If used, no saving of
                        an NV variable is possbile. This parameter is also ignored if Handle is NULL.

  @retval EFI_SUCCESS             If the Formset is displayed correctly.
  @retval EFI_UNSUPPORTED         If UseDatabase is FALSE or HandleCount is not 1.
  @retval EFI_INVALID_PARAMETER   If the *Handle passed in is not found in the database.
**/

EFI_STATUS
EFIAPI 
ThunkSendForm (
  IN  EFI_FORM_BROWSER_PROTOCOL         *This,
  IN  BOOLEAN                           UseDatabase,
  IN  FRAMEWORK_EFI_HII_HANDLE          *Handle,
  IN  UINTN                             HandleCount,
  IN  FRAMEWORK_EFI_IFR_PACKET          *Packet, OPTIONAL
  IN  EFI_HANDLE                        CallbackHandle, OPTIONAL
  IN  UINT8                             *NvMapOverride, OPTIONAL
  IN  FRAMEWORK_EFI_SCREEN_DESCRIPTOR   *ScreenDimensions, OPTIONAL
  OUT BOOLEAN                           *ResetRequired OPTIONAL
  )
{
  EFI_STATUS                        Status;
  EFI_BROWSER_ACTION_REQUEST        ActionRequest;
  HII_THUNK_CONTEXT                 *ThunkContext;
  HII_THUNK_PRIVATE_DATA            *Private;
  EFI_FORMBROWSER_THUNK_PRIVATE_DATA *BrowserPrivate;

  if (!UseDatabase) {
    //
    // ThunkSendForm only support displays forms registered into the HII database.
    //
    return EFI_UNSUPPORTED;
  }

  if (HandleCount != 1 ) {
    return EFI_UNSUPPORTED;
  }

  BrowserPrivate = EFI_FORMBROWSER_THUNK_PRIVATE_DATA_FROM_THIS (This);
  Private = BrowserPrivate->ThunkPrivate;

  ThunkContext = FwHiiHandleToThunkContext (Private, *Handle);
  if (ThunkContext == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  //
  // Following UEFI spec to do auto booting after a time-out. This feature is implemented 
  // in Framework Setup Browser and moved to MdeModulePkg/Universal/BdsDxe. The auto booting is
  // moved here in HII Thunk module. 
  //
  if (CompareGuid (&gFrameworkBdsFrontPageFormsetGuid, &ThunkContext->FormSet->Guid) && !mFrontPageDisplayed) {
    //
    // Send form is called before entering the 
    //
    mFrontPageDisplayed = TRUE;
    Status = ShowProgress (GetTimeout ());

    if (EFI_ERROR (Status)) {
      return Status;
    }
  }
  
  if (NvMapOverride != NULL) {
    ThunkContext->NvMapOverride = NvMapOverride;
  }

  Status = mFormBrowser2Protocol->SendForm (
                                    mFormBrowser2Protocol,
                                    &ThunkContext->UefiHiiHandle,
                                    1,
                                    NULL,
                                    0,
                                    (EFI_SCREEN_DESCRIPTOR *) ScreenDimensions,
                                    &ActionRequest
                                    );

  if (ActionRequest == EFI_BROWSER_ACTION_REQUEST_RESET) {
    *ResetRequired = TRUE;
  }
  
  return Status;
}

/** 

  Rountine used to display a generic dialog interface and return 
  the Key or Input from user input.

  @param NumberOfLines The number of lines for the dialog box.
  @param HotKey        Defines if a single character is parsed (TRUE) and returned in KeyValue
                       or if a string is returned in StringBuffer.
  @param MaximumStringSize The maximum size in bytes of a typed-in string.
  @param StringBuffer  On return contains the typed-in string if HotKey
         is FALSE.
  @param KeyValue      The EFI_INPUT_KEY value returned if HotKey is TRUE.
  @param String        The pointer to the first string in the list of strings
                       that comprise the dialog box.
  @param ...           A series of NumberOfLines text strings that will be used
                       to construct the dialog box.
  @retval EFI_SUCCESS  The dialog is created successfully and user interaction was received.
  @retval EFI_DEVICE_ERROR The user typed in an ESC.
  @retval EFI_INVALID_PARAMETER One of the parameters was invalid.(StringBuffer == NULL && HotKey == FALSE).
**/
EFI_STATUS
EFIAPI 
ThunkCreatePopUp (
  IN  UINTN                           NumberOfLines,
  IN  BOOLEAN                         HotKey,
  IN  UINTN                           MaximumStringSize,
  OUT CHAR16                          *StringBuffer,
  OUT EFI_INPUT_KEY                   *KeyValue,
  IN  CHAR16                          *String,
  ...
  )
{
  EFI_STATUS                        Status;
  VA_LIST                           Marker;

  if (HotKey != TRUE) {
    return EFI_UNSUPPORTED;
  }

  VA_START (Marker, String);
  
  Status = IfrLibCreatePopUp2 (NumberOfLines, KeyValue, String, Marker);

  VA_END (Marker);
  
  return Status;
}

/** 

  Initialize string packages in HII database.

**/
VOID
InitSetBrowserStrings (
  VOID
  )
{
  EFI_STATUS Status;
  
  //
  // Initialize strings to HII database
  //
  Status = HiiLibAddPackages (1, &gEfiHiiThunkProducerGuid, NULL, &gStringPackHandle, STRING_ARRAY_NAME);
  ASSERT_EFI_ERROR (Status);

}
