/** @file
  Example program using BltLib

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

**/

#include <Uefi.h>
#include <Library/BltLib.h>
#include <Library/DebugLib.h>
#include <Library/UefiLib.h>
#include <Library/UefiApplicationEntryPoint.h>
#include <Library/UefiBootServicesTableLib.h>


UINT64
ReadTimestamp (
  VOID
  )
{
#if defined (MDE_CPU_IA32) || defined (MDE_CPU_X64)
  return AsmReadTsc ();
#else
#error ReadTimestamp not supported for this architecture!
#endif
}

UINT32
Rand32 (
  VOID
  )
{
  UINTN    Found;
  INTN     Bits;
  UINT64   Tsc1;
  UINT64   Tsc2;
  UINT64   TscBits;
  UINT32   R32;

  R32 = 0;
  Found = 0;
  Tsc1 = ReadTimestamp ();
  Tsc2 = ReadTimestamp ();
  do {
    Tsc2 = ReadTimestamp ();
    TscBits = Tsc2 ^ Tsc1;
    Bits = HighBitSet64 (TscBits);
    if (Bits > 0) {
      Bits = Bits - 1;
    }
    R32 = (UINT32)((R32 << Bits) |
                   RShiftU64 (LShiftU64 (TscBits, (UINTN) (64 - Bits)), (UINTN) (64 - Bits)));
    Found = Found + Bits;
  } while (Found < 32);

  return R32;
}


VOID
TestFills (
  VOID
  )
{
  EFI_GRAPHICS_OUTPUT_BLT_PIXEL  Color;
  UINTN                          Loop;
  UINTN                          X;
  UINTN                          Y;
  UINTN                          W;
  UINTN                          H;
  UINTN                          Width;
  UINTN                          Height;

  BltLibGetSizes (&Width, &Height);
  for (Loop = 0; Loop < 10000; Loop++) {
    W = Width - (Rand32 () % Width);
    H = Height - (Rand32 () % Height);
    if (W != Width) {
      X = Rand32 () % (Width - W);
    } else {
      X = 0;
    }
    if (H != Height) {
      Y = Rand32 () % (Height - H);
    } else {
      Y = 0;
    }
    *(UINT32*) (&Color) = Rand32 () & 0xffffff;
    BltLibVideoFill (&Color, X, Y, W, H);
  }
}


VOID
TestColor1 (
  VOID
  )
{
  EFI_GRAPHICS_OUTPUT_BLT_PIXEL  Color;
  UINTN                          X;
  UINTN                          Y;
  UINTN                          Width;
  UINTN                          Height;

  BltLibGetSizes (&Width, &Height);
  *(UINT32*) (&Color) = 0;

  for (Y = 0; Y < Height; Y++) {
    for (X = 0; X < Width; X++) {
      Color.Red =   (UINT8) ((X * 0x100) / Width);
      Color.Green = (UINT8) ((Y * 0x100) / Height);
      Color.Blue =  (UINT8) ((Y * 0x100) / Height);
      BltLibVideoFill (&Color, X, Y, 1, 1);
    }
  }
}


UINT32
Uint32SqRt (
  IN  UINT32  Uint32
  )
{
  UINT32 Mask;
  UINT32 SqRt;
  UINT32 SqRtMask;
  UINT32 Squared;

  if (Uint32 == 0) {
    return 0;
  }

  for (SqRt = 0, Mask = (UINT32) (1 << (HighBitSet32 (Uint32) / 2));
       Mask != 0;
       Mask = Mask >> 1
      ) {
    SqRtMask = SqRt | Mask;
    //DEBUG ((EFI_D_INFO, "Uint32=0x%x SqRtMask=0x%x\n", Uint32, SqRtMask));
    Squared = (UINT32) (SqRtMask * SqRtMask);
    if (Squared > Uint32) {
      continue;
    } else if (Squared < Uint32) {
      SqRt = SqRtMask;
    } else {
      return SqRtMask;
    }
  }

  return SqRt;
}


UINT32
Uint32Dist (
  IN UINTN X,
  IN UINTN Y
  )
{
  return Uint32SqRt ((UINT32) ((X * X) + (Y * Y)));
}

UINT8
GetTriColor (
  IN UINTN ColorDist,
  IN UINTN TriWidth
  )
{
  return (UINT8) (((TriWidth - ColorDist) * 0x100) / TriWidth);
  //return (((TriWidth * TriWidth - ColorDist * ColorDist) * 0x100) / (TriWidth * TriWidth));
}

VOID
TestColor (
  VOID
  )
{
  EFI_GRAPHICS_OUTPUT_BLT_PIXEL  Color;
  UINTN                          X, Y;
  UINTN                          X1, X2, X3;
  UINTN                          Y1, Y2;
  UINTN                          LineWidth, TriWidth, ScreenWidth;
  UINTN                          TriHeight, ScreenHeight;
  UINT32                         ColorDist;

  BltLibGetSizes (&ScreenWidth, &ScreenHeight);
  *(UINT32*) (&Color) = 0;
  BltLibVideoFill (&Color, 0, 0, ScreenWidth, ScreenHeight);

  TriWidth = (UINTN) DivU64x32 (
                       MultU64x32 (11547005, (UINT32) ScreenHeight),
                       10000000
                       );
  TriHeight = (UINTN) DivU64x32 (
                        MultU64x32 (8660254, (UINT32) ScreenWidth),
                        10000000
                        );
  if (TriWidth > ScreenWidth) {
    DEBUG ((EFI_D_INFO, "TriWidth at %d was too big\n", TriWidth));
    TriWidth = ScreenWidth;
  } else if (TriHeight > ScreenHeight) {
    DEBUG ((EFI_D_INFO, "TriHeight at %d was too big\n", TriHeight));
    TriHeight = ScreenHeight;
  }

  DEBUG ((EFI_D_INFO, "Triangle Width: %d; Height: %d\n", TriWidth, TriHeight));

  X1 = (ScreenWidth - TriWidth) / 2;
  X3 = X1 + TriWidth - 1;
  X2 = (X1 + X3) / 2;
  Y2 = (ScreenHeight - TriHeight) / 2;
  Y1 = Y2 + TriHeight - 1;

  for (Y = Y2; Y <= Y1; Y++) {
    LineWidth =
      (UINTN) DivU64x32 (
                MultU64x32 (11547005, (UINT32) (Y - Y2)),
                20000000
                );
    for (X = X2 - LineWidth; X < (X2 + LineWidth); X++) {
      ColorDist = Uint32Dist(X - X1, Y1 - Y);
      Color.Red = GetTriColor (ColorDist, TriWidth);

      ColorDist = Uint32Dist((X < X2) ? X2 - X : X - X2, Y - Y2);
      Color.Green = GetTriColor (ColorDist, TriWidth);

      ColorDist = Uint32Dist(X3 - X, Y1 - Y);
      Color.Blue = GetTriColor (ColorDist, TriWidth);

      BltLibVideoFill (&Color, X, Y, 1, 1);
    }
  }
}


/**
  The user Entry Point for Application. The user code starts with this function
  as the real entry point for the application.

  @param[in] ImageHandle    The firmware allocated handle for the EFI image.
  @param[in] SystemTable    A pointer to the EFI System Table.

  @retval EFI_SUCCESS       The entry point is executed successfully.
  @retval other             Some error occurs when executing this entry point.

**/
EFI_STATUS
EFIAPI
UefiMain (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_STATUS                     Status;
  EFI_GRAPHICS_OUTPUT_PROTOCOL   *Gop;

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

  Status = BltLibConfigure (
             (VOID*)(UINTN) Gop->Mode->FrameBufferBase,
             Gop->Mode->Info
             );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  TestFills ();

  TestColor ();

  return EFI_SUCCESS;
}
