// Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and other CEED contributors.
// All Rights Reserved. See the top-level LICENSE and NOTICE files for details.
//
// SPDX-License-Identifier: BSD-2-Clause
//
// This file is part of CEED:  http://github.com/ceed

#define _POSIX_C_SOURCE 200112
#include <ceed-impl.h>
#include <ceed.h>
#include <ceed/backend.h>
#include <limits.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/// @cond DOXYGEN_SKIP
static CeedRequest ceed_request_immediate;
static CeedRequest ceed_request_ordered;

static struct {
  char prefix[CEED_MAX_RESOURCE_LEN];
  int (*init)(const char *resource, Ceed f);
  unsigned int priority;
} backends[32];
static size_t num_backends;

#define CEED_FTABLE_ENTRY(class, method) \
  { #class #method, offsetof(struct class##_private, method) }
/// @endcond

/// @file
/// Implementation of core components of Ceed library

/// @addtogroup CeedUser
/// @{

/**
  @brief Request immediate completion

  This predefined constant is passed as the @ref CeedRequest argument to interfaces when the caller wishes for the operation to be performed immediately.
  The code

  @code
    CeedOperatorApply(op, ..., CEED_REQUEST_IMMEDIATE);
  @endcode

  is semantically equivalent to

  @code
    CeedRequest request;
    CeedOperatorApply(op, ..., &request);
    CeedRequestWait(&request);
  @endcode

  @sa CEED_REQUEST_ORDERED
**/
CeedRequest *const CEED_REQUEST_IMMEDIATE = &ceed_request_immediate;

/**
  @brief Request ordered completion

  This predefined constant is passed as the @ref CeedRequest argument to interfaces when the caller wishes for the operation to be completed in the order that it is submitted to the device.
  It is typically used in a construct such as:

  @code
    CeedRequest request;
    CeedOperatorApply(op1, ..., CEED_REQUEST_ORDERED);
    CeedOperatorApply(op2, ..., &request);
    // other optional work
    CeedRequestWait(&request);
  @endcode

  which allows the sequence to complete asynchronously but does not start `op2` until `op1` has completed.

  @todo The current implementation is overly strict, offering equivalent semantics to @ref CEED_REQUEST_IMMEDIATE.

  @sa CEED_REQUEST_IMMEDIATE
 */
CeedRequest *const CEED_REQUEST_ORDERED = &ceed_request_ordered;

/**
  @brief Wait for a @ref CeedRequest to complete.

  Calling @ref CeedRequestWait() on a `NULL` request is a no-op.

  @param[in,out] req Address of @ref CeedRequest to wait for; zeroed on completion.

  @return An error code: 0 - success, otherwise - failure

  @ref User
**/
int CeedRequestWait(CeedRequest *req) {
  if (!*req) return CEED_ERROR_SUCCESS;
  return CeedError(NULL, CEED_ERROR_UNSUPPORTED, "CeedRequestWait not implemented");
}

/// @}

/// ----------------------------------------------------------------------------
/// Ceed Library Internal Functions
/// ----------------------------------------------------------------------------
/// @addtogroup CeedDeveloper
/// @{

/**
  @brief Register a Ceed backend internally.

  Note: Backends should call @ref CeedRegister() instead.

  @param[in] prefix   Prefix of resources for this backend to respond to.
                        For example, the reference backend responds to "/cpu/self".
  @param[in] init     Initialization function called by @ref CeedInit() when the backend is selected to drive the requested resource
  @param[in] priority Integer priority.
                        Lower values are preferred in case the resource requested by @ref CeedInit() has non-unique best prefix match.

  @return An error code: 0 - success, otherwise - failure

  @ref Developer
**/
int CeedRegisterImpl(const char *prefix, int (*init)(const char *, Ceed), unsigned int priority) {
  int ierr = 0;

  CeedPragmaCritical(CeedRegisterImpl) {
    if (num_backends < sizeof(backends) / sizeof(backends[0])) {
      strncpy(backends[num_backends].prefix, prefix, CEED_MAX_RESOURCE_LEN);
      backends[num_backends].prefix[CEED_MAX_RESOURCE_LEN - 1] = 0;
      backends[num_backends].init                              = init;
      backends[num_backends].priority                          = priority;
      num_backends++;
    } else {
      ierr = 1;
    }
  }
  CeedCheck(ierr == 0, NULL, CEED_ERROR_MAJOR, "Too many backends");
  return CEED_ERROR_SUCCESS;
}

/// @}

/// ----------------------------------------------------------------------------
/// Ceed Backend API
/// ----------------------------------------------------------------------------
/// @addtogroup CeedBackend
/// @{

/**
  @brief Return value of `CEED_DEBUG` environment variable

  @param[in] ceed `Ceed` context

  @return Boolean value: true  - debugging mode enabled
                         false - debugging mode disabled

  @ref Backend
**/
// LCOV_EXCL_START
bool CeedDebugFlag(const Ceed ceed) { return ceed->is_debug; }
// LCOV_EXCL_STOP

/**
  @brief Return value of `CEED_DEBUG` environment variable

  @return Boolean value: true  - debugging mode enabled
                         false - debugging mode disabled

  @ref Backend
**/
// LCOV_EXCL_START
bool CeedDebugFlagEnv(void) { return getenv("CEED_DEBUG") || getenv("DEBUG") || getenv("DBG"); }
// LCOV_EXCL_STOP

/**
  @brief Print debugging information in color

  @param[in] color  Color to print
  @param[in] format Printing format

  @ref Backend
**/
// LCOV_EXCL_START
void CeedDebugImpl256(const unsigned char color, const char *format, ...) {
  va_list args;
  va_start(args, format);
  fflush(stdout);
  if (color != CEED_DEBUG_COLOR_NONE) fprintf(stdout, "\033[38;5;%dm", color);
  vfprintf(stdout, format, args);
  if (color != CEED_DEBUG_COLOR_NONE) fprintf(stdout, "\033[m");
  fprintf(stdout, "\n");
  fflush(stdout);
  va_end(args);
}
// LCOV_EXCL_STOP

/**
  @brief Allocate an array on the host; use @ref CeedMalloc().

  Memory usage can be tracked by the library.
  This ensures sufficient alignment for vectorization and should be used for large allocations.

  @param[in]  n    Number of units to allocate
  @param[in]  unit Size of each unit
  @param[out] p    Address of pointer to hold the result

  @return An error code: 0 - success, otherwise - failure

  @ref Backend

  @sa CeedFree()
**/
int CeedMallocArray(size_t n, size_t unit, void *p) {
  int ierr = posix_memalign((void **)p, CEED_ALIGN, n * unit);
  CeedCheck(ierr == 0, NULL, CEED_ERROR_MAJOR, "posix_memalign failed to allocate %zd members of size %zd\n", n, unit);
  return CEED_ERROR_SUCCESS;
}

/**
  @brief Allocate a cleared (zeroed) array on the host; use @ref CeedCalloc().

  Memory usage can be tracked by the library.

  @param[in]  n    Number of units to allocate
  @param[in]  unit Size of each unit
  @param[out] p    Address of pointer to hold the result

  @return An error code: 0 - success, otherwise - failure

  @ref Backend

  @sa CeedFree()
**/
int CeedCallocArray(size_t n, size_t unit, void *p) {
  *(void **)p = calloc(n, unit);
  CeedCheck(!n || !unit || *(void **)p, NULL, CEED_ERROR_MAJOR, "calloc failed to allocate %zd members of size %zd\n", n, unit);
  return CEED_ERROR_SUCCESS;
}

/**
  @brief Reallocate an array on the host; use @ref CeedRealloc().

  Memory usage can be tracked by the library.

  @param[in]  n    Number of units to allocate
  @param[in]  unit Size of each unit
  @param[out] p    Address of pointer to hold the result

  @return An error code: 0 - success, otherwise - failure

  @ref Backend

  @sa CeedFree()
**/
int CeedReallocArray(size_t n, size_t unit, void *p) {
  *(void **)p = realloc(*(void **)p, n * unit);
  CeedCheck(!n || !unit || *(void **)p, NULL, CEED_ERROR_MAJOR, "realloc failed to allocate %zd members of size %zd\n", n, unit);
  return CEED_ERROR_SUCCESS;
}

/**
  @brief Allocate a cleared string buffer on the host.

  Memory usage can be tracked by the library.

  @param[in]  source Pointer to string to be copied
  @param[out] copy   Pointer to variable to hold newly allocated string copy

  @return An error code: 0 - success, otherwise - failure

  @ref Backend

  @sa CeedFree()
**/
int CeedStringAllocCopy(const char *source, char **copy) {
  size_t len = strlen(source);
  CeedCall(CeedCalloc(len + 1, copy));
  memcpy(*copy, source, len);
  return CEED_ERROR_SUCCESS;
}

/** Free memory allocated using @ref CeedMalloc() or @ref CeedCalloc()

  @param[in,out] p Address of pointer to memory.
                     This argument is of type `void*` to avoid needing a cast, but is the address of the pointer (which is zeroed) rather than the pointer.

  @return An error code: 0 - success, otherwise - failure

  @ref Backend
**/
int CeedFree(void *p) {
  free(*(void **)p);
  *(void **)p = NULL;
  return CEED_ERROR_SUCCESS;
}

/** Internal helper to manage handoff of user `source_array` to backend with proper @ref CeedCopyMode behavior.

  @param[in]     source_array          Source data provided by user
  @param[in]     copy_mode             Copy mode for the data
  @param[in]     num_values            Number of values to handle
  @param[in]     size_unit             Size of array element in bytes
  @param[in,out] target_array_owned    Pointer to location to allocated or hold owned data, may be freed if already allocated
  @param[out]    target_array_borrowed Pointer to location to hold borrowed data
  @param[out]    target_array          Pointer to location for data

  @return An error code: 0 - success, otherwise - failure

  @ref Backend
**/
static inline int CeedSetHostGenericArray(const void *source_array, CeedCopyMode copy_mode, size_t size_unit, CeedSize num_values,
                                          void *target_array_owned, void *target_array_borrowed, void *target_array) {
  switch (copy_mode) {
    case CEED_COPY_VALUES:
      if (!*(void **)target_array_owned) CeedCall(CeedCallocArray(num_values, size_unit, target_array_owned));
      if (source_array) memcpy(*(void **)target_array_owned, source_array, size_unit * num_values);
      *(void **)target_array_borrowed = NULL;
      *(void **)target_array          = *(void **)target_array_owned;
      break;
    case CEED_OWN_POINTER:
      CeedCall(CeedFree(target_array_owned));
      *(void **)target_array_owned    = (void *)source_array;
      *(void **)target_array_borrowed = NULL;
      *(void **)target_array          = *(void **)target_array_owned;
      break;
    case CEED_USE_POINTER:
      CeedCall(CeedFree(target_array_owned));
      *(void **)target_array_owned    = NULL;
      *(void **)target_array_borrowed = (void *)source_array;
      *(void **)target_array          = *(void **)target_array_borrowed;
  }
  return CEED_ERROR_SUCCESS;
}

/** Manage handoff of user `bool` `source_array` to backend with proper @ref CeedCopyMode behavior.

  @param[in]     source_array          Source data provided by user
  @param[in]     copy_mode             Copy mode for the data
  @param[in]     num_values            Number of values to handle
  @param[in,out] target_array_owned    Pointer to location to allocated or hold owned data, may be freed if already allocated
  @param[out]    target_array_borrowed Pointer to location to hold borrowed data
  @param[out]    target_array          Pointer to location for data

  @return An error code: 0 - success, otherwise - failure

  @ref Backend
**/
int CeedSetHostBoolArray(const bool *source_array, CeedCopyMode copy_mode, CeedSize num_values, const bool **target_array_owned,
                         const bool **target_array_borrowed, const bool **target_array) {
  CeedCall(CeedSetHostGenericArray(source_array, copy_mode, sizeof(bool), num_values, target_array_owned, target_array_borrowed, target_array));
  return CEED_ERROR_SUCCESS;
}

/** Manage handoff of user `CeedInt8` `source_array` to backend with proper @ref CeedCopyMode behavior.

  @param[in]     source_array          Source data provided by user
  @param[in]     copy_mode             Copy mode for the data
  @param[in]     num_values            Number of values to handle
  @param[in,out] target_array_owned    Pointer to location to allocated or hold owned data, may be freed if already allocated
  @param[out]    target_array_borrowed Pointer to location to hold borrowed data
  @param[out]    target_array          Pointer to location for data

  @return An error code: 0 - success, otherwise - failure

  @ref Backend
**/
int CeedSetHostCeedInt8Array(const CeedInt8 *source_array, CeedCopyMode copy_mode, CeedSize num_values, const CeedInt8 **target_array_owned,
                             const CeedInt8 **target_array_borrowed, const CeedInt8 **target_array) {
  CeedCall(CeedSetHostGenericArray(source_array, copy_mode, sizeof(CeedInt8), num_values, target_array_owned, target_array_borrowed, target_array));
  return CEED_ERROR_SUCCESS;
}

/** Manage handoff of user `CeedInt` `source_array` to backend with proper @ref CeedCopyMode behavior.

  @param[in]     source_array          Source data provided by user
  @param[in]     copy_mode             Copy mode for the data
  @param[in]     num_values            Number of values to handle
  @param[in,out] target_array_owned    Pointer to location to allocated or hold owned data, may be freed if already allocated
  @param[out]    target_array_borrowed Pointer to location to hold borrowed data
  @param[out]    target_array          Pointer to location for data

  @return An error code: 0 - success, otherwise - failure

  @ref Backend
**/
int CeedSetHostCeedIntArray(const CeedInt *source_array, CeedCopyMode copy_mode, CeedSize num_values, const CeedInt **target_array_owned,
                            const CeedInt **target_array_borrowed, const CeedInt **target_array) {
  CeedCall(CeedSetHostGenericArray(source_array, copy_mode, sizeof(CeedInt), num_values, target_array_owned, target_array_borrowed, target_array));
  return CEED_ERROR_SUCCESS;
}

/** Manage handoff of user `CeedScalar` `source_array` to backend with proper @ref CeedCopyMode behavior.

  @param[in]     source_array          Source data provided by user
  @param[in]     copy_mode             Copy mode for the data
  @param[in]     num_values            Number of values to handle
  @param[in,out] target_array_owned    Pointer to location to allocated or hold owned data, may be freed if already allocated
  @param[out]    target_array_borrowed Pointer to location to hold borrowed data
  @param[out]    target_array          Pointer to location for data

  @return An error code: 0 - success, otherwise - failure

  @ref Backend
**/
int CeedSetHostCeedScalarArray(const CeedScalar *source_array, CeedCopyMode copy_mode, CeedSize num_values, const CeedScalar **target_array_owned,
                               const CeedScalar **target_array_borrowed, const CeedScalar **target_array) {
  CeedCall(CeedSetHostGenericArray(source_array, copy_mode, sizeof(CeedScalar), num_values, target_array_owned, target_array_borrowed, target_array));
  return CEED_ERROR_SUCCESS;
}

/**
  @brief Register a `Ceed` backend

  @param[in] prefix   Prefix of resources for this backend to respond to.
                        For example, the reference backend responds to "/cpu/self".
  @param[in] init     Initialization function called by @ref CeedInit() when the backend is selected to drive the requested resource
  @param[in] priority Integer priority.
                        Lower values are preferred in case the resource requested by @ref CeedInit() has non-unique best prefix match.

  @return An error code: 0 - success, otherwise - failure

  @ref Backend
**/
int CeedRegister(const char *prefix, int (*init)(const char *, Ceed), unsigned int priority) {
  CeedDebugEnv("Backend Register: %s", prefix);
  CeedRegisterImpl(prefix, init, priority);
  return CEED_ERROR_SUCCESS;
}

/**
  @brief Return debugging status flag

  @param[in]  ceed     `Ceed` context to get debugging flag
  @param[out] is_debug Variable to store debugging flag

  @return An error code: 0 - success, otherwise - failure

  @ref Backend
**/
int CeedIsDebug(Ceed ceed, bool *is_debug) {
  *is_debug = ceed->is_debug;
  return CEED_ERROR_SUCCESS;
}

/**
  @brief Get the root of the requested resource

  @param[in]  ceed          `Ceed` context to get resource name of
  @param[in]  resource      Full user specified resource
  @param[in]  delineator    Delineator to break `resource_root` and `resource_spec`
  @param[out] resource_root Variable to store resource root

  @return An error code: 0 - success, otherwise - failure

  @ref Backend
**/
int CeedGetResourceRoot(Ceed ceed, const char *resource, const char *delineator, char **resource_root) {
  char  *device_spec       = strstr(resource, delineator);
  size_t resource_root_len = device_spec ? (size_t)(device_spec - resource) + 1 : strlen(resource) + 1;

  CeedCall(CeedCalloc(resource_root_len, resource_root));
  memcpy(*resource_root, resource, resource_root_len - 1);
  return CEED_ERROR_SUCCESS;
}

/**
  @brief Retrieve a parent `Ceed` context

  @param[in]  ceed   `Ceed` context to retrieve parent of
  @param[out] parent Address to save the parent to

  @return An error code: 0 - success, otherwise - failure

  @ref Backend
**/
int CeedGetParent(Ceed ceed, Ceed *parent) {
  if (ceed->parent) {
    CeedCall(CeedGetParent(ceed->parent, parent));
    return CEED_ERROR_SUCCESS;
  }
  *parent = ceed;
  return CEED_ERROR_SUCCESS;
}

/**
  @brief Retrieve a delegate `Ceed` context

  @param[in]  ceed     `Ceed` context to retrieve delegate of
  @param[out] delegate Address to save the delegate to

  @return An error code: 0 - success, otherwise - failure

  @ref Backend
**/
int CeedGetDelegate(Ceed ceed, Ceed *delegate) {
  *delegate = ceed->delegate;
  return CEED_ERROR_SUCCESS;
}

/**
  @brief Set a delegate `Ceed` context

  This function allows a `Ceed` context to set a delegate `Ceed` context.
  All backend implementations default to the delegate `Ceed` context, unless overridden.

  @param[in]  ceed     `Ceed` context to set delegate of
  @param[out] delegate Address to set the delegate to

  @return An error code: 0 - success, otherwise - failure

  @ref Backend
**/
int CeedSetDelegate(Ceed ceed, Ceed delegate) {
  ceed->delegate   = delegate;
  delegate->parent = ceed;
  return CEED_ERROR_SUCCESS;
}

/**
  @brief Retrieve a delegate `Ceed` context for a specific object type

  @param[in]  ceed     `Ceed` context to retrieve delegate of
  @param[out] delegate Address to save the delegate to
  @param[in]  obj_name Name of the object type to retrieve delegate for

  @return An error code: 0 - success, otherwise - failure

  @ref Backend
**/
int CeedGetObjectDelegate(Ceed ceed, Ceed *delegate, const char *obj_name) {
  // Check for object delegate
  for (CeedInt i = 0; i < ceed->obj_delegate_count; i++) {
    if (!strcmp(obj_name, ceed->obj_delegates->obj_name)) {
      *delegate = ceed->obj_delegates->delegate;
      return CEED_ERROR_SUCCESS;
    }
  }

  // Use default delegate if no object delegate
  CeedCall(CeedGetDelegate(ceed, delegate));
  return CEED_ERROR_SUCCESS;
}

/**
  @brief Set a delegate `Ceed` context for a specific object type

  This function allows a `Ceed` context to set a delegate `Ceed` context for a given type of `Ceed` object.
  All backend implementations default to the delegate `Ceed` context for this object.
  For example, `CeedSetObjectDelegate(ceed, delegate, "Basis")` uses delegate implementations for all `CeedBasis` backend functions.

  @param[in,out] ceed     `Ceed` context to set delegate of
  @param[in]     delegate `Ceed` context to use for delegation
  @param[in]     obj_name Name of the object type to set delegate for

  @return An error code: 0 - success, otherwise - failure

  @ref Backend
**/
int CeedSetObjectDelegate(Ceed ceed, Ceed delegate, const char *obj_name) {
  CeedInt count = ceed->obj_delegate_count;

  // Malloc or Realloc
  if (count) {
    CeedCall(CeedRealloc(count + 1, &ceed->obj_delegates));
  } else {
    CeedCall(CeedCalloc(1, &ceed->obj_delegates));
  }
  ceed->obj_delegate_count++;

  // Set object delegate
  ceed->obj_delegates[count].delegate = delegate;
  CeedCall(CeedStringAllocCopy(obj_name, &ceed->obj_delegates[count].obj_name));

  // Set delegate parent
  delegate->parent = ceed;
  return CEED_ERROR_SUCCESS;
}

/**
  @brief Get the fallback resource for `CeedOperator`

  @param[in]  ceed     `Ceed` context
  @param[out] resource Variable to store fallback resource

  @return An error code: 0 - success, otherwise - failure

  @ref Backend
**/
int CeedGetOperatorFallbackResource(Ceed ceed, const char **resource) {
  *resource = (const char *)ceed->op_fallback_resource;
  return CEED_ERROR_SUCCESS;
}

/**
  @brief Get the fallback `Ceed` for `CeedOperator`

  @param[in]  ceed          `Ceed` context
  @param[out] fallback_ceed Variable to store fallback `Ceed`

  @return An error code: 0 - success, otherwise - failure

  @ref Backend
**/
int CeedGetOperatorFallbackCeed(Ceed ceed, Ceed *fallback_ceed) {
  if (ceed->has_valid_op_fallback_resource) {
    CeedDebug256(ceed, CEED_DEBUG_COLOR_SUCCESS, "---------- CeedOperator Fallback ----------\n");
    CeedDebug(ceed, "Getting fallback from %s to %s\n", ceed->resource, ceed->op_fallback_resource);
  }

  // Create fallback Ceed if uninitalized
  if (!ceed->op_fallback_ceed && ceed->has_valid_op_fallback_resource) {
    CeedDebug(ceed, "Creating fallback Ceed");

    Ceed        fallback_ceed;
    const char *fallback_resource;

    CeedCall(CeedGetOperatorFallbackResource(ceed, &fallback_resource));
    CeedCall(CeedInit(fallback_resource, &fallback_ceed));
    fallback_ceed->op_fallback_parent = ceed;
    fallback_ceed->Error              = ceed->Error;
    ceed->op_fallback_ceed            = fallback_ceed;
  }
  *fallback_ceed = ceed->op_fallback_ceed;
  return CEED_ERROR_SUCCESS;
}

/**
  @brief Set the fallback resource for `CeedOperator`.

  The current resource, if any, is freed by calling this function.
  This string is freed upon the destruction of the `Ceed` context.

  @param[in,out] ceed     `Ceed` context
  @param[in]     resource Fallback resource to set

  @return An error code: 0 - success, otherwise - failure

  @ref Backend
**/
int CeedSetOperatorFallbackResource(Ceed ceed, const char *resource) {
  // Free old
  CeedCall(CeedFree(&ceed->op_fallback_resource));

  // Set new
  CeedCall(CeedStringAllocCopy(resource, (char **)&ceed->op_fallback_resource));

  // Check validity
  ceed->has_valid_op_fallback_resource = ceed->op_fallback_resource && ceed->resource && strcmp(ceed->op_fallback_resource, ceed->resource);
  return CEED_ERROR_SUCCESS;
}

/**
  @brief Flag `Ceed` context as deterministic

  @param[in]  ceed             `Ceed` to flag as deterministic
  @param[out] is_deterministic Deterministic status to set

  @return An error code: 0 - success, otherwise - failure

  @ref Backend
**/
int CeedSetDeterministic(Ceed ceed, bool is_deterministic) {
  ceed->is_deterministic = is_deterministic;
  return CEED_ERROR_SUCCESS;
}

/**
  @brief Set a backend function.

  This function is used for a backend to set the function associated with the Ceed objects.
  For example, `CeedSetBackendFunction(ceed, "Ceed", ceed, "VectorCreate", BackendVectorCreate)` sets the backend implementation of @ref CeedVectorCreate() and `CeedSetBackendFunction(ceed, "Basis", basis, "Apply", BackendBasisApply)` sets the backend implementation of @ref CeedBasisApply().
  Note, the prefix 'Ceed' is not required for the object type ("Basis" vs "CeedBasis").

  @param[in]  ceed      `Ceed` context for error handling
  @param[in]  type      Type of Ceed object to set function for
  @param[out] object    Ceed object to set function for
  @param[in]  func_name Name of function to set
  @param[in]  f         Function to set

  @return An error code: 0 - success, otherwise - failure

  @ref Backend
**/
int CeedSetBackendFunctionImpl(Ceed ceed, const char *type, void *object, const char *func_name, void (*f)(void)) {
  char lookup_name[CEED_MAX_RESOURCE_LEN + 1] = "";

  // Build lookup name
  if (strcmp(type, "Ceed")) strncat(lookup_name, "Ceed", CEED_MAX_RESOURCE_LEN);
  strncat(lookup_name, type, CEED_MAX_RESOURCE_LEN);
  strncat(lookup_name, func_name, CEED_MAX_RESOURCE_LEN);

  // Find and use offset
  for (CeedInt i = 0; ceed->f_offsets[i].func_name; i++) {
    if (!strcmp(ceed->f_offsets[i].func_name, lookup_name)) {
      size_t offset          = ceed->f_offsets[i].offset;
      int (**fpointer)(void) = (int (**)(void))((char *)object + offset);  // *NOPAD*

      *fpointer = (int (*)(void))f;
      return CEED_ERROR_SUCCESS;
    }
  }

  // LCOV_EXCL_START
  return CeedError(ceed, CEED_ERROR_UNSUPPORTED, "Requested function '%s' was not found for CEED object '%s'", func_name, type);
  // LCOV_EXCL_STOP
}

/**
  @brief Retrieve backend data for a `Ceed` context

  @param[in]  ceed `Ceed` context to retrieve data of
  @param[out] data Address to save data to

  @return An error code: 0 - success, otherwise - failure

  @ref Backend
**/
int CeedGetData(Ceed ceed, void *data) {
  *(void **)data = ceed->data;
  return CEED_ERROR_SUCCESS;
}

/**
  @brief Set backend data for a `Ceed` context

  @param[in,out] ceed `Ceed` context to set data of
  @param[in]     data Address of data to set

  @return An error code: 0 - success, otherwise - failure

  @ref Backend
**/
int CeedSetData(Ceed ceed, void *data) {
  ceed->data = data;
  return CEED_ERROR_SUCCESS;
}

/**
  @brief Increment the reference counter for a `Ceed` context

  @param[in,out] ceed `Ceed` context to increment the reference counter

  @return An error code: 0 - success, otherwise - failure

  @ref Backend
**/
int CeedReference(Ceed ceed) {
  ceed->ref_count++;
  return CEED_ERROR_SUCCESS;
}

/// @}

/// ----------------------------------------------------------------------------
/// Ceed Public API
/// ----------------------------------------------------------------------------
/// @addtogroup CeedUser
/// @{

/**
  @brief Get the list of available resource names for `Ceed` contexts

  Note: The caller is responsible for `free()`ing the resources and priorities arrays, but should not `free()` the contents of the resources array.

  @param[out] n          Number of available resources
  @param[out] resources  List of available resource names
  @param[out] priorities Resource name prioritization values, lower is better

  @return An error code: 0 - success, otherwise - failure

  @ref User
**/
// LCOV_EXCL_START
int CeedRegistryGetList(size_t *n, char ***const resources, CeedInt **priorities) {
  *n         = 0;
  *resources = malloc(num_backends * sizeof(**resources));
  CeedCheck(resources, NULL, CEED_ERROR_MAJOR, "malloc() failure");
  if (priorities) {
    *priorities = malloc(num_backends * sizeof(**priorities));
    CeedCheck(priorities, NULL, CEED_ERROR_MAJOR, "malloc() failure");
  }
  for (size_t i = 0; i < num_backends; i++) {
    // Only report compiled backends
    if (backends[i].priority < CEED_MAX_BACKEND_PRIORITY) {
      *resources[i] = backends[i].prefix;
      if (priorities) *priorities[i] = backends[i].priority;
      *n += 1;
    }
  }
  CeedCheck(*n, NULL, CEED_ERROR_MAJOR, "No backends installed");
  *resources = realloc(*resources, *n * sizeof(**resources));
  CeedCheck(resources, NULL, CEED_ERROR_MAJOR, "realloc() failure");
  if (priorities) {
    *priorities = realloc(*priorities, *n * sizeof(**priorities));
    CeedCheck(priorities, NULL, CEED_ERROR_MAJOR, "realloc() failure");
  }
  return CEED_ERROR_SUCCESS;
}
// LCOV_EXCL_STOP

/**
  @brief Initialize a `Ceed` context to use the specified resource.

  Note: Prefixing the resource with "help:" (e.g. "help:/cpu/self") will result in @ref CeedInt() printing the current libCEED version number and a list of current available backend resources to `stderr`.

  @param[in]  resource Resource to use, e.g., "/cpu/self"
  @param[out] ceed     The library context

  @return An error code: 0 - success, otherwise - failure

  @ref User

  @sa CeedRegister() CeedDestroy()
**/
int CeedInit(const char *resource, Ceed *ceed) {
  size_t match_len = 0, match_index = UINT_MAX, match_priority = CEED_MAX_BACKEND_PRIORITY, priority;

  // Find matching backend
  CeedCheck(resource, NULL, CEED_ERROR_MAJOR, "No resource provided");
  CeedCall(CeedRegisterAll());

  // Check for help request
  const char *help_prefix = "help";
  size_t      match_help  = 0;
  while (match_help < 4 && resource[match_help] == help_prefix[match_help]) match_help++;
  if (match_help == 4) {
    fprintf(stderr, "libCEED version: %d.%d%d%s\n", CEED_VERSION_MAJOR, CEED_VERSION_MINOR, CEED_VERSION_PATCH,
            CEED_VERSION_RELEASE ? "" : "+development");
    fprintf(stderr, "Available backend resources:\n");
    for (size_t i = 0; i < num_backends; i++) {
      // Only report compiled backends
      if (backends[i].priority < CEED_MAX_BACKEND_PRIORITY) fprintf(stderr, "  %s\n", backends[i].prefix);
    }
    fflush(stderr);
    match_help = 5;  // Delineating character expected
  } else {
    match_help = 0;
  }

  // Find best match, computed as number of matching characters from requested resource stem
  size_t stem_length = 0;
  while (resource[stem_length + match_help] && resource[stem_length + match_help] != ':') stem_length++;
  for (size_t i = 0; i < num_backends; i++) {
    size_t      n      = 0;
    const char *prefix = backends[i].prefix;
    while (prefix[n] && prefix[n] == resource[n + match_help]) n++;
    priority = backends[i].priority;
    if (n > match_len || (n == match_len && match_priority > priority)) {
      match_len      = n;
      match_priority = priority;
      match_index    = i;
    }
  }
  // Using Levenshtein distance to find closest match
  if (match_len <= 1 || match_len != stem_length) {
    // LCOV_EXCL_START
    size_t lev_dis   = UINT_MAX;
    size_t lev_index = UINT_MAX, lev_priority = CEED_MAX_BACKEND_PRIORITY;
    for (size_t i = 0; i < num_backends; i++) {
      const char *prefix        = backends[i].prefix;
      size_t      prefix_length = strlen(backends[i].prefix);
      size_t      min_len       = (prefix_length < stem_length) ? prefix_length : stem_length;
      size_t      column[min_len + 1];
      for (size_t j = 0; j <= min_len; j++) column[j] = j;
      for (size_t j = 1; j <= min_len; j++) {
        column[0] = j;
        for (size_t k = 1, last_diag = j - 1; k <= min_len; k++) {
          size_t old_diag = column[k];
          size_t min_1    = (column[k] < column[k - 1]) ? column[k] + 1 : column[k - 1] + 1;
          size_t min_2    = last_diag + (resource[k - 1] == prefix[j - 1] ? 0 : 1);
          column[k]       = (min_1 < min_2) ? min_1 : min_2;
          last_diag       = old_diag;
        }
      }
      size_t n = column[min_len];
      priority = backends[i].priority;
      if (n < lev_dis || (n == lev_dis && lev_priority > priority)) {
        lev_dis      = n;
        lev_priority = priority;
        lev_index    = i;
      }
    }
    const char *prefix_lev = backends[lev_index].prefix;
    size_t      lev_length = 0;
    while (prefix_lev[lev_length] && prefix_lev[lev_length] != '\0') lev_length++;
    size_t m = (lev_length < stem_length) ? lev_length : stem_length;
    if (lev_dis + 1 >= m) return CeedError(NULL, CEED_ERROR_MAJOR, "No suitable backend: %s", resource);
    else return CeedError(NULL, CEED_ERROR_MAJOR, "No suitable backend: %s\nClosest match: %s", resource, backends[lev_index].prefix);
    // LCOV_EXCL_STOP
  }

  // Setup Ceed
  CeedCall(CeedCalloc(1, ceed));
  CeedCall(CeedCalloc(1, &(*ceed)->jit_source_roots));
  const char *ceed_error_handler = getenv("CEED_ERROR_HANDLER");
  if (!ceed_error_handler) ceed_error_handler = "abort";
  if (!strcmp(ceed_error_handler, "exit")) (*ceed)->Error = CeedErrorExit;
  else if (!strcmp(ceed_error_handler, "store")) (*ceed)->Error = CeedErrorStore;
  else (*ceed)->Error = CeedErrorAbort;
  memcpy((*ceed)->err_msg, "No error message stored", 24);
  (*ceed)->ref_count = 1;
  (*ceed)->data      = NULL;

  // Set lookup table
  FOffset f_offsets[] = {
      CEED_FTABLE_ENTRY(Ceed, Error),
      CEED_FTABLE_ENTRY(Ceed, SetStream),
      CEED_FTABLE_ENTRY(Ceed, GetPreferredMemType),
      CEED_FTABLE_ENTRY(Ceed, Destroy),
      CEED_FTABLE_ENTRY(Ceed, VectorCreate),
      CEED_FTABLE_ENTRY(Ceed, ElemRestrictionCreate),
      CEED_FTABLE_ENTRY(Ceed, ElemRestrictionCreateAtPoints),
      CEED_FTABLE_ENTRY(Ceed, ElemRestrictionCreateBlocked),
      CEED_FTABLE_ENTRY(Ceed, BasisCreateTensorH1),
      CEED_FTABLE_ENTRY(Ceed, BasisCreateH1),
      CEED_FTABLE_ENTRY(Ceed, BasisCreateHdiv),
      CEED_FTABLE_ENTRY(Ceed, BasisCreateHcurl),
      CEED_FTABLE_ENTRY(Ceed, TensorContractCreate),
      CEED_FTABLE_ENTRY(Ceed, QFunctionCreate),
      CEED_FTABLE_ENTRY(Ceed, QFunctionContextCreate),
      CEED_FTABLE_ENTRY(Ceed, OperatorCreate),
      CEED_FTABLE_ENTRY(Ceed, OperatorCreateAtPoints),
      CEED_FTABLE_ENTRY(Ceed, CompositeOperatorCreate),
      CEED_FTABLE_ENTRY(CeedVector, HasValidArray),
      CEED_FTABLE_ENTRY(CeedVector, HasBorrowedArrayOfType),
      CEED_FTABLE_ENTRY(CeedVector, SetArray),
      CEED_FTABLE_ENTRY(CeedVector, TakeArray),
      CEED_FTABLE_ENTRY(CeedVector, SetValue),
      CEED_FTABLE_ENTRY(CeedVector, SyncArray),
      CEED_FTABLE_ENTRY(CeedVector, GetArray),
      CEED_FTABLE_ENTRY(CeedVector, GetArrayRead),
      CEED_FTABLE_ENTRY(CeedVector, GetArrayWrite),
      CEED_FTABLE_ENTRY(CeedVector, RestoreArray),
      CEED_FTABLE_ENTRY(CeedVector, RestoreArrayRead),
      CEED_FTABLE_ENTRY(CeedVector, Norm),
      CEED_FTABLE_ENTRY(CeedVector, Scale),
      CEED_FTABLE_ENTRY(CeedVector, AXPY),
      CEED_FTABLE_ENTRY(CeedVector, AXPBY),
      CEED_FTABLE_ENTRY(CeedVector, PointwiseMult),
      CEED_FTABLE_ENTRY(CeedVector, Reciprocal),
      CEED_FTABLE_ENTRY(CeedVector, Destroy),
      CEED_FTABLE_ENTRY(CeedElemRestriction, Apply),
      CEED_FTABLE_ENTRY(CeedElemRestriction, ApplyUnsigned),
      CEED_FTABLE_ENTRY(CeedElemRestriction, ApplyUnoriented),
      CEED_FTABLE_ENTRY(CeedElemRestriction, ApplyAtPointsInElement),
      CEED_FTABLE_ENTRY(CeedElemRestriction, ApplyBlock),
      CEED_FTABLE_ENTRY(CeedElemRestriction, GetOffsets),
      CEED_FTABLE_ENTRY(CeedElemRestriction, GetOrientations),
      CEED_FTABLE_ENTRY(CeedElemRestriction, GetCurlOrientations),
      CEED_FTABLE_ENTRY(CeedElemRestriction, GetAtPointsElementOffset),
      CEED_FTABLE_ENTRY(CeedElemRestriction, Destroy),
      CEED_FTABLE_ENTRY(CeedBasis, Apply),
      CEED_FTABLE_ENTRY(CeedBasis, ApplyAtPoints),
      CEED_FTABLE_ENTRY(CeedBasis, Destroy),
      CEED_FTABLE_ENTRY(CeedTensorContract, Apply),
      CEED_FTABLE_ENTRY(CeedTensorContract, Destroy),
      CEED_FTABLE_ENTRY(CeedQFunction, Apply),
      CEED_FTABLE_ENTRY(CeedQFunction, SetCUDAUserFunction),
      CEED_FTABLE_ENTRY(CeedQFunction, SetHIPUserFunction),
      CEED_FTABLE_ENTRY(CeedQFunction, Destroy),
      CEED_FTABLE_ENTRY(CeedQFunctionContext, HasValidData),
      CEED_FTABLE_ENTRY(CeedQFunctionContext, HasBorrowedDataOfType),
      CEED_FTABLE_ENTRY(CeedQFunctionContext, SetData),
      CEED_FTABLE_ENTRY(CeedQFunctionContext, TakeData),
      CEED_FTABLE_ENTRY(CeedQFunctionContext, GetData),
      CEED_FTABLE_ENTRY(CeedQFunctionContext, GetDataRead),
      CEED_FTABLE_ENTRY(CeedQFunctionContext, RestoreData),
      CEED_FTABLE_ENTRY(CeedQFunctionContext, RestoreDataRead),
      CEED_FTABLE_ENTRY(CeedQFunctionContext, DataDestroy),
      CEED_FTABLE_ENTRY(CeedQFunctionContext, Destroy),
      CEED_FTABLE_ENTRY(CeedOperator, LinearAssembleQFunction),
      CEED_FTABLE_ENTRY(CeedOperator, LinearAssembleQFunctionUpdate),
      CEED_FTABLE_ENTRY(CeedOperator, LinearAssembleDiagonal),
      CEED_FTABLE_ENTRY(CeedOperator, LinearAssembleAddDiagonal),
      CEED_FTABLE_ENTRY(CeedOperator, LinearAssemblePointBlockDiagonal),
      CEED_FTABLE_ENTRY(CeedOperator, LinearAssembleAddPointBlockDiagonal),
      CEED_FTABLE_ENTRY(CeedOperator, LinearAssembleSymbolic),
      CEED_FTABLE_ENTRY(CeedOperator, LinearAssemble),
      CEED_FTABLE_ENTRY(CeedOperator, LinearAssembleSingle),
      CEED_FTABLE_ENTRY(CeedOperator, CreateFDMElementInverse),
      CEED_FTABLE_ENTRY(CeedOperator, Apply),
      CEED_FTABLE_ENTRY(CeedOperator, ApplyComposite),
      CEED_FTABLE_ENTRY(CeedOperator, ApplyAdd),
      CEED_FTABLE_ENTRY(CeedOperator, ApplyAddComposite),
      CEED_FTABLE_ENTRY(CeedOperator, ApplyJacobian),
      CEED_FTABLE_ENTRY(CeedOperator, Destroy),
      {NULL, 0}  // End of lookup table - used in SetBackendFunction loop
  };

  CeedCall(CeedCalloc(sizeof(f_offsets), &(*ceed)->f_offsets));
  memcpy((*ceed)->f_offsets, f_offsets, sizeof(f_offsets));

  // Set fallback for advanced CeedOperator functions
  const char fallback_resource[] = "";
  CeedCall(CeedSetOperatorFallbackResource(*ceed, fallback_resource));

  // Record env variables CEED_DEBUG or DBG
  (*ceed)->is_debug = getenv("CEED_DEBUG") || getenv("DEBUG") || getenv("DBG");

  // Copy resource prefix, if backend setup successful
  CeedCall(CeedStringAllocCopy(backends[match_index].prefix, (char **)&(*ceed)->resource));

  // Set default JiT source root
  // Note: there will always be the default root for every Ceed but all additional paths are added to the top-most parent
  CeedCall(CeedAddJitSourceRoot(*ceed, (char *)CeedJitSourceRootDefault));

  // Backend specific setup
  CeedCall(backends[match_index].init(&resource[match_help], *ceed));
  return CEED_ERROR_SUCCESS;
}

/**
  @brief Set the GPU stream for a `Ceed` context

  @param[in,out] ceed   `Ceed` context to set the stream
  @param[in]     handle Handle to GPU stream

  @return An error code: 0 - success, otherwise - failure

  @ref User
**/
int CeedSetStream(Ceed ceed, void *handle) {
  CeedCheck(handle, ceed, CEED_ERROR_INCOMPATIBLE, "Stream handle must be non-NULL");
  if (ceed->SetStream) {
    CeedCall(ceed->SetStream(ceed, handle));
  } else {
    Ceed delegate;
    CeedCall(CeedGetDelegate(ceed, &delegate));

    if (delegate) CeedCall(CeedSetStream(delegate, handle));
    else return CeedError(ceed, CEED_ERROR_UNSUPPORTED, "Backend does not support setting stream");
  }
  return CEED_ERROR_SUCCESS;
}

/**
  @brief Copy the pointer to a `Ceed` context.

  Both pointers should be destroyed with @ref CeedDestroy().

  Note: If the value of `*ceed_copy` passed to this function is non-`NULL`, then it is assumed that `*ceed_copy` is a pointer to a `Ceed` context.
        This `Ceed` context will be destroyed if `*ceed_copy` is the only reference to this `Ceed` context.

  @param[in]     ceed      `Ceed` context to copy reference to
  @param[in,out] ceed_copy Variable to store copied reference

  @return An error code: 0 - success, otherwise - failure

  @ref User
**/
int CeedReferenceCopy(Ceed ceed, Ceed *ceed_copy) {
  CeedCall(CeedReference(ceed));
  CeedCall(CeedDestroy(ceed_copy));
  *ceed_copy = ceed;
  return CEED_ERROR_SUCCESS;
}

/**
  @brief Get the full resource name for a `Ceed` context

  @param[in]  ceed     `Ceed` context to get resource name of
  @param[out] resource Variable to store resource name

  @return An error code: 0 - success, otherwise - failure

  @ref User
**/
int CeedGetResource(Ceed ceed, const char **resource) {
  *resource = (const char *)ceed->resource;
  return CEED_ERROR_SUCCESS;
}

/**
  @brief Return `Ceed` context preferred memory type

  @param[in]  ceed     `Ceed` context to get preferred memory type of
  @param[out] mem_type Address to save preferred memory type to

  @return An error code: 0 - success, otherwise - failure

  @ref User
**/
int CeedGetPreferredMemType(Ceed ceed, CeedMemType *mem_type) {
  if (ceed->GetPreferredMemType) {
    CeedCall(ceed->GetPreferredMemType(mem_type));
  } else {
    Ceed delegate;
    CeedCall(CeedGetDelegate(ceed, &delegate));

    if (delegate) {
      CeedCall(CeedGetPreferredMemType(delegate, mem_type));
    } else {
      *mem_type = CEED_MEM_HOST;
    }
  }
  return CEED_ERROR_SUCCESS;
}

/**
  @brief Get deterministic status of `Ceed` context

  @param[in]  ceed             `Ceed` context
  @param[out] is_deterministic Variable to store deterministic status

  @return An error code: 0 - success, otherwise - failure

  @ref User
**/
int CeedIsDeterministic(Ceed ceed, bool *is_deterministic) {
  *is_deterministic = ceed->is_deterministic;
  return CEED_ERROR_SUCCESS;
}

/**
  @brief Set additional JiT source root for `Ceed` context

  @param[in,out] ceed            `Ceed` context
  @param[in]     jit_source_root Absolute path to additional JiT source directory

  @return An error code: 0 - success, otherwise - failure

  @ref User
**/
int CeedAddJitSourceRoot(Ceed ceed, const char *jit_source_root) {
  Ceed ceed_parent;

  CeedCall(CeedGetParent(ceed, &ceed_parent));

  CeedInt index       = ceed_parent->num_jit_source_roots;
  size_t  path_length = strlen(jit_source_root);

  CeedCall(CeedRealloc(index + 1, &ceed_parent->jit_source_roots));
  CeedCall(CeedCalloc(path_length + 1, &ceed_parent->jit_source_roots[index]));
  memcpy(ceed_parent->jit_source_roots[index], jit_source_root, path_length);
  ceed_parent->num_jit_source_roots++;
  return CEED_ERROR_SUCCESS;
}

/**
  @brief View a `Ceed`

  @param[in] ceed   `Ceed` to view
  @param[in] stream Filestream to write to

  @return An error code: 0 - success, otherwise - failure

  @ref User
**/
int CeedView(Ceed ceed, FILE *stream) {
  CeedMemType mem_type;

  CeedCall(CeedGetPreferredMemType(ceed, &mem_type));

  fprintf(stream,
          "Ceed\n"
          "  Ceed Resource: %s\n"
          "  Preferred MemType: %s\n",
          ceed->resource, CeedMemTypes[mem_type]);
  return CEED_ERROR_SUCCESS;
}

/**
  @brief Destroy a `Ceed`

  @param[in,out] ceed Address of `Ceed` context to destroy

  @return An error code: 0 - success, otherwise - failure

  @ref User
**/
int CeedDestroy(Ceed *ceed) {
  if (!*ceed || --(*ceed)->ref_count > 0) {
    *ceed = NULL;
    return CEED_ERROR_SUCCESS;
  }
  if ((*ceed)->delegate) CeedCall(CeedDestroy(&(*ceed)->delegate));

  if ((*ceed)->obj_delegate_count > 0) {
    for (CeedInt i = 0; i < (*ceed)->obj_delegate_count; i++) {
      CeedCall(CeedDestroy(&((*ceed)->obj_delegates[i].delegate)));
      CeedCall(CeedFree(&(*ceed)->obj_delegates[i].obj_name));
    }
    CeedCall(CeedFree(&(*ceed)->obj_delegates));
  }

  if ((*ceed)->Destroy) CeedCall((*ceed)->Destroy(*ceed));

  for (CeedInt i = 0; i < (*ceed)->num_jit_source_roots; i++) {
    CeedCall(CeedFree(&(*ceed)->jit_source_roots[i]));
  }
  CeedCall(CeedFree(&(*ceed)->jit_source_roots));

  CeedCall(CeedFree(&(*ceed)->f_offsets));
  CeedCall(CeedFree(&(*ceed)->resource));
  CeedCall(CeedDestroy(&(*ceed)->op_fallback_ceed));
  CeedCall(CeedFree(&(*ceed)->op_fallback_resource));
  CeedCall(CeedFree(ceed));
  return CEED_ERROR_SUCCESS;
}

// LCOV_EXCL_START
const char *CeedErrorFormat(Ceed ceed, const char *format, va_list *args) {
  if (ceed->parent) return CeedErrorFormat(ceed->parent, format, args);
  if (ceed->op_fallback_parent) return CeedErrorFormat(ceed->op_fallback_parent, format, args);
  // Using pointer to va_list for better FFI, but clang-tidy can't verify va_list is initalized
  vsnprintf(ceed->err_msg, CEED_MAX_RESOURCE_LEN, format, *args);  // NOLINT
  return ceed->err_msg;
}
// LCOV_EXCL_STOP

/**
  @brief Error handling implementation; use @ref CeedError() instead.

  @return An error code: 0 - success, otherwise - failure

  @ref Developer
**/
int CeedErrorImpl(Ceed ceed, const char *filename, int lineno, const char *func, int ecode, const char *format, ...) {
  va_list args;
  int     ret_val;

  va_start(args, format);
  if (ceed) {
    ret_val = ceed->Error(ceed, filename, lineno, func, ecode, format, &args);
  } else {
    // LCOV_EXCL_START
    const char *ceed_error_handler = getenv("CEED_ERROR_HANDLER");
    if (!ceed_error_handler) ceed_error_handler = "abort";
    if (!strcmp(ceed_error_handler, "return")) {
      ret_val = CeedErrorReturn(ceed, filename, lineno, func, ecode, format, &args);
    } else {
      // This function will not return
      ret_val = CeedErrorAbort(ceed, filename, lineno, func, ecode, format, &args);
    }
  }
  va_end(args);
  return ret_val;
  // LCOV_EXCL_STOP
}

/**
  @brief Error handler that returns without printing anything.

  Pass this to @ref CeedSetErrorHandler() to obtain this error handling behavior.

  @return An error code: 0 - success, otherwise - failure

  @ref Developer
**/
// LCOV_EXCL_START
int CeedErrorReturn(Ceed ceed, const char *filename, int line_no, const char *func, int err_code, const char *format, va_list *args) {
  return err_code;
}
// LCOV_EXCL_STOP

/**
  @brief Error handler that stores the error message for future use and returns the error.

  Pass this to @ref CeedSetErrorHandler() to obtain this error handling behavior.

  @return An error code: 0 - success, otherwise - failure

  @ref Developer
**/
// LCOV_EXCL_START
int CeedErrorStore(Ceed ceed, const char *filename, int line_no, const char *func, int err_code, const char *format, va_list *args) {
  if (ceed->parent) return CeedErrorStore(ceed->parent, filename, line_no, func, err_code, format, args);
  if (ceed->op_fallback_parent) return CeedErrorStore(ceed->op_fallback_parent, filename, line_no, func, err_code, format, args);

  // Build message
  int len = snprintf(ceed->err_msg, CEED_MAX_RESOURCE_LEN, "%s:%d in %s(): ", filename, line_no, func);
  // Using pointer to va_list for better FFI, but clang-tidy can't verify va_list is initalized
  vsnprintf(ceed->err_msg + len, CEED_MAX_RESOURCE_LEN - len, format, *args);  // NOLINT
  return err_code;
}
// LCOV_EXCL_STOP

/**
  @brief Error handler that prints to `stderr` and aborts

  Pass this to @ref CeedSetErrorHandler() to obtain this error handling behavior.

  @return An error code: 0 - success, otherwise - failure

  @ref Developer
**/
// LCOV_EXCL_START
int CeedErrorAbort(Ceed ceed, const char *filename, int line_no, const char *func, int err_code, const char *format, va_list *args) {
  fprintf(stderr, "%s:%d in %s(): ", filename, line_no, func);
  vfprintf(stderr, format, *args);
  fprintf(stderr, "\n");
  abort();
  return err_code;
}
// LCOV_EXCL_STOP

/**
  @brief Error handler that prints to `stderr` and exits.

  Pass this to @ref CeedSetErrorHandler() to obtain this error handling behavior.

  In contrast to @ref CeedErrorAbort(), this exits without a signal, so `atexit()` handlers (e.g., as used by gcov) are run.

  @return An error code: 0 - success, otherwise - failure

  @ref Developer
**/
int CeedErrorExit(Ceed ceed, const char *filename, int line_no, const char *func, int err_code, const char *format, va_list *args) {
  fprintf(stderr, "%s:%d in %s(): ", filename, line_no, func);
  // Using pointer to va_list for better FFI, but clang-tidy can't verify va_list is initalized
  vfprintf(stderr, format, *args);  // NOLINT
  fprintf(stderr, "\n");
  exit(err_code);
  return err_code;
}

/**
  @brief Set error handler

  A default error handler is set in @ref CeedInit().
  Use this function to change the error handler to @ref CeedErrorReturn(), @ref CeedErrorAbort(), or a user-defined error handler.

  @return An error code: 0 - success, otherwise - failure

  @ref Developer
**/
int CeedSetErrorHandler(Ceed ceed, CeedErrorHandler handler) {
  ceed->Error = handler;
  if (ceed->delegate) CeedSetErrorHandler(ceed->delegate, handler);
  for (CeedInt i = 0; i < ceed->obj_delegate_count; i++) CeedSetErrorHandler(ceed->obj_delegates[i].delegate, handler);
  return CEED_ERROR_SUCCESS;
}

/**
  @brief Get error message

  The error message is only stored when using the error handler @ref CeedErrorStore()

  @param[in]  ceed    `Ceed` context to retrieve error message
  @param[out] err_msg Char pointer to hold error message

  @return An error code: 0 - success, otherwise - failure

  @ref Developer
**/
int CeedGetErrorMessage(Ceed ceed, const char **err_msg) {
  if (ceed->parent) return CeedGetErrorMessage(ceed->parent, err_msg);
  if (ceed->op_fallback_parent) return CeedGetErrorMessage(ceed->op_fallback_parent, err_msg);
  *err_msg = ceed->err_msg;
  return CEED_ERROR_SUCCESS;
}

/**
  @brief Restore error message.

  The error message is only stored when using the error handler @ref CeedErrorStore().

  @param[in]  ceed    `Ceed` context to restore error message
  @param[out] err_msg Char pointer that holds error message

  @return An error code: 0 - success, otherwise - failure

  @ref Developer
**/
int CeedResetErrorMessage(Ceed ceed, const char **err_msg) {
  if (ceed->parent) return CeedResetErrorMessage(ceed->parent, err_msg);
  if (ceed->op_fallback_parent) return CeedResetErrorMessage(ceed->op_fallback_parent, err_msg);
  *err_msg = NULL;
  memcpy(ceed->err_msg, "No error message stored", 24);
  return CEED_ERROR_SUCCESS;
}

/**
  @brief Get libCEED library version information.

  libCEED version numbers have the form major.minor.patch.
  Non-release versions may contain unstable interfaces.

  @param[out] major   Major version of the library
  @param[out] minor   Minor version of the library
  @param[out] patch   Patch (subminor) version of the library
  @param[out] release True for releases; false for development branches

  The caller may pass `NULL` for any arguments that are not needed.

  @return An error code: 0 - success, otherwise - failure

  @ref Developer

  @sa CEED_VERSION_GE()
*/
int CeedGetVersion(int *major, int *minor, int *patch, bool *release) {
  if (major) *major = CEED_VERSION_MAJOR;
  if (minor) *minor = CEED_VERSION_MINOR;
  if (patch) *patch = CEED_VERSION_PATCH;
  if (release) *release = CEED_VERSION_RELEASE;
  return CEED_ERROR_SUCCESS;
}

/**
  @brief Get libCEED scalar type, such as F64 or F32

  @param[out] scalar_type Type of libCEED scalars

  @return An error code: 0 - success, otherwise - failure

  @ref Developer
*/
int CeedGetScalarType(CeedScalarType *scalar_type) {
  *scalar_type = CEED_SCALAR_TYPE;
  return CEED_ERROR_SUCCESS;
}

/// @}
