/** @file

  The implementation of EFI REST Resource JSON to C structure convertor
  Protocol.

  (C) Copyright 2020 Hewlett Packard Enterprise Development LP<BR>

  SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include <Uefi.h>
#include <Protocol/RestJsonStructure.h>
#include "RestJsonStructureInternal.h"

LIST_ENTRY  mRestJsonStructureList;
EFI_HANDLE  mProtocolHandle;

/**
  This function registers Restful resource interpreter for the
  specific schema.

  @param[in]    This                     This is the EFI_REST_JSON_STRUCTURE_PROTOCOL instance.
  @param[in]    JsonStructureSupported   The type and version of REST JSON resource which this converter
                                         supports.
  @param[in]    ToStructure              The function to convert REST JSON resource to structure.
  @param[in]    ToJson                   The function to convert REST JSON structure to JSON in text format.
  @param[in]    DestroyStructure         Destroy REST JSON structure returned in ToStructure()  function.

  @retval EFI_SUCCESS             Register successfully.
  @retval Others                  Fail to register.

**/
EFI_STATUS
EFIAPI
RestJsonStructureRegister (
  IN EFI_REST_JSON_STRUCTURE_PROTOCOL           *This,
  IN EFI_REST_JSON_STRUCTURE_SUPPORTED          *JsonStructureSupported,
  IN EFI_REST_JSON_STRUCTURE_TO_STRUCTURE       ToStructure,
  IN EFI_REST_JSON_STRUCTURE_TO_JSON            ToJson,
  IN EFI_REST_JSON_STRUCTURE_DESTORY_STRUCTURE  DestroyStructure
  )
{
  UINTN                                   NumberOfNS;
  UINTN                                   Index;
  LIST_ENTRY                              *ThisList;
  REST_JSON_STRUCTURE_INSTANCE            *Instance;
  EFI_REST_JSON_RESOURCE_TYPE_IDENTIFIER  *CloneSupportedInterpId;
  EFI_REST_JSON_STRUCTURE_SUPPORTED       *ThisSupportedInterp;

  if ((This == NULL) ||
      (ToStructure == NULL) ||
      (ToJson == NULL) ||
      (DestroyStructure == NULL) ||
      (JsonStructureSupported == NULL)
      )
  {
    return EFI_INVALID_PARAMETER;
  }

  //
  // Check how many name space interpreter can interpret.
  //
  ThisList   = &JsonStructureSupported->NextSupportedRsrcInterp;
  NumberOfNS = 1;
  while (TRUE) {
    if (ThisList->ForwardLink == &JsonStructureSupported->NextSupportedRsrcInterp) {
      break;
    } else {
      ThisList = ThisList->ForwardLink;
      NumberOfNS++;
    }
  }

  Instance =
    (REST_JSON_STRUCTURE_INSTANCE *)AllocateZeroPool (sizeof (REST_JSON_STRUCTURE_INSTANCE) + NumberOfNS * sizeof (EFI_REST_JSON_RESOURCE_TYPE_IDENTIFIER));
  if (Instance == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  InitializeListHead (&Instance->NextRestJsonStructureInstance);
  Instance->NumberOfNameSpaceToConvert = NumberOfNS;
  Instance->SupportedRsrcIndentifier   = (EFI_REST_JSON_RESOURCE_TYPE_IDENTIFIER *)((REST_JSON_STRUCTURE_INSTANCE *)Instance + 1);
  //
  // Copy supported resource identifer interpreter.
  //
  CloneSupportedInterpId = Instance->SupportedRsrcIndentifier;
  ThisSupportedInterp    = JsonStructureSupported;
  for (Index = 0; Index < NumberOfNS; Index++) {
    CopyMem ((VOID *)CloneSupportedInterpId, (VOID *)&ThisSupportedInterp->RestResourceInterp, sizeof (EFI_REST_JSON_RESOURCE_TYPE_IDENTIFIER));
    ThisSupportedInterp = (EFI_REST_JSON_STRUCTURE_SUPPORTED *)ThisSupportedInterp->NextSupportedRsrcInterp.ForwardLink;
    CloneSupportedInterpId++;
  }

  Instance->JsonToStructure  = ToStructure;
  Instance->StructureToJson  = ToJson;
  Instance->DestroyStructure = DestroyStructure;
  InsertTailList (&mRestJsonStructureList, &Instance->NextRestJsonStructureInstance);
  return EFI_SUCCESS;
}

/**
  This function check if this interpreter instance support the given namesapce.

  @param[in]    This                EFI_REST_JSON_STRUCTURE_PROTOCOL instance.
  @param[in]    InterpreterInstance REST_JSON_STRUCTURE_INSTANCE
  @param[in]    RsrcTypeIdentifier  Resource type identifier.
  @param[in]    ResourceRaw         Given Restful resource.
  @param[out]   RestJSonHeader      Property interpreted from given ResourceRaw.

  @retval EFI_SUCCESS
  @retval Others.

**/
EFI_STATUS
InterpreterInstanceToStruct (
  IN EFI_REST_JSON_STRUCTURE_PROTOCOL        *This,
  IN REST_JSON_STRUCTURE_INSTANCE            *InterpreterInstance,
  IN EFI_REST_JSON_RESOURCE_TYPE_IDENTIFIER  *RsrcTypeIdentifier OPTIONAL,
  IN CHAR8                                   *ResourceRaw,
  OUT EFI_REST_JSON_STRUCTURE_HEADER         **RestJSonHeader
  )
{
  UINTN                                   Index;
  EFI_STATUS                              Status;
  EFI_REST_JSON_RESOURCE_TYPE_IDENTIFIER  *ThisSupportedRsrcTypeId;

  if ((This == NULL) ||
      (InterpreterInstance == NULL) ||
      (ResourceRaw == NULL) ||
      (RestJSonHeader == NULL)
      )
  {
    return EFI_INVALID_PARAMETER;
  }

  Status = EFI_UNSUPPORTED;
  if (RsrcTypeIdentifier == NULL) {
    //
    // No resource type identifier, send to intepreter anyway.
    // Interpreter may recognize this resource.
    //
    Status = InterpreterInstance->JsonToStructure (
                                    This,
                                    NULL,
                                    ResourceRaw,
                                    RestJSonHeader
                                    );
  } else {
    //
    // Check if the namesapce and version is supported by this interpreter.
    //
    ThisSupportedRsrcTypeId = InterpreterInstance->SupportedRsrcIndentifier;
    for (Index = 0; Index < InterpreterInstance->NumberOfNameSpaceToConvert; Index++) {
      if (AsciiStrCmp (
            RsrcTypeIdentifier->NameSpace.ResourceTypeName,
            ThisSupportedRsrcTypeId->NameSpace.ResourceTypeName
            ) == 0)
      {
        if ((RsrcTypeIdentifier->NameSpace.MajorVersion == NULL) &&
            (RsrcTypeIdentifier->NameSpace.MinorVersion == NULL) &&
            (RsrcTypeIdentifier->NameSpace.ErrataVersion == NULL)
            )
        {
          //
          // Don't check version of this resource type identifier.
          //
          Status = InterpreterInstance->JsonToStructure (
                                          This,
                                          RsrcTypeIdentifier,
                                          ResourceRaw,
                                          RestJSonHeader
                                          );
          break;
        } else {
          //
          // Check version.
          //
          if ((AsciiStrCmp (
                 RsrcTypeIdentifier->NameSpace.MajorVersion,
                 ThisSupportedRsrcTypeId->NameSpace.MajorVersion
                 ) == 0) &&
              (AsciiStrCmp (
                 RsrcTypeIdentifier->NameSpace.MinorVersion,
                 ThisSupportedRsrcTypeId->NameSpace.MinorVersion
                 ) == 0) &&
              (AsciiStrCmp (
                 RsrcTypeIdentifier->NameSpace.ErrataVersion,
                 ThisSupportedRsrcTypeId->NameSpace.ErrataVersion
                 ) == 0))
          {
            Status = InterpreterInstance->JsonToStructure (
                                            This,
                                            RsrcTypeIdentifier,
                                            ResourceRaw,
                                            RestJSonHeader
                                            );
            break;
          }
        }
      }

      ThisSupportedRsrcTypeId++;
    }
  }

  return Status;
}

/**
  This function converts JSON C structure to JSON property.

  @param[in]    This                 EFI_REST_JSON_STRUCTURE_PROTOCOL instance.
  @param[in]    InterpreterInstance  REST_JSON_STRUCTURE_INSTANCE
  @param[in]    RestJSonHeader       Resource type identifier.
  @param[out]   ResourceRaw          Output in JSON text format.

  @retval EFI_SUCCESS
  @retval Others.

**/
EFI_STATUS
InterpreterEfiStructToInstance (
  IN EFI_REST_JSON_STRUCTURE_PROTOCOL  *This,
  IN REST_JSON_STRUCTURE_INSTANCE      *InterpreterInstance,
  IN EFI_REST_JSON_STRUCTURE_HEADER    *RestJSonHeader,
  OUT CHAR8                            **ResourceRaw
  )
{
  UINTN                                   Index;
  EFI_STATUS                              Status;
  EFI_REST_JSON_RESOURCE_TYPE_IDENTIFIER  *ThisSupportedRsrcTypeId;
  EFI_REST_JSON_RESOURCE_TYPE_IDENTIFIER  *RsrcTypeIdentifier;

  if ((This == NULL) ||
      (InterpreterInstance == NULL) ||
      (RestJSonHeader == NULL) ||
      (ResourceRaw == NULL)
      )
  {
    return EFI_INVALID_PARAMETER;
  }

  RsrcTypeIdentifier = &RestJSonHeader->JsonRsrcIdentifier;
  if ((RsrcTypeIdentifier == NULL) ||
      (RsrcTypeIdentifier->NameSpace.ResourceTypeName == NULL) ||
      (RsrcTypeIdentifier->NameSpace.MajorVersion == NULL) ||
      (RsrcTypeIdentifier->NameSpace.MinorVersion == NULL) ||
      (RsrcTypeIdentifier->NameSpace.ErrataVersion == NULL)
      )
  {
    return EFI_INVALID_PARAMETER;
  }

  //
  // Check if the namesapce and version is supported by this interpreter.
  //
  Status                  = EFI_UNSUPPORTED;
  ThisSupportedRsrcTypeId = InterpreterInstance->SupportedRsrcIndentifier;
  for (Index = 0; Index < InterpreterInstance->NumberOfNameSpaceToConvert; Index++) {
    if (AsciiStrCmp (
          RsrcTypeIdentifier->NameSpace.ResourceTypeName,
          ThisSupportedRsrcTypeId->NameSpace.ResourceTypeName
          ) == 0)
    {
      //
      // Check version.
      //
      if ((AsciiStrCmp (
             RsrcTypeIdentifier->NameSpace.MajorVersion,
             ThisSupportedRsrcTypeId->NameSpace.MajorVersion
             ) == 0) &&
          (AsciiStrCmp (
             RsrcTypeIdentifier->NameSpace.MinorVersion,
             ThisSupportedRsrcTypeId->NameSpace.MinorVersion
             ) == 0) &&
          (AsciiStrCmp (
             RsrcTypeIdentifier->NameSpace.ErrataVersion,
             ThisSupportedRsrcTypeId->NameSpace.ErrataVersion
             ) == 0))
      {
        Status = InterpreterInstance->StructureToJson (
                                        This,
                                        RestJSonHeader,
                                        ResourceRaw
                                        );
        break;
      }
    }

    ThisSupportedRsrcTypeId++;
  }

  return Status;
}

/**
  This function destory REST property structure.

  @param[in]    This                 EFI_REST_JSON_STRUCTURE_PROTOCOL instance.
  @param[in]    InterpreterInstance  REST_JSON_STRUCTURE_INSTANCE
  @param[in]    RestJSonHeader       Property interpreted from given ResourceRaw.

  @retval EFI_SUCCESS
  @retval Others.

**/
EFI_STATUS
InterpreterInstanceDestoryJsonStruct (
  IN EFI_REST_JSON_STRUCTURE_PROTOCOL  *This,
  IN REST_JSON_STRUCTURE_INSTANCE      *InterpreterInstance,
  IN EFI_REST_JSON_STRUCTURE_HEADER    *RestJSonHeader
  )
{
  UINTN                                   Index;
  EFI_STATUS                              Status;
  EFI_REST_JSON_RESOURCE_TYPE_IDENTIFIER  *ThisSupportedRsrcTypeId;

  if ((This == NULL) ||
      (InterpreterInstance == NULL) ||
      (RestJSonHeader == NULL)
      )
  {
    return EFI_INVALID_PARAMETER;
  }

  Status = EFI_UNSUPPORTED;
  //
  // Check if the namesapce and version is supported by this interpreter.
  //
  ThisSupportedRsrcTypeId = InterpreterInstance->SupportedRsrcIndentifier;
  for (Index = 0; Index < InterpreterInstance->NumberOfNameSpaceToConvert; Index++) {
    if (AsciiStrCmp (
          RestJSonHeader->JsonRsrcIdentifier.NameSpace.ResourceTypeName,
          ThisSupportedRsrcTypeId->NameSpace.ResourceTypeName
          ) == 0)
    {
      if ((RestJSonHeader->JsonRsrcIdentifier.NameSpace.MajorVersion == NULL) &&
          (RestJSonHeader->JsonRsrcIdentifier.NameSpace.MinorVersion == NULL) &&
          (RestJSonHeader->JsonRsrcIdentifier.NameSpace.ErrataVersion == NULL)
          )
      {
        //
        // Don't check version of this resource type identifier.
        //
        Status = InterpreterInstance->DestroyStructure (
                                        This,
                                        RestJSonHeader
                                        );
        break;
      } else {
        //
        // Check version.
        //
        if ((AsciiStrCmp (
               RestJSonHeader->JsonRsrcIdentifier.NameSpace.MajorVersion,
               ThisSupportedRsrcTypeId->NameSpace.MajorVersion
               ) == 0) &&
            (AsciiStrCmp (
               RestJSonHeader->JsonRsrcIdentifier.NameSpace.MinorVersion,
               ThisSupportedRsrcTypeId->NameSpace.MinorVersion
               ) == 0) &&
            (AsciiStrCmp (
               RestJSonHeader->JsonRsrcIdentifier.NameSpace.ErrataVersion,
               ThisSupportedRsrcTypeId->NameSpace.ErrataVersion
               ) == 0))
        {
          Status = InterpreterInstance->DestroyStructure (
                                          This,
                                          RestJSonHeader
                                          );
          break;
        }
      }
    }

    ThisSupportedRsrcTypeId++;
  }

  return Status;
}

/**
  This function translates the given JSON text to JSON C Structure.

  @param[in]    This                EFI_REST_JSON_STRUCTURE_PROTOCOL instance.
  @param[in]    RsrcTypeIdentifier  Resource type identifier.
  @param[in]    ResourceJsonText    Given Restful resource.
  @param[out]   JsonStructure       Property interpreted from given ResourceRaw.

  @retval EFI_SUCCESS
  @retval Others.

**/
EFI_STATUS
EFIAPI
RestJsonStructureToStruct (
  IN EFI_REST_JSON_STRUCTURE_PROTOCOL        *This,
  IN EFI_REST_JSON_RESOURCE_TYPE_IDENTIFIER  *RsrcTypeIdentifier OPTIONAL,
  IN CHAR8                                   *ResourceJsonText,
  OUT EFI_REST_JSON_STRUCTURE_HEADER         **JsonStructure
  )
{
  EFI_STATUS                    Status;
  REST_JSON_STRUCTURE_INSTANCE  *Instance;

  if ((This == NULL) ||
      (ResourceJsonText == NULL) ||
      (JsonStructure == NULL)
      )
  {
    return EFI_INVALID_PARAMETER;
  }

  if (IsListEmpty (&mRestJsonStructureList)) {
    return EFI_UNSUPPORTED;
  }

  Status   = EFI_SUCCESS;
  Instance = (REST_JSON_STRUCTURE_INSTANCE *)GetFirstNode (&mRestJsonStructureList);
  while (TRUE) {
    Status = InterpreterInstanceToStruct (
               This,
               Instance,
               RsrcTypeIdentifier,
               ResourceJsonText,
               JsonStructure
               );
    if (!EFI_ERROR (Status)) {
      break;
    }

    if (IsNodeAtEnd (&mRestJsonStructureList, &Instance->NextRestJsonStructureInstance)) {
      Status = EFI_UNSUPPORTED;
      break;
    }

    Instance = (REST_JSON_STRUCTURE_INSTANCE *)GetNextNode (&mRestJsonStructureList, &Instance->NextRestJsonStructureInstance);
  }

  return Status;
}

/**
  This function destory REST property EFI structure which returned in
  JsonToStructure().

  @param[in]    This            EFI_REST_JSON_STRUCTURE_PROTOCOL instance.
  @param[in]    RestJSonHeader  Property to destory.

  @retval EFI_SUCCESS
  @retval Others

**/
EFI_STATUS
EFIAPI
RestJsonStructureDestroyStruct (
  IN EFI_REST_JSON_STRUCTURE_PROTOCOL  *This,
  IN EFI_REST_JSON_STRUCTURE_HEADER    *RestJSonHeader
  )
{
  EFI_STATUS                    Status;
  REST_JSON_STRUCTURE_INSTANCE  *Instance;

  if ((This == NULL) || (RestJSonHeader == NULL)) {
    return EFI_INVALID_PARAMETER;
  }

  if (IsListEmpty (&mRestJsonStructureList)) {
    return EFI_UNSUPPORTED;
  }

  Status   = EFI_SUCCESS;
  Instance = (REST_JSON_STRUCTURE_INSTANCE *)GetFirstNode (&mRestJsonStructureList);
  while (TRUE) {
    Status = InterpreterInstanceDestoryJsonStruct (
               This,
               Instance,
               RestJSonHeader
               );
    if (!EFI_ERROR (Status)) {
      break;
    }

    if (IsNodeAtEnd (&mRestJsonStructureList, &Instance->NextRestJsonStructureInstance)) {
      Status = EFI_UNSUPPORTED;
      break;
    }

    Instance = (REST_JSON_STRUCTURE_INSTANCE *)GetNextNode (&mRestJsonStructureList, &Instance->NextRestJsonStructureInstance);
  }

  return Status;
}

/**
  This function translates the given JSON C Structure to JSON text.

  @param[in]    This            EFI_REST_JSON_STRUCTURE_PROTOCOL instance.
  @param[in]    RestJSonHeader  Given Restful resource.
  @param[out]   ResourceRaw     Resource in RESTfuls service oriented.

  @retval EFI_SUCCESS
  @retval Others             Fail to remove the entry

**/
EFI_STATUS
EFIAPI
RestJsonStructureToJson (
  IN EFI_REST_JSON_STRUCTURE_PROTOCOL  *This,
  IN EFI_REST_JSON_STRUCTURE_HEADER    *RestJSonHeader,
  OUT CHAR8                            **ResourceRaw
  )
{
  EFI_STATUS                    Status;
  REST_JSON_STRUCTURE_INSTANCE  *Instance;

  if ((This == NULL) || (RestJSonHeader == NULL) || (ResourceRaw == NULL)) {
    return EFI_INVALID_PARAMETER;
  }

  if (IsListEmpty (&mRestJsonStructureList)) {
    return EFI_UNSUPPORTED;
  }

  Status   = EFI_SUCCESS;
  Instance = (REST_JSON_STRUCTURE_INSTANCE *)GetFirstNode (&mRestJsonStructureList);
  while (TRUE) {
    Status = InterpreterEfiStructToInstance (
               This,
               Instance,
               RestJSonHeader,
               ResourceRaw
               );
    if (!EFI_ERROR (Status)) {
      break;
    }

    if (IsNodeAtEnd (&mRestJsonStructureList, &Instance->NextRestJsonStructureInstance)) {
      Status = EFI_UNSUPPORTED;
      break;
    }

    Instance = (REST_JSON_STRUCTURE_INSTANCE *)GetNextNode (&mRestJsonStructureList, &Instance->NextRestJsonStructureInstance);
  }

  return Status;
}

EFI_REST_JSON_STRUCTURE_PROTOCOL  mRestJsonStructureProtocol = {
  RestJsonStructureRegister,
  RestJsonStructureToStruct,
  RestJsonStructureToJson,
  RestJsonStructureDestroyStruct
};

/**
  This is the declaration of an EFI image entry point.

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

  @retval EFI_SUCCESS           The operation completed successfully.
  @retval Others                An unexpected error occurred.
**/
EFI_STATUS
EFIAPI
RestJsonStructureEntryPoint (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_STATUS  Status;

  InitializeListHead (&mRestJsonStructureList);
  //
  // Install the Restful Resource Interpreter Protocol.
  //
  mProtocolHandle = NULL;
  Status          = gBS->InstallProtocolInterface (
                           &mProtocolHandle,
                           &gEfiRestJsonStructureProtocolGuid,
                           EFI_NATIVE_INTERFACE,
                           (VOID *)&mRestJsonStructureProtocol
                           );
  return Status;
}

/**
  This is the unload handle for Redfish discover module.

  Disconnect the driver specified by ImageHandle from all the devices in the handle database.
  Uninstall all the protocols installed in the driver entry point.

  @param[in] ImageHandle           The drivers' driver image.

  @retval    EFI_SUCCESS           The image is unloaded.
  @retval    Others                Failed to unload the image.

**/
EFI_STATUS
EFIAPI
RestJsonStructureUnload (
  IN EFI_HANDLE  ImageHandle
  )
{
  EFI_STATUS                    Status;
  REST_JSON_STRUCTURE_INSTANCE  *Instance;
  REST_JSON_STRUCTURE_INSTANCE  *NextInstance;

  Status = gBS->UninstallProtocolInterface (
                  mProtocolHandle,
                  &gEfiRestJsonStructureProtocolGuid,
                  (VOID *)&mRestJsonStructureProtocol
                  );

  if (IsListEmpty (&mRestJsonStructureList)) {
    return Status;
  }

  //
  // Free memory of REST_JSON_STRUCTURE_INSTANCE instance.
  //
  Instance = (REST_JSON_STRUCTURE_INSTANCE *)GetFirstNode (&mRestJsonStructureList);
  do {
    NextInstance = NULL;
    if (!IsNodeAtEnd (&mRestJsonStructureList, &Instance->NextRestJsonStructureInstance)) {
      NextInstance = (REST_JSON_STRUCTURE_INSTANCE *)GetNextNode (&mRestJsonStructureList, &Instance->NextRestJsonStructureInstance);
    }

    FreePool ((VOID *)Instance);
    Instance = NextInstance;
  } while (Instance != NULL);

  return Status;
}
