/** @file
  This library is only intended to be used by PlatformBootManagerLib
  to show progress bar and LOGO.

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

**/

#include <Uefi.h>
#include <Protocol/GraphicsOutput.h>
#include <Protocol/SimpleTextOut.h>
#include <Protocol/PlatformLogo.h>
#include <Protocol/UgaDraw.h>
#include <Protocol/BootLogo.h>
#include <Protocol/BootLogo2.h>
#include <Library/BaseLib.h>
#include <Library/UefiLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/PcdLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/DebugLib.h>

/**
  Show LOGO returned from Edkii Platform Logo protocol on all consoles.

  @retval EFI_SUCCESS     Logo was displayed.
  @retval EFI_UNSUPPORTED Logo was not found or cannot be displayed.
**/
EFI_STATUS
EFIAPI
BootLogoEnableLogo (
  VOID
  )
{
  EFI_STATUS                             Status;
  EDKII_PLATFORM_LOGO_PROTOCOL           *PlatformLogo;
  EDKII_PLATFORM_LOGO_DISPLAY_ATTRIBUTE  Attribute;
  INTN                                   OffsetX;
  INTN                                   OffsetY;
  UINT32                                 SizeOfX;
  UINT32                                 SizeOfY;
  INTN                                   DestX;
  INTN                                   DestY;
  UINT32                                 Instance;
  EFI_IMAGE_INPUT                        Image;
  EFI_GRAPHICS_OUTPUT_BLT_PIXEL          *Blt;
  EFI_UGA_DRAW_PROTOCOL                  *UgaDraw;
  UINT32                                 ColorDepth;
  UINT32                                 RefreshRate;
  EFI_GRAPHICS_OUTPUT_PROTOCOL           *GraphicsOutput;
  EFI_BOOT_LOGO_PROTOCOL                 *BootLogo;
  EDKII_BOOT_LOGO2_PROTOCOL              *BootLogo2;
  UINTN                                  NumberOfLogos;
  EFI_GRAPHICS_OUTPUT_BLT_PIXEL          *LogoBlt;
  UINTN                                  LogoDestX;
  UINTN                                  LogoDestY;
  UINTN                                  LogoHeight;
  UINTN                                  LogoWidth;
  UINTN                                  NewDestX;
  UINTN                                  NewDestY;
  UINTN                                  BufferSize;

  Status = gBS->LocateProtocol (&gEdkiiPlatformLogoProtocolGuid, NULL, (VOID **)&PlatformLogo);
  if (EFI_ERROR (Status)) {
    return EFI_UNSUPPORTED;
  }

  UgaDraw = NULL;
  //
  // Try to open GOP first
  //
  Status = gBS->HandleProtocol (gST->ConsoleOutHandle, &gEfiGraphicsOutputProtocolGuid, (VOID **)&GraphicsOutput);
  if (EFI_ERROR (Status) && FeaturePcdGet (PcdUgaConsumeSupport)) {
    GraphicsOutput = NULL;
    //
    // Open GOP failed, try to open UGA
    //
    Status = gBS->HandleProtocol (gST->ConsoleOutHandle, &gEfiUgaDrawProtocolGuid, (VOID **)&UgaDraw);
    if (EFI_ERROR (Status)) {
      UgaDraw = NULL;
    }
  }

  if (EFI_ERROR (Status)) {
    return EFI_UNSUPPORTED;
  }

  //
  // Try to open Boot Logo Protocol.
  //
  Status = gBS->LocateProtocol (&gEfiBootLogoProtocolGuid, NULL, (VOID **)&BootLogo);
  if (EFI_ERROR (Status)) {
    BootLogo = NULL;
  }

  //
  // Try to open Boot Logo 2 Protocol.
  //
  Status = gBS->LocateProtocol (&gEdkiiBootLogo2ProtocolGuid, NULL, (VOID **)&BootLogo2);
  if (EFI_ERROR (Status)) {
    BootLogo2 = NULL;
  }

  //
  // Erase Cursor from screen
  //
  gST->ConOut->EnableCursor (gST->ConOut, FALSE);

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

  Blt           = NULL;
  NumberOfLogos = 0;
  LogoDestX     = 0;
  LogoDestY     = 0;
  LogoHeight    = 0;
  LogoWidth     = 0;
  NewDestX      = 0;
  NewDestY      = 0;
  Instance      = 0;
  DestX         = 0;
  DestY         = 0;
  while (TRUE) {
    //
    // Get image from PlatformLogo protocol.
    //
    Status = PlatformLogo->GetImage (
                             PlatformLogo,
                             &Instance,
                             &Image,
                             &Attribute,
                             &OffsetX,
                             &OffsetY
                             );
    if (EFI_ERROR (Status)) {
      break;
    }

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

    Blt = Image.Bitmap;

    //
    // Calculate the display position according to Attribute.
    //
    switch (Attribute) {
      case EdkiiPlatformLogoDisplayAttributeLeftTop:
        DestX = 0;
        DestY = 0;
        break;
      case EdkiiPlatformLogoDisplayAttributeCenterTop:
        DestX = (SizeOfX - Image.Width) / 2;
        DestY = 0;
        break;
      case EdkiiPlatformLogoDisplayAttributeRightTop:
        DestX = SizeOfX - Image.Width;
        DestY = 0;
        break;

      case EdkiiPlatformLogoDisplayAttributeCenterLeft:
        DestX = 0;
        DestY = (SizeOfY - Image.Height) / 2;
        break;
      case EdkiiPlatformLogoDisplayAttributeCenter:
        DestX = (SizeOfX - Image.Width) / 2;
        DestY = (SizeOfY - Image.Height) / 2;
        break;
      case EdkiiPlatformLogoDisplayAttributeCenterRight:
        DestX = SizeOfX - Image.Width;
        DestY = (SizeOfY - Image.Height) / 2;
        break;

      case EdkiiPlatformLogoDisplayAttributeLeftBottom:
        DestX = 0;
        DestY = SizeOfY - Image.Height;
        break;
      case EdkiiPlatformLogoDisplayAttributeCenterBottom:
        DestX = (SizeOfX - Image.Width) / 2;
        DestY = SizeOfY - Image.Height;
        break;
      case EdkiiPlatformLogoDisplayAttributeRightBottom:
        DestX = SizeOfX - Image.Width;
        DestY = SizeOfY - Image.Height;
        break;

      default:
        ASSERT (FALSE);
        continue;
        break;
    }

    DestX += OffsetX;
    DestY += OffsetY;

    if ((DestX >= 0) && (DestY >= 0)) {
      if (GraphicsOutput != NULL) {
        Status = GraphicsOutput->Blt (
                                   GraphicsOutput,
                                   Blt,
                                   EfiBltBufferToVideo,
                                   0,
                                   0,
                                   (UINTN)DestX,
                                   (UINTN)DestY,
                                   Image.Width,
                                   Image.Height,
                                   Image.Width * sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL)
                                   );
      } else {
        ASSERT (UgaDraw != NULL);
        Status = UgaDraw->Blt (
                            UgaDraw,
                            (EFI_UGA_PIXEL *)Blt,
                            EfiUgaBltBufferToVideo,
                            0,
                            0,
                            (UINTN)DestX,
                            (UINTN)DestY,
                            Image.Width,
                            Image.Height,
                            Image.Width * sizeof (EFI_UGA_PIXEL)
                            );
      }

      //
      // Report displayed Logo information.
      //
      if (!EFI_ERROR (Status)) {
        NumberOfLogos++;

        if (NumberOfLogos == 1) {
          //
          // The first Logo.
          //
          LogoDestX  = (UINTN)DestX;
          LogoDestY  = (UINTN)DestY;
          LogoWidth  = Image.Width;
          LogoHeight = Image.Height;
        } else {
          //
          // Merge new logo with old one.
          //
          NewDestX   = MIN ((UINTN)DestX, LogoDestX);
          NewDestY   = MIN ((UINTN)DestY, LogoDestY);
          LogoWidth  = MAX ((UINTN)DestX + Image.Width, LogoDestX + LogoWidth) - NewDestX;
          LogoHeight = MAX ((UINTN)DestY + Image.Height, LogoDestY + LogoHeight) - NewDestY;

          LogoDestX = NewDestX;
          LogoDestY = NewDestY;
        }
      }
    }
  }

  if (((BootLogo == NULL) && (BootLogo2 == NULL)) || (NumberOfLogos == 0)) {
    //
    // No logo displayed.
    //
    if (Blt != NULL) {
      FreePool (Blt);
    }

    return Status;
  }

  //
  // Advertise displayed Logo information.
  //
  if (NumberOfLogos == 1) {
    //
    // Only one logo displayed, use its Blt buffer directly for BootLogo protocol.
    //
    LogoBlt = Blt;
    Status  = EFI_SUCCESS;
  } else {
    //
    // More than one Logo displayed, get merged BltBuffer using VideoToBuffer operation.
    //
    if (Blt != NULL) {
      FreePool (Blt);
    }

    //
    // Ensure the LogoHeight * LogoWidth * sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL) doesn't overflow
    //
    if (LogoHeight > MAX_UINTN / LogoWidth / sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL)) {
      return EFI_UNSUPPORTED;
    }

    BufferSize = LogoWidth * LogoHeight * sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL);

    LogoBlt = AllocatePool (BufferSize);
    if (LogoBlt == NULL) {
      return EFI_OUT_OF_RESOURCES;
    }

    if (GraphicsOutput != NULL) {
      Status = GraphicsOutput->Blt (
                                 GraphicsOutput,
                                 LogoBlt,
                                 EfiBltVideoToBltBuffer,
                                 LogoDestX,
                                 LogoDestY,
                                 0,
                                 0,
                                 LogoWidth,
                                 LogoHeight,
                                 LogoWidth * sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL)
                                 );
    } else {
      Status = UgaDraw->Blt (
                          UgaDraw,
                          (EFI_UGA_PIXEL *)LogoBlt,
                          EfiUgaVideoToBltBuffer,
                          LogoDestX,
                          LogoDestY,
                          0,
                          0,
                          LogoWidth,
                          LogoHeight,
                          LogoWidth * sizeof (EFI_UGA_PIXEL)
                          );
    }
  }

  if (!EFI_ERROR (Status)) {
    //
    // Attempt to register logo with Boot Logo 2 Protocol first
    //
    if (BootLogo2 != NULL) {
      Status = BootLogo2->SetBootLogo (BootLogo2, LogoBlt, LogoDestX, LogoDestY, LogoWidth, LogoHeight);
    }

    //
    // If Boot Logo 2 Protocol is not available or registration with Boot Logo 2
    // Protocol failed, then attempt to register logo with Boot Logo Protocol
    //
    if (EFI_ERROR (Status) && (BootLogo != NULL)) {
      Status = BootLogo->SetBootLogo (BootLogo, LogoBlt, LogoDestX, LogoDestY, LogoWidth, LogoHeight);
    }

    //
    // Status of this function is EFI_SUCCESS even if registration with Boot
    // Logo 2 Protocol or Boot Logo Protocol fails.
    //
    Status = EFI_SUCCESS;
  }

  FreePool (LogoBlt);

  return Status;
}

/**
  Use SystemTable Conout to turn on video based Simple Text Out consoles. The
  Simple Text Out screens will now be synced up with all non video output devices

  @retval EFI_SUCCESS     UGA devices are back in text mode and synced up.

**/
EFI_STATUS
EFIAPI
BootLogoDisableLogo (
  VOID
  )
{
  //
  // Enable Cursor on Screen
  //
  gST->ConOut->EnableCursor (gST->ConOut, TRUE);
  return EFI_SUCCESS;
}

/**

  Update 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
EFIAPI
BootLogoUpdateProgress (
  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) && FeaturePcdGet (PcdUgaConsumeSupport)) {
    GraphicsOutput = NULL;

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

  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 if (UgaDraw != NULL) {
    Status = UgaDraw->GetMode (
                        UgaDraw,
                        &SizeOfX,
                        &SizeOfY,
                        &ColorDepth,
                        &RefreshRate
                        );
    if (EFI_ERROR (Status)) {
      return EFI_UNSUPPORTED;
    }
  } else {
    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 if (FeaturePcdGet (PcdUgaConsumeSupport)) {
      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)
                          );
    } else {
      return EFI_UNSUPPORTED;
    }
  }

  //
  // 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 if (FeaturePcdGet (PcdUgaConsumeSupport)) {
      Status = UgaDraw->Blt (
                          UgaDraw,
                          (EFI_UGA_PIXEL *)&ProgressColor,
                          EfiUgaVideoFill,
                          0,
                          0,
                          PosX,
                          PosY,
                          BlockWidth - 1,
                          BlockHeight,
                          (BlockWidth) * sizeof (EFI_UGA_PIXEL)
                          );
    } else {
      return EFI_UNSUPPORTED;
    }
  }

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

  return EFI_SUCCESS;
}
