/** @file
  Copyright (c) 2020, PMheart. All rights reserved.
  SPDX-License-Identifier: BSD-3-Clause
**/

#include <UserBootServices.h>

#include <stdio.h>

EFI_BOOT_SERVICES mBootServices = {
  .RaiseTPL                  = DummyRaiseTPL,
  .LocateProtocol            = DummyLocateProtocol,
  .AllocatePages             = DummyAllocatePages,
  .InstallConfigurationTable = DummyInstallConfigurationTable
};

EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL mConOut = {
  .OutputString = NullTextOutputString
};

EFI_SYSTEM_TABLE mSystemTable = {
  .BootServices = &mBootServices,
  .ConOut       = &mConOut
};

EFI_RUNTIME_SERVICES  mRuntimeServices = {};

EFI_SYSTEM_TABLE      *gST  = &mSystemTable;
EFI_BOOT_SERVICES     *gBS  = &mBootServices;

EFI_HANDLE            gImageHandle = (EFI_HANDLE) 0xDEADBABEULL;

BOOLEAN               mPostEBS  = FALSE;
EFI_SYSTEM_TABLE      *mDebugST = &mSystemTable;

EFI_RUNTIME_SERVICES  *gRT      = &mRuntimeServices;

#define CONFIG_TABLE_SIZE_INCREASED  0x10
UINTN mSystemTableAllocateSize  = 0ULL;

EFI_TPL
EFIAPI
DummyRaiseTPL (
  IN EFI_TPL      NewTpl
  )
{
  ASSERT (FALSE);

  return 0;
}

EFI_STATUS
EFIAPI
DummyLocateProtocol (
  IN  EFI_GUID  *Protocol,
  IN  VOID      *Registration, OPTIONAL
  OUT VOID      **Interface
  )
{
  return EFI_NOT_FOUND;
}

EFI_STATUS
EFIAPI
DummyAllocatePages (
  IN     EFI_ALLOCATE_TYPE            Type,
  IN     EFI_MEMORY_TYPE              MemoryType,
  IN     UINTN                        Pages,
  IN OUT EFI_PHYSICAL_ADDRESS         *Memory
  )
{
  *Memory = (UINTN) AllocatePages (Pages);

  return Memory != NULL ? EFI_SUCCESS : EFI_NOT_FOUND;
}

EFI_STATUS
EFIAPI
DummyInstallConfigurationTable (
  IN EFI_GUID                 *Guid,
  IN VOID                     *Table
  )
{
  //
  // NOTE: This code is adapted from original EDK II implementations.
  //

  UINTN                    Index;
  EFI_CONFIGURATION_TABLE  *EfiConfigurationTable;
  EFI_CONFIGURATION_TABLE  *OldTable;

  //
  // If Guid is NULL, then this operation cannot be performed
  //
  if (Guid == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  EfiConfigurationTable = gST->ConfigurationTable;

  //
  // Search all the table for an entry that matches Guid
  //
  for (Index = 0; Index < gST->NumberOfTableEntries; Index++) {
    if (CompareGuid (Guid, &(gST->ConfigurationTable[Index].VendorGuid))) {
      break;
    }
  }

  if (Index < gST->NumberOfTableEntries) {
    //
    // A match was found, so this is either a modify or a delete operation
    //
    if (Table != NULL) {
      //
      // If Table is not NULL, then this is a modify operation.
      // Modify the table entry and return.
      //
      gST->ConfigurationTable[Index].VendorTable = Table;

      return EFI_SUCCESS;
    }

    //
    // A match was found and Table is NULL, so this is a delete operation.
    //
    gST->NumberOfTableEntries--;

    //
    // Copy over deleted entry
    //
    CopyMem (
      &(EfiConfigurationTable[Index]),
      &(gST->ConfigurationTable[Index + 1]),
      (gST->NumberOfTableEntries - Index) * sizeof (EFI_CONFIGURATION_TABLE)
      );
  } else {
    //
    // No matching GUIDs were found, so this is an add operation.
    //
    if (Table == NULL) {
      //
      // If Table is NULL on an add operation, then return an error.
      //
      return EFI_NOT_FOUND;
    }

    //
    // Assume that Index == gST->NumberOfTableEntries
    //
    if ((Index * sizeof (EFI_CONFIGURATION_TABLE)) >= mSystemTableAllocateSize) {
      //
      // Allocate a table with one additional entry.
      //
      mSystemTableAllocateSize += (CONFIG_TABLE_SIZE_INCREASED * sizeof (EFI_CONFIGURATION_TABLE));
      EfiConfigurationTable     = AllocatePool (mSystemTableAllocateSize);
      if (EfiConfigurationTable == NULL) {
        //
        // If a new table could not be allocated, then return an error.
        //
        return EFI_OUT_OF_RESOURCES;
      }

      if (gST->ConfigurationTable != NULL) {
        //
        // Copy the old table to the new table.
        //
        CopyMem (
          EfiConfigurationTable,
          gST->ConfigurationTable,
          Index * sizeof (EFI_CONFIGURATION_TABLE)
          );

        //
        // Record the old table pointer.
        //
        OldTable = gST->ConfigurationTable;

        //
        // As the CoreInstallConfigurationTable() may be re-entered by CoreFreePool()
        // in its calling stack, updating System table to the new table pointer must
        // be done before calling CoreFreePool() to free the old table.
        // It can make sure the gST->ConfigurationTable point to the new table
        // and avoid the errors of use-after-free to the old table by the reenter of
        // CoreInstallConfigurationTable() in CoreFreePool()'s calling stack.
        //
        gST->ConfigurationTable = EfiConfigurationTable;

        //
        // Free the old table after updating System Table to the new table pointer.
        //
        FreePool (OldTable);
      } else {
        //
        // Update System Table
        //
        gST->ConfigurationTable = EfiConfigurationTable;
      }
    }

    //
    // Fill in the new entry
    //
    CopyGuid ((VOID *) &EfiConfigurationTable[Index].VendorGuid, Guid);
    EfiConfigurationTable[Index].VendorTable  = Table;

    //
    // This is an add operation, so increment the number of table entries
    //
    gST->NumberOfTableEntries++;
  }

  return EFI_SUCCESS;
}

EFI_STATUS
EFIAPI
NullTextOutputString (
  IN EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL  *This,
  IN CHAR16                           *String
  )
{
  while (*String != '\0') {
    if (*String != '\r') {
      //
      // Cast string to CHAR8 to truncate unicode symbols.
      //
      putchar ((CHAR8) *String);
    }

    ++String;
  }
  
  return EFI_SUCCESS;
}