/** @file
  RedfishHttpData handles internal data to support Redfish HTTP protocol.

  Copyright (c) 2023-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved.

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

**/

#include "RedfishHttpData.h"
#include "RedfishHttpOperation.h"

/**
  This function update session token in Redfish Service.

  @param[in]  Service         Pointer to service instance.
  @param[in]  Token           Session token.

  @retval     EFI_SUCCESS     Session token is updated.
  @retval     Others          Error occurs.

**/
EFI_STATUS
UpdateSessionToken (
  IN REDFISH_SERVICE_PRIVATE  *Service,
  IN CHAR8                    *Token
  )
{
  if ((Service == NULL) || IS_EMPTY_STRING (Token)) {
    return EFI_INVALID_PARAMETER;
  }

  if (Service->SessionToken != NULL) {
    FreePool (Service->SessionToken);
  }

  Service->SessionToken = ASCII_STR_DUPLICATE (Token);
  if (Service->SessionToken == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  return EFI_SUCCESS;
}

/**
  This function release Redfish Service.

  @param[in]  Service         Pointer to service instance.

  @retval     EFI_SUCCESS     Service is released.
  @retval     Others          Error occurs.

**/
EFI_STATUS
ReleaseRedfishService (
  IN REDFISH_SERVICE_PRIVATE  *Service
  )
{
  if (Service == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  if (Service->Host != NULL) {
    FreePool (Service->Host);
  }

  if (Service->HostName != NULL) {
    FreePool (Service->HostName);
  }

  if (Service->BasicAuth != NULL) {
    ZeroMem (Service->BasicAuth, AsciiStrSize (Service->BasicAuth));
    FreePool (Service->BasicAuth);
  }

  if (Service->SessionToken != NULL) {
    ZeroMem (Service->SessionToken, AsciiStrSize (Service->SessionToken));
    FreePool (Service->SessionToken);
  }

  FreePool (Service);

  return EFI_SUCCESS;
}

/**
  This function creat new service. Host and HostName are copied to
  newly created service instance.

  @param[in]  Host            Host string.
  @param[in]  HostName        Hostname string.
  @param[in]  BasicAuth       Basic Authorization string.
  @param[in]  SessionToken    Session token string.
  @param[in]  RestEx          Rest EX protocol instance.

  @retval     REDFISH_PAYLOAD_PRIVATE  Newly created service.
  @retval     NULL             Error occurs.

**/
REDFISH_SERVICE_PRIVATE *
CreateRedfishService (
  IN CHAR8                 *Host,
  IN CHAR8                 *HostName,
  IN CHAR8                 *BasicAuth OPTIONAL,
  IN CHAR8                 *SessionToken OPTIONAL,
  IN EFI_REST_EX_PROTOCOL  *RestEx
  )
{
  REDFISH_SERVICE_PRIVATE  *NewService;
  UINTN                    AuthStrSize;

  if (IS_EMPTY_STRING (Host) || IS_EMPTY_STRING (HostName) || (RestEx == NULL)) {
    return NULL;
  }

  NewService = AllocateZeroPool (sizeof (REDFISH_SERVICE_PRIVATE));
  if (NewService == NULL) {
    return NULL;
  }

  NewService->Signature = REDFISH_HTTP_SERVICE_SIGNATURE;
  NewService->Host      = ASCII_STR_DUPLICATE (Host);
  if (NewService->Host == NULL) {
    goto ON_ERROR;
  }

  NewService->HostName = ASCII_STR_DUPLICATE (HostName);
  if (NewService->HostName == NULL) {
    goto ON_ERROR;
  }

  if (!IS_EMPTY_STRING (BasicAuth)) {
    AuthStrSize           = AsciiStrSize (BasicAuth) + AsciiStrLen (REDFISH_HTTP_BASIC_AUTH_STR);
    NewService->BasicAuth = AllocateZeroPool (AuthStrSize);
    if (NewService->BasicAuth == NULL) {
      goto ON_ERROR;
    }

    AsciiSPrint (NewService->BasicAuth, AuthStrSize, "%a%a", REDFISH_HTTP_BASIC_AUTH_STR, BasicAuth);
  }

  if (!IS_EMPTY_STRING (SessionToken)) {
    NewService->SessionToken = ASCII_STR_DUPLICATE (SessionToken);
    if (NewService->SessionToken == NULL) {
      goto ON_ERROR;
    }
  }

  NewService->RestEx = RestEx;

  return NewService;

ON_ERROR:

  ReleaseRedfishService (NewService);

  return NULL;
}

/**
  This function release Redfish Payload.

  @param[in]  Payload         Pointer to payload instance.

  @retval     EFI_SUCCESS     Payload is released.
  @retval     Others          Error occurs.

**/
EFI_STATUS
ReleaseRedfishPayload (
  IN REDFISH_PAYLOAD_PRIVATE  *Payload
  )
{
  if (Payload == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  if (Payload->Service != NULL) {
    ReleaseRedfishService (Payload->Service);
  }

  if (Payload->JsonValue != NULL) {
    JsonValueFree (Payload->JsonValue);
  }

  FreePool (Payload);

  return EFI_SUCCESS;
}

/**
  This function creat new payload. Server and JsonObj are
  copied to newly created payload.

  @param[in]  Service          Pointer to Service instance.
  @param[in]  JsonValue        Pointer to JSON value.

  @retval     REDFISH_PAYLOAD_PRIVATE  Newly created payload.
  @retval     NULL                     Error occurs.

**/
REDFISH_PAYLOAD_PRIVATE *
CreateRedfishPayload (
  IN REDFISH_SERVICE_PRIVATE  *Service,
  IN EDKII_JSON_VALUE         JsonValue
  )
{
  REDFISH_PAYLOAD_PRIVATE  *NewPayload;

  if ((Service == NULL) || (JsonValue == NULL)) {
    return NULL;
  }

  NewPayload = AllocateZeroPool (sizeof (REDFISH_PAYLOAD_PRIVATE));
  if (NewPayload == NULL) {
    return NULL;
  }

  NewPayload->Signature = REDFISH_HTTP_PAYLOAD_SIGNATURE;
  NewPayload->Service   = CreateRedfishService (Service->Host, Service->HostName, Service->BasicAuth, Service->SessionToken, Service->RestEx);
  if (NewPayload->Service == NULL) {
    goto ON_ERROR;
  }

  NewPayload->JsonValue = JsonValueClone (JsonValue);
  if (NewPayload->JsonValue == NULL) {
    goto ON_ERROR;
  }

  return NewPayload;

ON_ERROR:

  ReleaseRedfishPayload (NewPayload);

  return NULL;
}

/**
  This function copy the data in SrcResponse to DstResponse.

  @param[in]  SrcResponse      Source Response to copy.
  @param[out] DstResponse      Destination Response.

  @retval     EFI_SUCCESS      Response is copied successfully.
  @retval     Others           Error occurs.

**/
EFI_STATUS
CopyRedfishResponse (
  IN  REDFISH_RESPONSE  *SrcResponse,
  OUT REDFISH_RESPONSE  *DstResponse
  )
{
  REDFISH_PAYLOAD_PRIVATE  *Payload;
  UINTN                    Index;

  if ((SrcResponse == NULL) || (DstResponse == NULL)) {
    return EFI_INVALID_PARAMETER;
  }

  if (SrcResponse == DstResponse) {
    return EFI_SUCCESS;
  }

  //
  // Status code
  //
  if (SrcResponse->StatusCode != NULL) {
    DstResponse->StatusCode = AllocateCopyPool (sizeof (EFI_HTTP_STATUS_CODE), SrcResponse->StatusCode);
    if (DstResponse->StatusCode == NULL) {
      goto ON_ERROR;
    }
  }

  //
  // Header
  //
  if ((SrcResponse->HeaderCount > 0) && (SrcResponse->Headers != NULL)) {
    DstResponse->HeaderCount = 0;
    DstResponse->Headers     = AllocateZeroPool (sizeof (EFI_HTTP_HEADER) * SrcResponse->HeaderCount);
    if (DstResponse->Headers == NULL) {
      goto ON_ERROR;
    }

    DstResponse->HeaderCount = SrcResponse->HeaderCount;

    for (Index = 0; Index < SrcResponse->HeaderCount; Index++) {
      DstResponse->Headers[Index].FieldName = ASCII_STR_DUPLICATE (SrcResponse->Headers[Index].FieldName);
      if (DstResponse->Headers[Index].FieldName == NULL) {
        goto ON_ERROR;
      }

      DstResponse->Headers[Index].FieldValue = ASCII_STR_DUPLICATE (SrcResponse->Headers[Index].FieldValue);
      if (DstResponse->Headers[Index].FieldValue == NULL) {
        goto ON_ERROR;
      }
    }
  }

  //
  // Payload
  //
  if (SrcResponse->Payload != NULL) {
    Payload = (REDFISH_PAYLOAD_PRIVATE *)SrcResponse->Payload;
    if (Payload->Signature != REDFISH_HTTP_PAYLOAD_SIGNATURE) {
      DEBUG ((DEBUG_ERROR, "%a: signature check failure\n", __func__));
      goto ON_ERROR;
    }

    DstResponse->Payload = CreateRedfishPayload (Payload->Service, Payload->JsonValue);
    if (DstResponse->Payload  == NULL) {
      goto ON_ERROR;
    }
  }

  return EFI_SUCCESS;

ON_ERROR:

  ReleaseRedfishResponse (DstResponse);

  return EFI_OUT_OF_RESOURCES;
}

/**
  This function clone input response and return to caller

  @param[in]  Response      Response to clone.

  @retval     REDFISH_RESPONSE *  Response is cloned.
  @retval     NULL                Errors occur.

**/
REDFISH_RESPONSE *
CloneRedfishResponse (
  IN REDFISH_RESPONSE  *Response
  )
{
  EFI_STATUS        Status;
  REDFISH_RESPONSE  *NewResponse;

  if (Response == NULL) {
    return NULL;
  }

  NewResponse = AllocateZeroPool (sizeof (REDFISH_RESPONSE));
  if (NewResponse == NULL) {
    return NULL;
  }

  Status = CopyRedfishResponse (Response, NewResponse);
  if (EFI_ERROR (Status)) {
    FreePool (NewResponse);
    return NULL;
  }

  return NewResponse;
}

/**
  Release REDFISH_HTTP_CACHE_DATA resource

  @param[in]    Data    Pointer to REDFISH_HTTP_CACHE_DATA instance

  @retval EFI_SUCCESS             REDFISH_HTTP_CACHE_DATA is released successfully.
  @retval EFI_INVALID_PARAMETER   Data is NULL

**/
EFI_STATUS
ReleaseHttpCacheData (
  IN REDFISH_HTTP_CACHE_DATA  *Data
  )
{
  if (Data == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  if (Data->Uri != NULL) {
    FreePool (Data->Uri);
  }

  if (Data->Response != NULL) {
    ReleaseRedfishResponse (Data->Response);
    FreePool (Data->Response);
  }

  FreePool (Data);

  return EFI_SUCCESS;
}

/**
  Create new cache data.

  @param[in]    Uri       The URI string matching to this cache data.
  @param[in]    Response  HTTP response.

  @retval REDFISH_HTTP_CACHE_DATA *   Pointer to newly created cache data.
  @retval NULL                        No memory available.

**/
REDFISH_HTTP_CACHE_DATA *
NewHttpCacheData (
  IN  EFI_STRING        Uri,
  IN  REDFISH_RESPONSE  *Response
  )
{
  REDFISH_HTTP_CACHE_DATA  *NewData;
  UINTN                    Size;

  if (IS_EMPTY_STRING (Uri) || (Response == NULL)) {
    return NULL;
  }

  NewData = AllocateZeroPool (sizeof (REDFISH_HTTP_CACHE_DATA));
  if (NewData == NULL) {
    return NULL;
  }

  NewData->Signature = REDFISH_HTTP_CACHE_SIGNATURE;
  Size               = StrSize (Uri);
  NewData->Uri       = AllocateCopyPool (Size, Uri);
  if (NewData->Uri == NULL) {
    goto ON_ERROR;
  }

  NewData->Response = Response;
  NewData->HitCount = 1;

  return NewData;

ON_ERROR:

  if (NewData != NULL) {
    ReleaseHttpCacheData (NewData);
  }

  return NULL;
}

/**
  Search on given ListHeader for given URI string.

  @param[in]    ListHeader  Target list to search.
  @param[in]    Uri         Target URI to search.

  @retval REDFISH_HTTP_CACHE_DATA   Target cache data is found.
  @retval NULL                      No cache data with given URI is found.

**/
REDFISH_HTTP_CACHE_DATA *
FindHttpCacheData (
  IN  LIST_ENTRY  *ListHeader,
  IN  EFI_STRING  Uri
  )
{
  LIST_ENTRY               *List;
  REDFISH_HTTP_CACHE_DATA  *Data;

  if (IS_EMPTY_STRING (Uri)) {
    return NULL;
  }

  if (IsListEmpty (ListHeader)) {
    return NULL;
  }

  Data = NULL;
  List = GetFirstNode (ListHeader);
  while (!IsNull (ListHeader, List)) {
    Data = REDFISH_HTTP_CACHE_FROM_LIST (List);

    if (StrCmp (Data->Uri, Uri) == 0) {
      return Data;
    }

    List = GetNextNode (ListHeader, List);
  }

  return NULL;
}

/**
  Search on given ListHeader and return cache data with minimum hit count.

  @param[in]    ListHeader  Target list to search.

  @retval REDFISH_HTTP_CACHE_DATA   Target cache data is returned.
  @retval NULL                      No cache data is found.

**/
REDFISH_HTTP_CACHE_DATA *
FindUnusedHttpCacheData (
  IN  LIST_ENTRY  *ListHeader
  )
{
  LIST_ENTRY               *List;
  REDFISH_HTTP_CACHE_DATA  *Data;
  REDFISH_HTTP_CACHE_DATA  *UnusedData;
  UINTN                    HitCount;

  if (IsListEmpty (ListHeader)) {
    return NULL;
  }

  Data       = NULL;
  UnusedData = NULL;
  HitCount   = 0;

  List       = GetFirstNode (ListHeader);
  Data       = REDFISH_HTTP_CACHE_FROM_LIST (List);
  UnusedData = Data;
  HitCount   = Data->HitCount;
  List       = GetNextNode (ListHeader, List);

  while (!IsNull (ListHeader, List)) {
    Data = REDFISH_HTTP_CACHE_FROM_LIST (List);

    if (Data->HitCount < HitCount) {
      HitCount   = Data->HitCount;
      UnusedData = Data;
    }

    List = GetNextNode (ListHeader, List);
  }

  return UnusedData;
}

/**
  Delete a cache data by given cache instance.

  @param[in]    List    Target cache list to be removed.
  @param[in]    Data    Pointer to the instance to be deleted.

  @retval EFI_SUCCESS   Cache data is removed.
  @retval Others        Fail to remove cache data.

**/
EFI_STATUS
DeleteHttpCacheData (
  IN  REDFISH_HTTP_CACHE_LIST  *List,
  IN  REDFISH_HTTP_CACHE_DATA  *Data
  )
{
  if ((List == NULL) || (Data == NULL)) {
    return EFI_INVALID_PARAMETER;
  }

  DEBUG ((REDFISH_HTTP_CACHE_DEBUG, "%a: delete: %s\n", __func__, Data->Uri));

  RemoveEntryList (&Data->List);
  --List->Count;

  return ReleaseHttpCacheData (Data);
}

/**
  Add new cache by given URI and HTTP response to specify List.

  @param[in]    List      Target cache list to add.
  @param[in]    Uri       The URI string matching to this cache data.
  @param[in]    Response  HTTP response.

  @retval EFI_SUCCESS   Cache data is added.
  @retval Others        Fail to add cache data.

**/
EFI_STATUS
AddHttpCacheData (
  IN  REDFISH_HTTP_CACHE_LIST  *List,
  IN  EFI_STRING               Uri,
  IN  REDFISH_RESPONSE         *Response
  )
{
  REDFISH_HTTP_CACHE_DATA  *NewData;
  REDFISH_HTTP_CACHE_DATA  *OldData;
  REDFISH_HTTP_CACHE_DATA  *UnusedData;
  REDFISH_RESPONSE         *NewResponse;

  if ((List == NULL) || IS_EMPTY_STRING (Uri) || (Response == NULL)) {
    return EFI_INVALID_PARAMETER;
  }

  //
  // If same cache data exist, replace it with latest one.
  //
  OldData = FindHttpCacheData (&List->Head, Uri);
  if (OldData != NULL) {
    DeleteHttpCacheData (List, OldData);
  }

  //
  // Check capacity
  //
  if (List->Count >= List->Capacity) {
    DEBUG ((REDFISH_HTTP_CACHE_DEBUG, "%a: list is full and retire unused cache\n", __func__));
    UnusedData = FindUnusedHttpCacheData (&List->Head);
    if (UnusedData == NULL) {
      return EFI_OUT_OF_RESOURCES;
    }

    DeleteHttpCacheData (List, UnusedData);
  }

  //
  // Clone a local copy
  //
  NewResponse = CloneRedfishResponse (Response);
  if (NewResponse == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  NewData = NewHttpCacheData (Uri, NewResponse);
  if (NewData == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  InsertTailList (&List->Head, &NewData->List);
  ++List->Count;

  DEBUG ((REDFISH_HTTP_CACHE_DEBUG, "%a: cache(%d/%d) %s\n", __func__, List->Count, List->Capacity, NewData->Uri));

  return EFI_SUCCESS;
}

/**
  Release all cache from list.

  @param[in]    CacheList    The list to be released.

  @retval EFI_SUCCESS             All cache data are released.
  @retval EFI_INVALID_PARAMETER   CacheList is NULL.

**/
EFI_STATUS
ReleaseCacheList (
  IN  REDFISH_HTTP_CACHE_LIST  *CacheList
  )
{
  LIST_ENTRY               *List;
  LIST_ENTRY               *Next;
  REDFISH_HTTP_CACHE_DATA  *Data;

  if (CacheList == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  if (IsListEmpty (&CacheList->Head)) {
    return EFI_SUCCESS;
  }

  Data = NULL;
  Next = NULL;
  List = GetFirstNode (&CacheList->Head);
  while (!IsNull (&CacheList->Head, List)) {
    Data = REDFISH_HTTP_CACHE_FROM_LIST (List);
    Next = GetNextNode (&CacheList->Head, List);

    DeleteHttpCacheData (CacheList, Data);

    List = Next;
  }

  return EFI_SUCCESS;
}
