/** @file
  This file is cloned from DMTF libredfish library tag v1.0.0 and maintained
  by EDKII.

//----------------------------------------------------------------------------
// Copyright Notice:
// Copyright 2017 Distributed Management Task Force, Inc. All rights reserved.
// License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/libredfish/LICENSE.md
//----------------------------------------------------------------------------

  Copyright (c) 2019, Intel Corporation. All rights reserved.<BR>
  (C) Copyright 2021 Hewlett Packard Enterprise Development LP<BR>
  Copyright (c) 2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved.

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

**/
#include <redfishPayload.h>

static redfishPayload *
getOpResult (
  redfishPayload        *payload,
  const char            *propName,
  const char            *op,
  const char            *value,
  EFI_HTTP_STATUS_CODE  **StatusCode
  );

static redfishPayload *
collectionEvalOp (
  redfishPayload        *payload,
  const char            *propName,
  const char            *op,
  const char            *value,
  EFI_HTTP_STATUS_CODE  **StatusCode
  );

static redfishPayload *
arrayEvalOp (
  redfishPayload        *payload,
  const char            *propName,
  const char            *op,
  const char            *value,
  EFI_HTTP_STATUS_CODE  **StatusCode
  );

static redfishPayload *
createCollection (
  redfishService  *service,
  size_t          count,
  redfishPayload  **payloads
  );

static json_t *
json_object_get_by_index (
  json_t  *json,
  size_t  index
  );

bool
isPayloadCollection (
  redfishPayload  *payload
  )
{
  json_t  *members;
  json_t  *count;

  if (!payload || !json_is_object (payload->json)) {
    return false;
  }

  members = json_object_get (payload->json, "Members");
  count   = json_object_get (payload->json, "Members@odata.count");
  return ((members != NULL) && (count != NULL));
}

size_t
getCollectionSize (
  redfishPayload  *payload
  )
{
  json_t  *members;
  json_t  *count;

  if (!payload || !json_is_object (payload->json)) {
    return 0;
  }

  members = json_object_get (payload->json, "Members");
  count   = json_object_get (payload->json, "Members@odata.count");
  if (!members || !count) {
    return 0;
  }

  return (size_t)json_integer_value (count);
}

bool
isPayloadArray (
  redfishPayload  *payload
  )
{
  if (!payload || !json_is_array (payload->json)) {
    return false;
  }

  return true;
}

char *
payloadToString (
  redfishPayload  *payload,
  bool            prettyPrint
  )
{
  size_t  flags = 0;

  if (!payload) {
    return NULL;
  }

  if (prettyPrint) {
    flags = JSON_INDENT (2);
  }

  return json_dumps (payload->json, flags);
}

redfishPayload *
createRedfishPayload (
  json_t          *value,
  redfishService  *service
  )
{
  redfishPayload  *payload;

  payload = (redfishPayload *)malloc (sizeof (redfishPayload));
  if (payload != NULL) {
    payload->json    = value;
    payload->service = service;
  }

  return payload;
}

redfishPayload *
getPayloadByNodeName (
  redfishPayload        *payload,
  const char            *nodeName,
  EFI_HTTP_STATUS_CODE  **StatusCode
  )
{
  json_t      *value;
  json_t      *odataId;
  const char  *uri;

  if (!payload || !nodeName || (StatusCode == NULL)) {
    return NULL;
  }

  *StatusCode = NULL;

  value = json_object_get (payload->json, nodeName);
  if (value == NULL) {
    return NULL;
  }

  json_incref (value);
  if (json_object_size (value) == 1) {
    odataId = json_object_get (value, "@odata.id");
    if (odataId != NULL) {
      json_incref (odataId);
      uri = json_string_value (odataId);
      json_decref (value);
      value = getUriFromService (payload->service, uri, StatusCode);
      json_decref (odataId);
      if ((value == NULL) || (*StatusCode == NULL)) {
        return NULL;
      }
    }
  }

  if ((*StatusCode == NULL) || ((**StatusCode >= HTTP_STATUS_200_OK) && (**StatusCode <= HTTP_STATUS_206_PARTIAL_CONTENT))) {
    if (json_is_string (value)) {
      odataId = json_object ();
      json_object_set (odataId, nodeName, value);
      json_decref (value);
      value = odataId;
    }
  }

  return createRedfishPayload (value, payload->service);
}

redfishPayload *
getPayloadByIndex (
  redfishPayload        *payload,
  size_t                index,
  EFI_HTTP_STATUS_CODE  **StatusCode
  )
{
  json_t      *value = NULL;
  json_t      *odataId;
  const char  *uri;
  BOOLEAN     FromServerFlag = FALSE;

  if (!payload || (StatusCode == NULL)) {
    return NULL;
  }

  *StatusCode = NULL;

  if (isPayloadCollection (payload)) {
    redfishPayload  *members = getPayloadByNodeName (payload, "Members", StatusCode);
    if (((*StatusCode == NULL) && (members == NULL)) ||
        ((*StatusCode != NULL) && ((**StatusCode < HTTP_STATUS_200_OK) || (**StatusCode > HTTP_STATUS_206_PARTIAL_CONTENT))))
    {
      return members;
    }

    if (*StatusCode != NULL) {
      //
      // The Payload (members) are retrieved from server.
      //
      FreePool (*StatusCode);
      *StatusCode    = NULL;
      FromServerFlag = TRUE;
    }

    redfishPayload  *ret = getPayloadByIndex (members, index, StatusCode);
    if ((*StatusCode == NULL) && (ret != NULL) && FromServerFlag) {
      //
      // In such a case, the Redfish resource is parsed from the input payload (members) directly.
      // Since the members are retrieved from server, we still return HTTP_STATUS_200_OK.
      //
      *StatusCode = AllocateZeroPool (sizeof (EFI_HTTP_STATUS_CODE));
      if (*StatusCode == NULL) {
        ret = NULL;
      } else {
        **StatusCode = HTTP_STATUS_200_OK;
      }
    }

    cleanupPayload (members);
    return ret;
  }

  if (json_is_array (payload->json)) {
    //
    // The valid range for index is from 0 to the return value of json_array_size() minus 1
    //
    value = json_array_get (payload->json, index);
  } else if (json_is_object (payload->json)) {
    value = json_object_get_by_index (payload->json, index);
  }

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

  json_incref (value);
  if (json_object_size (value) == 1) {
    odataId = json_object_get (value, "@odata.id");
    if (odataId != NULL) {
      uri = json_string_value (odataId);
      json_decref (value);
      value = getUriFromService (payload->service, uri, StatusCode);
      if (value == NULL) {
        return NULL;
      }
    }
  }

  return createRedfishPayload (value, payload->service);
}

redfishPayload *
getPayloadForPath (
  redfishPayload        *payload,
  redPathNode           *redpath,
  EFI_HTTP_STATUS_CODE  **StatusCode
  )
{
  redfishPayload  *ret = NULL;
  redfishPayload  *tmp;

  if (!payload || !redpath || (StatusCode == NULL)) {
    return NULL;
  }

  *StatusCode = NULL;
  BOOLEAN  FromServerFlag = FALSE;

  if (redpath->nodeName) {
    ret = getPayloadByNodeName (payload, redpath->nodeName, StatusCode);
    if (((*StatusCode == NULL) && (ret == NULL)) ||
        ((*StatusCode != NULL) && ((**StatusCode < HTTP_STATUS_200_OK) || (**StatusCode > HTTP_STATUS_206_PARTIAL_CONTENT))))
    {
      //
      // Any error happen, return directly.
      //
      return ret;
    }
  } else if (redpath->isIndex) {
    ASSERT (redpath->index >= 1);
    ret = getPayloadByIndex (payload, redpath->index - 1, StatusCode);
    if (((*StatusCode == NULL) && (ret == NULL)) ||
        ((*StatusCode != NULL) && ((**StatusCode < HTTP_STATUS_200_OK) || (**StatusCode > HTTP_STATUS_206_PARTIAL_CONTENT))))
    {
      //
      // Any error happen, return directly.
      //
      return ret;
    }
  } else if (redpath->op) {
    ret = getOpResult (payload, redpath->propName, redpath->op, redpath->value, StatusCode);
    if (((*StatusCode == NULL) && (ret == NULL)) ||
        ((*StatusCode != NULL) && ((**StatusCode < HTTP_STATUS_200_OK) || (**StatusCode > HTTP_STATUS_206_PARTIAL_CONTENT))))
    {
      //
      // Any error happen, return directly.
      //
      return ret;
    }
  } else {
    return NULL;
  }

  if ((redpath->next == NULL) || (ret == NULL)) {
    return ret;
  } else {
    if (*StatusCode != NULL) {
      FreePool (*StatusCode);
      *StatusCode    = NULL;
      FromServerFlag = TRUE;
    }

    tmp = getPayloadForPath (ret, redpath->next, StatusCode);
    if ((*StatusCode == NULL) && (tmp != NULL) && FromServerFlag) {
      //
      // In such a case, the Redfish resource is parsed from the input payload (ret) directly.
      // Since the ret are retrieved from server, we still return HTTP_STATUS_200_OK.
      //
      *StatusCode = AllocateZeroPool (sizeof (EFI_HTTP_STATUS_CODE));
      if (*StatusCode == NULL) {
        tmp = NULL;
      } else {
        **StatusCode = HTTP_STATUS_200_OK;
      }
    }

    cleanupPayload (ret);
    return tmp;
  }
}

redfishPayload *
getPayloadForPathString (
  redfishPayload        *payload,
  const char            *string,
  EFI_HTTP_STATUS_CODE  **StatusCode
  )
{
  redPathNode     *redpath;
  redfishPayload  *ret;

  if (!string || (StatusCode == NULL)) {
    return NULL;
  }

  *StatusCode = NULL;

  redpath = parseRedPath (string);
  if (redpath == NULL) {
    return NULL;
  }

  ret = getPayloadForPath (payload, redpath, StatusCode);
  cleanupRedPath (redpath);
  return ret;
}

redfishPayload *
patchPayload (
  redfishPayload        *target,
  redfishPayload        *payload,
  EFI_HTTP_STATUS_CODE  **StatusCode
  )
{
  json_t  *json;
  char    *content;
  char    *uri;

  if (!target || !payload || (StatusCode == NULL)) {
    return NULL;
  }

  *StatusCode = NULL;

  json = json_object_get (target->json, "@odata.id");
  if (json == NULL) {
    return NULL;
  }

  uri = strdup (json_string_value (json));

  content = json_dumps (payload->json, 0);
  json_decref (json);

  json = patchUriFromService (target->service, uri, content, StatusCode);
  free (uri);
  free (content);
  if (json == NULL) {
    return NULL;
  }

  return createRedfishPayload (json, target->service);
}

redfishPayload *
postContentToPayload (
  redfishPayload        *target,
  const char            *data,
  size_t                dataSize,
  const char            *contentType,
  EFI_HTTP_STATUS_CODE  **StatusCode
  )
{
  json_t  *json;
  char    *uri;

  if (!target || !data || (StatusCode == NULL)) {
    return NULL;
  }

  *StatusCode = NULL;

  json = json_object_get (target->json, "@odata.id");
  if (json == NULL) {
    json = json_object_get (target->json, "target");
    if (json == NULL) {
      return NULL;
    }
  }

  uri  = strdup (json_string_value (json));
  json = postUriFromService (target->service, uri, data, dataSize, contentType, StatusCode);
  free (uri);
  if (json == NULL) {
    return NULL;
  }

  return createRedfishPayload (json, target->service);
}

redfishPayload *
postPayload (
  redfishPayload        *target,
  redfishPayload        *payload,
  EFI_HTTP_STATUS_CODE  **StatusCode
  )
{
  char            *content;
  redfishPayload  *ret;

  if (!target || !payload || (StatusCode == NULL)) {
    return NULL;
  }

  *StatusCode = NULL;

  if (!json_is_object (payload->json)) {
    return NULL;
  }

  content = payloadToString (payload, false);
  ret     = postContentToPayload (target, content, strlen (content), NULL, StatusCode);
  free (content);
  return ret;
}

void
cleanupPayload (
  redfishPayload  *payload
  )
{
  if (!payload) {
    return;
  }

  json_decref (payload->json);
  // Don't free payload->service, let the caller handle cleaning up the service
  free (payload);
}

static redfishPayload *
getOpResult (
  redfishPayload        *payload,
  const char            *propName,
  const char            *op,
  const char            *value,
  EFI_HTTP_STATUS_CODE  **StatusCode
  )
{
  const char      *propStr;
  json_t          *stringProp;
  bool            ret = false;
  redfishPayload  *prop;
  long long       intVal, intPropVal;
  json_type       jsonType;

  if (isPayloadCollection (payload)) {
    return collectionEvalOp (payload, propName, op, value, StatusCode);
  }

  if (isPayloadArray (payload)) {
    return arrayEvalOp (payload, propName, op, value, StatusCode);
  }

  prop = getPayloadByNodeName (payload, propName, StatusCode);
  if (((*StatusCode == NULL) && (prop == NULL)) ||
      ((*StatusCode != NULL) && ((**StatusCode < HTTP_STATUS_200_OK) || (**StatusCode > HTTP_STATUS_206_PARTIAL_CONTENT))))
  {
    return prop;
  }

  stringProp = prop->json;
  jsonType   = (json_type)JsonGetType (prop->json);
  switch (jsonType) {
    case JSON_OBJECT:
      stringProp = json_object_get (prop->json, propName);
    case JSON_STRING:
      if (strcmp (op, "=") == 0) {
        propStr = json_string_value (stringProp);
        if (propStr == NULL) {
          cleanupPayload (prop);
          return NULL;
        }

        ret = (strcmp (propStr, value) == 0);
      } else if (strcmp (op, "~") == 0) {
        propStr = json_string_value (stringProp);
        if (propStr == NULL) {
          cleanupPayload (prop);
          return NULL;
        }

        ret = (strcasecmp (propStr, value) == 0);
      }

      break;
    case JSON_TRUE:
      if (strcmp (op, "=") == 0) {
        ret = (strcmp (value, "true") == 0);
      }

      break;
    case JSON_FALSE:
      if (strcmp (op, "=") == 0) {
        ret = (strcmp (value, "false") == 0);
      }

      break;
    case JSON_INTEGER:
      intPropVal = json_integer_value (prop->json);
      intVal     = strtoll (value, NULL, 0);
      if (strcmp (op, "=") == 0) {
        ret = (intPropVal == intVal);
      } else if (strcmp (op, "<") == 0) {
        ret = (intPropVal < intVal);
      } else if (strcmp (op, ">") == 0) {
        ret = (intPropVal > intVal);
      } else if (strcmp (op, "<=") == 0) {
        ret = (intPropVal <= intVal);
      } else if (strcmp (op, ">=") == 0) {
        ret = (intPropVal >= intVal);
      }

      break;
    default:
      break;
  }

  cleanupPayload (prop);
  if (ret) {
    return payload;
  } else {
    return NULL;
  }
}

static redfishPayload *
collectionEvalOp (
  redfishPayload        *payload,
  const char            *propName,
  const char            *op,
  const char            *value,
  EFI_HTTP_STATUS_CODE  **StatusCode
  )
{
  redfishPayload  *ret;
  redfishPayload  *tmp;
  redfishPayload  *members;
  redfishPayload  **valid;
  size_t          validMax;
  size_t          validCount = 0;
  size_t          i;

  validMax = getCollectionSize (payload);
  if (validMax == 0) {
    return NULL;
  }

  valid = (redfishPayload **)calloc (validMax, sizeof (redfishPayload *));
  if (valid == NULL) {
    return NULL;
  }

  /*Technically getPayloadByIndex would do this, but this optimizes things*/
  members = getPayloadByNodeName (payload, "Members", StatusCode);
  if (((*StatusCode == NULL) && (members == NULL)) ||
      ((*StatusCode != NULL) && ((**StatusCode < HTTP_STATUS_200_OK) || (**StatusCode > HTTP_STATUS_206_PARTIAL_CONTENT))))
  {
    free (valid);
    return members;
  }

  for (i = 0; i < validMax; i++) {
    if (*StatusCode != NULL) {
      FreePool (*StatusCode);
      *StatusCode = NULL;
    }

    tmp = getPayloadByIndex (members, i, StatusCode);
    if (((*StatusCode == NULL) && (tmp == NULL)) ||
        ((*StatusCode != NULL) && ((**StatusCode < HTTP_STATUS_200_OK) || (**StatusCode > HTTP_STATUS_206_PARTIAL_CONTENT))))
    {
      free (valid);
      return tmp;
    }

    if (*StatusCode != NULL) {
      FreePool (*StatusCode);
      *StatusCode = NULL;
    }

    valid[validCount] = getOpResult (tmp, propName, op, value, StatusCode);

    /*
    if ((*StatusCode == NULL && valid[validCount] == NULL) ||
        (*StatusCode != NULL && (**StatusCode < HTTP_STATUS_200_OK || **StatusCode > HTTP_STATUS_206_PARTIAL_CONTENT))) {
      return valid[validCount];
    }
    */
    if (valid[validCount] != NULL) {
      validCount++;
    } else {
      cleanupPayload (tmp);
    }
  }

  cleanupPayload (members);
  if (validCount == 0) {
    ret = NULL;
  } else if (validCount == 1) {
    ret = valid[0];
  } else {
    ret = createCollection (payload->service, validCount, valid);
  }

  free (valid);
  return ret;
}

static redfishPayload *
arrayEvalOp (
  redfishPayload        *payload,
  const char            *propName,
  const char            *op,
  const char            *value,
  EFI_HTTP_STATUS_CODE  **StatusCode
  )
{
  redfishPayload  *ret;
  redfishPayload  *tmp;
  redfishPayload  **valid;
  size_t          validMax;
  size_t          validCount = 0;
  size_t          i;

  validMax = json_array_size (payload->json);
  if (validMax == 0) {
    return NULL;
  }

  valid = (redfishPayload **)calloc (validMax, sizeof (redfishPayload *));
  if (valid == NULL) {
    return NULL;
  }

  for (i = 0; i < validMax; i++) {
    if (*StatusCode != NULL) {
      FreePool (*StatusCode);
      *StatusCode = NULL;
    }

    tmp = getPayloadByIndex (payload, i, StatusCode);
    if (((*StatusCode == NULL) && (tmp == NULL)) ||
        ((*StatusCode != NULL) && ((**StatusCode < HTTP_STATUS_200_OK) || (**StatusCode > HTTP_STATUS_206_PARTIAL_CONTENT))))
    {
      return tmp;
    }

    if (*StatusCode != NULL) {
      FreePool (*StatusCode);
      *StatusCode = NULL;
    }

    valid[validCount] = getOpResult (tmp, propName, op, value, StatusCode);

    /*
    if ((*StatusCode == NULL && valid[validCount] == NULL) ||
        (*StatusCode != NULL && (**StatusCode < HTTP_STATUS_200_OK || **StatusCode > HTTP_STATUS_206_PARTIAL_CONTENT))) {
      return valid[validCount];
    }
    */

    if (valid[validCount] != NULL) {
      validCount++;
    } else {
      cleanupPayload (tmp);
    }
  }

  if (validCount == 0) {
    free (valid);
    return NULL;
  }

  if (validCount == 1) {
    ret = valid[0];
    free (valid);
    return ret;
  } else {
    ret = createCollection (payload->service, validCount, valid);
    free (valid);
    return ret;
  }
}

static redfishPayload *
createCollection (
  redfishService  *service,
  size_t          count,
  redfishPayload  **payloads
  )
{
  redfishPayload  *ret;
  json_t          *collectionJson = json_object ();
  json_t          *jcount         = json_integer ((json_int_t)count);
  json_t          *members        = json_array ();
  size_t          i;

  if (!collectionJson) {
    return NULL;
  }

  if (!members) {
    json_decref (collectionJson);
    return NULL;
  }

  json_object_set (collectionJson, "Members@odata.count", jcount);
  json_decref (jcount);
  for (i = 0; i < count; i++) {
    json_array_append (members, payloads[i]->json);
    cleanupPayload (payloads[i]);
  }

  json_object_set (collectionJson, "Members", members);
  json_decref (members);

  ret = createRedfishPayload (collectionJson, service);
  return ret;
}

static json_t *
json_object_get_by_index (
  json_t  *json,
  size_t  index
  )
{
  void    *iter;
  size_t  i;

  iter = json_object_iter (json);
  for (i = 0; i < index; i++) {
    iter = json_object_iter_next (json, iter);
    if (iter == NULL) {
      break;
    }
  }

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

  return json_object_iter_value (iter);
}

/* vim: set tabstop=4 shiftwidth=4 expandtab: */
