/** @file
Implementation for EFI_HII_IMAGE_EX_PROTOCOL.


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

**/


#include "HiiDatabase.h"

/**
  The prototype of this extension function is the same with EFI_HII_IMAGE_PROTOCOL.NewImage().
  This protocol invokes EFI_HII_IMAGE_PROTOCOL.NewImage() implicitly.

  @param  This                   A pointer to the EFI_HII_IMAGE_EX_PROTOCOL instance.
  @param  PackageList            Handle of the package list where this image will
                                 be added.
  @param  ImageId                On return, contains the new image id, which is
                                 unique within PackageList.
  @param  Image                  Points to the image.

  @retval EFI_SUCCESS            The new image was added successfully.
  @retval EFI_NOT_FOUND          The PackageList could not be found.
  @retval EFI_OUT_OF_RESOURCES   Could not add the image due to lack of resources.
  @retval EFI_INVALID_PARAMETER  Image is NULL or ImageId is NULL.
**/
EFI_STATUS
EFIAPI
HiiNewImageEx (
  IN  CONST EFI_HII_IMAGE_EX_PROTOCOL *This,
  IN  EFI_HII_HANDLE                  PackageList,
  OUT EFI_IMAGE_ID                    *ImageId,
  IN  CONST EFI_IMAGE_INPUT           *Image
  )
{
  HII_DATABASE_PRIVATE_DATA           *Private;

  Private = HII_IMAGE_EX_DATABASE_PRIVATE_DATA_FROM_THIS (This);
  return HiiNewImage (&Private->HiiImage, PackageList, ImageId, Image);
}

/**
  Return the information about the image, associated with the package list.
  The prototype of this extension function is the same with EFI_HII_IMAGE_PROTOCOL.GetImage().

  This function is similar to EFI_HII_IMAGE_PROTOCOL.GetImage().The difference is that
  this function will locate all EFI_HII_IMAGE_DECODER_PROTOCOL instances installed in the
  system if the decoder of the certain image type is not supported by the
  EFI_HII_IMAGE_EX_PROTOCOL. The function will attempt to decode the image to the
  EFI_IMAGE_INPUT using the first EFI_HII_IMAGE_DECODER_PROTOCOL instance that
  supports the requested image type.

  @param  This                   A pointer to the EFI_HII_IMAGE_EX_PROTOCOL instance.
  @param  PackageList            The package list in the HII database to search for the
                                 specified image.
  @param  ImageId                The image's id, which is unique within PackageList.
  @param  Image                  Points to the image.

  @retval EFI_SUCCESS            The new image was returned successfully.
  @retval EFI_NOT_FOUND          The image specified by ImageId is not available. The specified
                                 PackageList is not in the Database.
  @retval EFI_INVALID_PARAMETER  Image was NULL or ImageId was 0.
  @retval EFI_OUT_OF_RESOURCES   The bitmap could not be retrieved because there
                                 was not enough memory.

**/
EFI_STATUS
EFIAPI
HiiGetImageEx (
  IN  CONST EFI_HII_IMAGE_EX_PROTOCOL *This,
  IN  EFI_HII_HANDLE                  PackageList,
  IN  EFI_IMAGE_ID                    ImageId,
  OUT EFI_IMAGE_INPUT                 *Image
  )
{
  HII_DATABASE_PRIVATE_DATA           *Private;

  Private = HII_IMAGE_EX_DATABASE_PRIVATE_DATA_FROM_THIS (This);
  return IGetImage (&Private->DatabaseList, PackageList, ImageId, Image, FALSE);
}


/**
  Change the information about the image.

  Same with EFI_HII_IMAGE_PROTOCOL.SetImage(),this protocol invokes
  EFI_HII_IMAGE_PROTOCOL.SetImage()implicitly.

  @param  This                   A pointer to the EFI_HII_IMAGE_EX_PROTOCOL instance.
  @param  PackageList            The package list containing the images.
  @param  ImageId                The image's id, which is unique within PackageList.
  @param  Image                  Points to the image.

  @retval EFI_SUCCESS            The new image was successfully updated.
  @retval EFI_NOT_FOUND          The image specified by ImageId is not in the
                                 database. The specified PackageList is not in
                                 the database.
  @retval EFI_INVALID_PARAMETER  The Image was NULL, the ImageId was 0 or
                                 the Image->Bitmap was NULL.

**/
EFI_STATUS
EFIAPI
HiiSetImageEx (
  IN CONST EFI_HII_IMAGE_EX_PROTOCOL *This,
  IN EFI_HII_HANDLE                  PackageList,
  IN EFI_IMAGE_ID                    ImageId,
  IN CONST EFI_IMAGE_INPUT           *Image
  )
{
  HII_DATABASE_PRIVATE_DATA           *Private;
  Private = HII_IMAGE_EX_DATABASE_PRIVATE_DATA_FROM_THIS (This);
  return HiiSetImage (&Private->HiiImage, PackageList, ImageId, Image);
}


/**
  Renders an image to a bitmap or to the display.

  The prototype of this extension function is the same with
  EFI_HII_IMAGE_PROTOCOL.DrawImage(). This protocol invokes
  EFI_HII_IMAGE_PROTOCOL.DrawImage() implicitly.

  @param  This                   A pointer to the EFI_HII_IMAGE_EX_PROTOCOL instance.
  @param  Flags                  Describes how the image is to be drawn.
  @param  Image                  Points to the image to be displayed.
  @param  Blt                    If this points to a non-NULL on entry, this points
                                 to the image, which is Width pixels wide and
                                 Height pixels high.  The image will be drawn onto
                                 this image and  EFI_HII_DRAW_FLAG_CLIP is implied.
                                 If this points to a NULL on entry, then a buffer
                                 will be allocated to hold the generated image and
                                 the pointer updated on exit. It is the caller's
                                 responsibility to free this buffer.
  @param  BltX                   Specifies the offset from the left and top edge of
                                 the output image of the first pixel in the image.
  @param  BltY                   Specifies the offset from the left and top edge of
                                 the output image of the first pixel in the image.

  @retval EFI_SUCCESS            The image was successfully drawn.
  @retval EFI_OUT_OF_RESOURCES   Unable to allocate an output buffer for Blt.
  @retval EFI_INVALID_PARAMETER  The Image or Blt was NULL.

**/
EFI_STATUS
EFIAPI
HiiDrawImageEx (
  IN CONST EFI_HII_IMAGE_EX_PROTOCOL *This,
  IN EFI_HII_DRAW_FLAGS              Flags,
  IN CONST EFI_IMAGE_INPUT           *Image,
  IN OUT EFI_IMAGE_OUTPUT            **Blt,
  IN UINTN                           BltX,
  IN UINTN                           BltY
  )
{
  HII_DATABASE_PRIVATE_DATA           *Private;
  Private = HII_IMAGE_EX_DATABASE_PRIVATE_DATA_FROM_THIS (This);
  return HiiDrawImage (&Private->HiiImage, Flags, Image, Blt, BltX, BltY);
}


/**
  Renders an image to a bitmap or the screen containing the contents of the specified
  image.

  This function is similar to EFI_HII_IMAGE_PROTOCOL.DrawImageId(). The difference is that
  this function will locate all EFI_HII_IMAGE_DECODER_PROTOCOL instances installed in the
  system if the decoder of the certain image type is not supported by the
  EFI_HII_IMAGE_EX_PROTOCOL. The function will attempt to decode the image to the
  EFI_IMAGE_INPUT using the first EFI_HII_IMAGE_DECODER_PROTOCOL instance that
  supports the requested image type.

  @param  This                   A pointer to the EFI_HII_IMAGE_EX_PROTOCOL instance.
  @param  Flags                  Describes how the image is to be drawn.
  @param  PackageList            The package list in the HII database to search for
                                 the  specified image.
  @param  ImageId                The image's id, which is unique within PackageList.
  @param  Blt                    If this points to a non-NULL on entry, this points
                                 to the image, which is Width pixels wide and
                                 Height pixels high. The image will be drawn onto
                                 this image and EFI_HII_DRAW_FLAG_CLIP is implied.
                                 If this points to a NULL on entry, then a buffer
                                 will be allocated to hold  the generated image
                                 and the pointer updated on exit. It is the caller's
                                 responsibility to free this buffer.
  @param  BltX                   Specifies the offset from the left and top edge of
                                 the output image of the first pixel in the image.
  @param  BltY                   Specifies the offset from the left and top edge of
                                 the output image of the first pixel in the image.

  @retval EFI_SUCCESS            The image was successfully drawn.
  @retval EFI_OUT_OF_RESOURCES   Unable to allocate an output buffer for Blt.
  @retval EFI_INVALID_PARAMETER  The Blt was NULL or ImageId was 0.
  @retval EFI_NOT_FOUND          The image specified by ImageId is not in the database.
                                 The specified PackageList is not in the database.

**/
EFI_STATUS
EFIAPI
HiiDrawImageIdEx (
  IN CONST EFI_HII_IMAGE_EX_PROTOCOL *This,
  IN EFI_HII_DRAW_FLAGS              Flags,
  IN EFI_HII_HANDLE                  PackageList,
  IN EFI_IMAGE_ID                    ImageId,
  IN OUT EFI_IMAGE_OUTPUT            **Blt,
  IN UINTN                           BltX,
  IN UINTN                           BltY
  )
{
  EFI_STATUS                          Status;
  EFI_IMAGE_INPUT                     Image;

  //
  // Check input parameter.
  //
  if (This == NULL || Blt == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  //
  // Get the specified Image.
  //
  Status = HiiGetImageEx (This, PackageList, ImageId, &Image);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Draw this image.
  //
  Status = HiiDrawImageEx (This, Flags, &Image, Blt, BltX, BltY);
  if (Image.Bitmap != NULL) {
    FreePool (Image.Bitmap);
  }
  return Status;
}

/**
  Return the first HII image decoder instance which supports the DecoderName.

  @param BlockType  The image block type.

  @retval Pointer to the HII image decoder instance.
**/
EFI_HII_IMAGE_DECODER_PROTOCOL *
LocateHiiImageDecoder (
  UINT8                          BlockType
  )
{
  EFI_STATUS                     Status;
  EFI_HII_IMAGE_DECODER_PROTOCOL *Decoder;
  EFI_HANDLE                     *Handles;
  UINTN                          HandleNum;
  UINTN                          Index;
  EFI_GUID                       *DecoderNames;
  UINT16                         NumberOfDecoderName;
  UINT16                         DecoderNameIndex;
  EFI_GUID                       *DecoderName;

  switch (BlockType) {
  case EFI_HII_IIBT_IMAGE_JPEG:
    DecoderName = &gEfiHiiImageDecoderNameJpegGuid;
    break;

  case EFI_HII_IIBT_IMAGE_PNG:
    DecoderName = &gEfiHiiImageDecoderNamePngGuid;
    break;

  default:
    ASSERT (FALSE);
    return NULL;
  }

  Status = gBS->LocateHandleBuffer (ByProtocol, &gEfiHiiImageDecoderProtocolGuid, NULL, &HandleNum, &Handles);
  if (EFI_ERROR (Status)) {
    return NULL;
  }
  for (Index = 0; Index < HandleNum; Index++) {
    Status = gBS->HandleProtocol (Handles[Index], &gEfiHiiImageDecoderProtocolGuid, (VOID **) &Decoder);
    if (EFI_ERROR (Status)) {
      continue;
    }

    Status = Decoder->GetImageDecoderName (Decoder, &DecoderNames, &NumberOfDecoderName);
    if (EFI_ERROR (Status)) {
      continue;
    }
    for (DecoderNameIndex = 0; DecoderNameIndex < NumberOfDecoderName; DecoderNameIndex++) {
      if (CompareGuid (DecoderName, &DecoderNames[DecoderNameIndex])) {
        return Decoder;
      }
    }
  }

  return NULL;
}

/**
  This function returns the image information to EFI_IMAGE_OUTPUT. Only the width
  and height are returned to the EFI_IMAGE_OUTPUT instead of decoding the image
  to the buffer. This function is used to get the geometry of the image. This function
  will try to locate all of the EFI_HII_IMAGE_DECODER_PROTOCOL installed on the
  system if the decoder of image type is not supported by the EFI_HII_IMAGE_EX_PROTOCOL.

  @param  This                   A pointer to the EFI_HII_IMAGE_EX_PROTOCOL instance.
  @param  PackageList            Handle of the package list where this image will
                                 be searched.
  @param  ImageId                The image's id, which is unique within PackageList.
  @param  Image                  Points to the image.

  @retval EFI_SUCCESS            The new image was returned successfully.
  @retval EFI_NOT_FOUND          The image specified by ImageId is not in the
                                 database. The specified PackageList is not in the database.
  @retval EFI_BUFFER_TOO_SMALL   The buffer specified by ImageSize is too small to
                                 hold the image.
  @retval EFI_INVALID_PARAMETER  The Image was NULL or the ImageId was 0.
  @retval EFI_OUT_OF_RESOURCES   The bitmap could not be retrieved because there
                                 was not enough memory.

**/
EFI_STATUS
EFIAPI
HiiGetImageInfo (
  IN CONST  EFI_HII_IMAGE_EX_PROTOCOL       *This,
  IN        EFI_HII_HANDLE                  PackageList,
  IN        EFI_IMAGE_ID                    ImageId,
  OUT       EFI_IMAGE_OUTPUT                *Image
  )
{
  EFI_STATUS                              Status;
  HII_DATABASE_PRIVATE_DATA               *Private;
  HII_DATABASE_PACKAGE_LIST_INSTANCE      *PackageListNode;
  HII_IMAGE_PACKAGE_INSTANCE              *ImagePackage;
  EFI_HII_IMAGE_BLOCK                     *CurrentImageBlock;
  EFI_HII_IMAGE_DECODER_PROTOCOL          *Decoder;
  EFI_HII_IMAGE_DECODER_IMAGE_INFO_HEADER *ImageInfo;

  if (Image == NULL || ImageId == 0) {
    return EFI_INVALID_PARAMETER;
  }

  Private = HII_IMAGE_EX_DATABASE_PRIVATE_DATA_FROM_THIS (This);
  PackageListNode = LocatePackageList (&Private->DatabaseList, PackageList);
  if (PackageListNode == NULL) {
    return EFI_NOT_FOUND;
  }
  ImagePackage = PackageListNode->ImagePkg;
  if (ImagePackage == NULL) {
    return EFI_NOT_FOUND;
  }

  //
  // Find the image block specified by ImageId
  //
  CurrentImageBlock = GetImageIdOrAddress (ImagePackage->ImageBlock, &ImageId);
  if (CurrentImageBlock == NULL) {
    return EFI_NOT_FOUND;
  }

  switch (CurrentImageBlock->BlockType) {
  case EFI_HII_IIBT_IMAGE_JPEG:
  case EFI_HII_IIBT_IMAGE_PNG:
    Decoder = LocateHiiImageDecoder (CurrentImageBlock->BlockType);
    if (Decoder == NULL) {
      return EFI_UNSUPPORTED;
    }
    //
    // Use the common block code since the definition of two structures is the same.
    //
    ASSERT (OFFSET_OF (EFI_HII_IIBT_JPEG_BLOCK, Data) == OFFSET_OF (EFI_HII_IIBT_PNG_BLOCK, Data));
    ASSERT (sizeof (((EFI_HII_IIBT_JPEG_BLOCK *) CurrentImageBlock)->Data) ==
            sizeof (((EFI_HII_IIBT_PNG_BLOCK *) CurrentImageBlock)->Data));
    ASSERT (OFFSET_OF (EFI_HII_IIBT_JPEG_BLOCK, Size) == OFFSET_OF (EFI_HII_IIBT_PNG_BLOCK, Size));
    ASSERT (sizeof (((EFI_HII_IIBT_JPEG_BLOCK *) CurrentImageBlock)->Size) ==
            sizeof (((EFI_HII_IIBT_PNG_BLOCK *) CurrentImageBlock)->Size));
    Status = Decoder->GetImageInfo (
      Decoder,
      ((EFI_HII_IIBT_JPEG_BLOCK *) CurrentImageBlock)->Data,
      ((EFI_HII_IIBT_JPEG_BLOCK *) CurrentImageBlock)->Size,
      &ImageInfo
    );

    //
    // Spec requires to use the first capable image decoder instance.
    // The first image decoder instance may fail to decode the image.
    //
    if (!EFI_ERROR (Status)) {
      Image->Height = ImageInfo->ImageHeight;
      Image->Width = ImageInfo->ImageWidth;
      Image->Image.Bitmap = NULL;
      FreePool (ImageInfo);
    }
    return Status;

  case EFI_HII_IIBT_IMAGE_1BIT_TRANS:
  case EFI_HII_IIBT_IMAGE_4BIT_TRANS:
  case EFI_HII_IIBT_IMAGE_8BIT_TRANS:
  case EFI_HII_IIBT_IMAGE_1BIT:
  case EFI_HII_IIBT_IMAGE_4BIT:
  case EFI_HII_IIBT_IMAGE_8BIT:
    //
    // Use the common block code since the definition of these structures is the same.
    //
    Image->Width = ReadUnaligned16 (&((EFI_HII_IIBT_IMAGE_1BIT_BLOCK *) CurrentImageBlock)->Bitmap.Width);
    Image->Height = ReadUnaligned16 (&((EFI_HII_IIBT_IMAGE_1BIT_BLOCK *) CurrentImageBlock)->Bitmap.Height);
    Image->Image.Bitmap = NULL;
    return EFI_SUCCESS;

  case EFI_HII_IIBT_IMAGE_24BIT_TRANS:
  case EFI_HII_IIBT_IMAGE_24BIT:
    Image->Width = ReadUnaligned16 ((VOID *) &((EFI_HII_IIBT_IMAGE_24BIT_BLOCK *) CurrentImageBlock)->Bitmap.Width);
    Image->Height = ReadUnaligned16 ((VOID *) &((EFI_HII_IIBT_IMAGE_24BIT_BLOCK *) CurrentImageBlock)->Bitmap.Height);
    Image->Image.Bitmap = NULL;
    return EFI_SUCCESS;

  default:
    return EFI_NOT_FOUND;
  }
}