/******************************************************************************
 * Copyright (c) 2024 Lawrence Livermore National Security, LLC and other
 * HYPRE Project Developers. See the top-level COPYRIGHT file for details.
 *
 * SPDX-License-Identifier: MIT
 ******************************************************************************/

#ifndef UTILS_HEADER
#define UTILS_HEADER

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "HYPRE_utilities.h"
#include "internal/compatibility.h"
#include "internal/error.h"

/*-----------------------------------------------------------------------------
 * HYPRE_SAFE_CALL macro
 *-----------------------------------------------------------------------------*/

// Macro for safely calling HYPRE functions.
// Sets HYPREDRV error codes and returns early from the calling function.
// This macro does NOT abort - it allows error propagation through HYPREDRV's error
// system.
#ifndef HYPRE_SAFE_CALL
#define HYPRE_SAFE_CALL(call)                                                     \
   do                                                                             \
   {                                                                              \
      HYPRE_Int hypre_ierr = (call);                                              \
      /* GCOVR_EXCL_BR_START */ /* SAFE_CALL hypre error path */                  \
      if (hypre_ierr != 0)      /* GCOVR_EXCL_BR_STOP */                          \
      {                                                                           \
         hypredrv_ErrorCodeSet(ERROR_HYPRE_INTERNAL);                             \
         char hypre_err_msg[HYPRE_MAX_MSG_LEN];                                   \
         HYPRE_DescribeError(hypre_ierr, hypre_err_msg);                          \
         hypredrv_ErrorMsgAdd("HYPRE call failed at %s:%d in %s(): %s", __FILE__, \
                              __LINE__, __func__, hypre_err_msg);                 \
         return;                                                                  \
      }                                                                           \
   } while (0)
#endif

/*-----------------------------------------------------------------------------
 * Public prototypes
 *-----------------------------------------------------------------------------*/

char *hypredrv_StrToLowerCase(char *);
char *hypredrv_StrTrim(char *);
void  hypredrv_TrimTrailingWhitespace(char *);
void  hypredrv_NormalizeWhitespace(char *);
int   hypredrv_CheckBinaryDataExists(const char *);
int   hypredrv_CheckASCIIDataExists(const char *);
int   hypredrv_CountNumberOfPartitions(const char *);
int   hypredrv_BinaryPathPrefixIsSafe(const char *prefix);
FILE *hypredrv_FopenCreateRestricted(const char *path, int append, int binary);
int   hypredrv_ComputeNumberOfDigits(int);
void  hypredrv_SplitFilename(const char *, char **, char **);
void  hypredrv_CombineFilename(const char *, const char *, char **);
bool  hypredrv_IsYAMLFilename(const char *);
bool  hypredrv_PathIsUnderRoot(const char *, const char *);

/* Clear hypre's sticky error flag after a public setup/solve boundary call.
 * Convergence and argument-flag warnings are treated as soft results.
 * Use this at API edges that previously called HYPRE_ClearAllErrors(). For
 * nested/inexact solvers that should only discard HYPRE_ERROR_CONV, call
 * hypredrv_HypreClearConvergenceErrors() instead. */
void hypredrv_HypreConsumeErrors(void);

/* Clear hypre's sticky error flag only when it is exclusively HYPRE_ERROR_CONV.
 * Harder hypre failures are left intact. Prefer hypredrv_HypreConsumeErrors() at
 * public API boundaries. */
void hypredrv_HypreClearConvergenceErrors(void);

static inline int
hypredrv_FloatIsFinite(float value)
{
   uint32_t bits = 0;

   memcpy(&bits, &value, sizeof(bits));
   return (bits & UINT32_C(0x7f800000)) != UINT32_C(0x7f800000);
}

static inline int
hypredrv_DoubleIsFinite(double value)
{
   uint64_t bits = 0;

   memcpy(&bits, &value, sizeof(bits));
   return (bits & UINT64_C(0x7ff0000000000000)) != UINT64_C(0x7ff0000000000000);
}

/*******************************************************************************
 *******************************************************************************/

/* Check if HYPRE_DEVELOP_NUMBER is greater or equal than a given value */
#define HYPRE_DEVELOP_NUMBER_GE(develop) (HYPREDRV_HYPRE_DEVELOP_NUMBER >= (develop))

/* Check if HYPRE_RELEASE_NUMBER is greater than a given value */
#define HYPRE_RELEASE_NUMBER_GT(release) (HYPREDRV_HYPRE_RELEASE_NUMBER > (release))

/* Check for a specific hypre release number and whether
   HYPRE_DEVELOP_NUMBER is defined and greater or equal than a given value */
#define HYPRE_RELEASE_NUMBER_EQ_AND_DEVELOP_NUMBER_GE(release, develop) \
   (HYPREDRV_HYPRE_RELEASE_NUMBER == (release) && HYPRE_DEVELOP_NUMBER_GE(develop))

/* Check for minimum HYPRE version in order to allow certain features.
 *
 * Exact tagged releases may omit HYPRE_DEVELOP_NUMBER from generated headers.
 * Those tags satisfy same-release checks only when the requested develop number
 * is zero.  Positive develop-number checks are for post-release snapshots and
 * must not be enabled for the exact release tag. */
#define HYPRE_CHECK_MIN_VERSION(release, develop)  \
   (HYPRE_RELEASE_NUMBER_GT(release) ||            \
    (HYPREDRV_HYPRE_RELEASE_NUMBER == (release) && \
     ((develop) == 0 || HYPRE_DEVELOP_NUMBER_GE(develop))))

/* Mark a function or variable that may be unused in some build configurations
   (e.g. gated behind an optional dependency), suppressing -Wunused warnings. */
#if defined(__GNUC__) || defined(__clang__) || defined(__NVCOMPILER)
#define HYPREDRV_MAYBE_UNUSED __attribute__((unused))
#else
#define HYPREDRV_MAYBE_UNUSED
#endif

enum
{
   GB_TO_BYTES        = (1 << 30),
   MAX_DIVISOR_LENGTH = 87,
};

static inline void
PrintLine(char ch, size_t len)
{
   for (size_t i = 0; i < len; i++)
   {
      putchar(ch);
   }
   putchar('\n');
}

#define PRINT_DASHED_LINE(_l) PrintLine('-', (_l))
#define PRINT_EQUAL_LINE(_l) PrintLine('=', (_l))

#define HAVE_COLORS 0 // TODO: add a runtime option for this parameter

#if HAVE_COLORS
#define TEXT_RESET "\033[0m"
#define TEXT_GREEN "\033[32m"
#define TEXT_BOLD "\033[1m"
#define TEXT_REDBOLD "\033[1;31m"
#define TEXT_YELLOWBOLD "\033[1;33m"
#else
#define TEXT_RESET ""
#define TEXT_GREEN ""
#define TEXT_BOLD ""
#define TEXT_REDBOLD ""
#define TEXT_YELLOWBOLD ""
#endif

/* Check if two types match */
#ifdef __GNUC__
#define TYPES_MATCH(T1, T2) __builtin_types_compatible_p(T1, T2)
#else
#define TYPES_MATCH(T1, T2) (sizeof(T1) == sizeof(T2)) /* fallback */
#endif

#endif /* UTILS_HEADER */
