/*
 * Copyright(C) 1999-2025 National Technology & Engineering Solutions
 * of Sandia, LLC (NTESS).  Under the terms of Contract DE-NA0003525 with
 * NTESS, the U.S. Government retains certain rights in this software.
 *
 * See packages/seacas/LICENSE for details
 */

#include "exodusII.h" // for exoptval, MAX_ERR_LENGTH, etc
#include "exodusII_int.h"

#if defined(EXODUS_THREADSAFE)
EX_errval_t *ex_errval = NULL;
#define EX_PNAME   ex_errval->last_pname
#define EX_ERRMSG  ex_errval->last_errmsg
#define EX_ERR_NUM ex_errval->last_err_num
#else
int exerrval = 0; /* clear initial global error code value */

static char last_pname[MAX_ERR_LENGTH + 1];
static char last_errmsg[MAX_ERR_LENGTH + 1];
static int  last_err_num;

#define EX_PNAME   last_pname
#define EX_ERRMSG  last_errmsg
#define EX_ERR_NUM last_err_num
#endif

/*!
  \ingroup Utilities
  \undoc
*/
void exi_reset_error_status(void)
{
#if !defined(EXODUS_THREADSAFE)
  exerrval   = 0;
  EX_ERR_NUM = 0;
#endif
}

/*!
\ingroup Utilities
The function ex_err(exoid, ...) logs an error to stderr. It is intended
to provide explanatory messages for error codes returned from other
exodus routines.

The passed in error codes and corresponding messages are listed in
\file{exodusII.h}. The programmer may supplement the error message printed
for standard errors by providing an error message. If the error code
is provided with no error message, the predefined message will be
used. The error code \c EX_MSG is available to log application
specific messages.

\param[in]  module_name  This is a string containing the name of the calling function.
\param[in]  message      This is a string containing a message explaining the error
                         or problem. If \c EX_VERBOSE (see ex_opts()) is true,
                         this message will be printed to stderr. Otherwise,
                         nothing will be printed. Maximum length is \c MAX_ERR_LENGTH.

\param[in] err_num       This is an integer code identifying the error. exodus C
                         functions place an error code value in exerrval,
                         an external integer. Negative values are considered
                         fatal errors while positive values are warnings.
                         There is a set of predefined values defined in
                         \file{exodusII.h}. The predefined constant
                         \c EX_PRTLASTMSG will cause the last error message
                         to be output, regardless of the setting
                         of the error reporting level (see ex_opts()).

The following is an example of the use of this function:

~~~{.c}
int exoid, CPU_word_size, IO_word_size, errval;
float version;
char errmsg[MAX_ERR_LENGTH];

CPU_word_size = sizeof(float);
IO_word_size = 0;

\comment{open exodus file}
if (exoid = ex_open ("test.exo", EX_READ, &CPU_word_size,
                     &IO_word_size, &version)) {
   errval = 999;
   snprintf(errmsg, MAX_ERR_LENGTH,"ERROR: cannot open file test.exo");
   ex_err(__func__, errmsg, errval);
}
~~~

*/
void ex_err(const char *module_name, const char *message, int err_num)
{
  EX_FUNC_ENTER_INT();
  if (err_num == 0) { /* zero is no error, ignore and return */
    exerrval = err_num;
    EX_FUNC_VOID();
  }

  /* save the error message for replays */
  if (message != NULL) {
    ex_copy_string(EX_ERRMSG, message, MAX_ERR_LENGTH + 1);
  }
  if (module_name != NULL) {
    ex_copy_string(EX_PNAME, module_name, MAX_ERR_LENGTH + 1);
  }

  if (err_num == EX_PRTLASTMSG) {
    fprintf(stderr, "\n[%s] %s\n", EX_PNAME, EX_ERRMSG);
    fprintf(stderr, "    exerrval = %d\n", EX_ERR_NUM);
    fprintf(stderr, "\t%s\n", ex_strerror(EX_ERR_NUM));
    EX_FUNC_VOID();
  }

  if (err_num == EX_LASTERR) {
    err_num = EX_ERR_NUM;
  }
  else {
    exerrval   = err_num;
    EX_ERR_NUM = err_num;
  }

  if (err_num == EX_NULLENTITY) {
    if (exoptval & EX_NULLVERBOSE) {
      fprintf(stderr, "\nExodus Library Warning: [%s]\n\t%s\n", module_name, message);
    }
  }

  else if (exoptval & EX_VERBOSE) { /* check see if we really want to hear this */
    fprintf(stderr, "\nExodus Library Warning/Error: [%s]\n\t%s\n", module_name, message);
    fprintf(stderr, "\t%s\n", ex_strerror(err_num));
  }
  fflush(stderr);

  /* with netCDF 3.4, (fatal) system error codes are > 0;
     so all EXODUS fatal error codes are > 0    */
  if ((err_num > 0) && (exoptval & EX_ABORT)) {
    exit(err_num);
  }
  EX_FUNC_VOID();
}

/*!
\ingroup Utilities
The function ex_err_fn() logs an error to stderr. It is intended
to provide explanatory messages for error codes returned from other
exodus routines.  The main difference between ex_err_fn() and ex_err() is
that ex_err_fn() will print the name of the exodus file that the error occurred on.

The passed in error codes and corresponding messages are listed in
\file{exodusII.h}. The programmer may supplement the error message printed
for standard errors by providing an error message. If the error code
is provided with no error message, the predefined message will be
used. The error code \c EX_MSG is available to log application
specific messages.

\param[in]   exoid       exodus file ID returned from a previous call to ex_create() or ex_open().
\param[in]  module_name  This is a string containing the name of the calling function.
\param[in]  message      This is a string containing a message explaining the error
                         or problem. If \c EX_VERBOSE (see ex_opts()) is true,
                         this message will be printed to stderr. Otherwise,
                         nothing will be printed. Maximum length is \c MAX_ERR_LENGTH.

\param[in] err_num       This is an integer code identifying the error. exodus C
                         functions place an error code value in exerrval,
                         an external integer. Negative values are considered
                         fatal errors while positive values are warnings.
                         There is a set of predefined values defined in
                         \file{exodusII.h}. The predefined constant
                         \c EX_PRTLASTMSG will cause the last error message
                         to be output, regardless of the setting
                         of the error reporting level (see ex_opts()).

The following is an example of the use of this function:

~~~{.c}
int exoid, CPU_word_size, IO_word_size, errval;
float version;
char errmsg[MAX_ERR_LENGTH];

CPU_word_size = sizeof(float);
IO_word_size = 0;

\comment{open exodus file}
if (exoid = ex_open ("test.exo", EX_READ, &CPU_word_size,
                     &IO_word_size, &version)) {
   errval = 999;
   snprintf(errmsg, MAX_ERR_LENGTH,"ERROR: cannot open file test.exo");
   ex_err_fn(exoid, __func__, errmsg, errval);
}
~~~

*/
void ex_err_fn(int exoid, const char *module_name, const char *message, int err_num)
{
  EX_FUNC_ENTER_INT();
  if (err_num == 0) { /* zero is no error, ignore and return */
    exerrval = err_num;
    EX_FUNC_VOID();
  }

  /* save the error message for replays */
  if (message != NULL) {
    ex_copy_string(EX_ERRMSG, message, MAX_ERR_LENGTH + 1);
  }
  if (module_name != NULL) {
    ex_copy_string(EX_PNAME, module_name, MAX_ERR_LENGTH + 1);
  }

  if (err_num == EX_PRTLASTMSG) {
    fprintf(stderr, "\n[%s] %s\n", EX_PNAME, EX_ERRMSG);

    struct exi_file_item *file = exi_find_file_item(exoid);
    if (file) {
      size_t pathlen = 0;
      nc_inq_path(exoid, &pathlen, NULL);
      char *path = NULL;
      if (pathlen > 0) {
        path = malloc(pathlen + 1);
        if (path != NULL) {
          nc_inq_path(exoid, NULL, path);
          fprintf(stderr, "    in file '%s'", path);
          free(path);
        }
      }
    }

    fprintf(stderr, "    exerrval = %d\n", EX_ERR_NUM);
    fprintf(stderr, "\t%s\n", ex_strerror(EX_ERR_NUM));
    fflush(stderr);
    EX_FUNC_VOID();
  }

  if (err_num == EX_LASTERR) {
    err_num = EX_ERR_NUM;
  }
  else {
    exerrval   = err_num;
    EX_ERR_NUM = err_num;
  }

  if (err_num == EX_NULLENTITY) {
    if (exoptval & EX_NULLVERBOSE) {
      fprintf(stderr, "\nExodus Library Warning: [%s]\n\t%s\n", module_name, message);
      fflush(stderr);
    }
  }

  else if (exoptval & EX_VERBOSE) { /* check see if we really want to hear this */
    char                 *path = NULL;
    struct exi_file_item *file = exi_find_file_item(exoid);
    if (file) {
      size_t pathlen = 0;
      nc_inq_path(exoid, &pathlen, NULL);
      if (pathlen > 0) {
        path = malloc(pathlen + 1);
        nc_inq_path(exoid, NULL, path);
      }
    }
    if (path) {
      fprintf(stderr, "\nExodus Library Warning/Error: [%s] in file '%s'\n\t%s\n", module_name,
              path, message);
      free(path);
    }
    else {
      fprintf(stderr, "\nExodus Library Warning/Error: [%s]\n\t%s\n", module_name, message);
    }
    fprintf(stderr, "\t%s\n", ex_strerror(err_num));
    fflush(stderr);
  }

  /* with netCDF 3.4, (fatal) system error codes are > 0;
     so all EXODUS fatal error codes are > 0    */
  if ((err_num > 0) && (exoptval & EX_ABORT)) {
    exit(err_num);
  }
  EX_FUNC_VOID();
}

/*!
  \ingroup Utilities
  \undoc
*/
void ex_set_err(const char *module_name, const char *message, int err_num)
{
  EX_FUNC_ENTER_INT();
  /* save the error message for replays */
  ex_copy_string(EX_ERRMSG, message, MAX_ERR_LENGTH + 1);
  ex_copy_string(EX_PNAME, module_name, MAX_ERR_LENGTH + 1);
  if (err_num != EX_LASTERR) {
    /* Use last set error number, but add new function and message */
    EX_ERR_NUM = err_num;
  }
  EX_FUNC_VOID();
}

/*!
  \ingroup Utilities
  \undoc
*/
void ex_get_err(const char **msg, const char **func, int *err_num)
{
  EX_FUNC_ENTER_INT();
  if (msg) {
    (*msg) = EX_ERRMSG;
  }

  if (func) {
    (*func) = EX_PNAME;
  }

  if (err_num) {
    (*err_num) = EX_ERR_NUM;
  }
  EX_FUNC_VOID();
}

/*!
  \ingroup Utilities
  \undoc
  Returns a pointer to a string which gives a text description of the error code err_num.
  If the error code refers to a NetCDF error, then that string is returned.

~~~{.c}
    std::ostringstream errmsg;
    \comment{Create errmsg here so that the exerrval doesn't get cleared by}
    \comment{the ex_close call.}
    int status;
    ex_get_err(nullptr, nullptr, &status);
    fmt::print(errmsg, "Exodus error ({}) {} at line {} of file '{}' in function '{}'.", status,
               ex_strerror(status), lineno, filename, function);
~~~
*/
const char *ex_strerror(int err_num)
{
  switch (err_num) {
  case EX_MEMFAIL: return "Memory allocation failure";
  case EX_BADFILEMODE: return "Bad file mode -- cannot specify both EX_READ and EX_WRITE";
  case EX_BADFILEID: return "Bad file id. Could not find exodus file associated with file id.";
  case EX_WRONGFILETYPE:
    return "File does not exist or is not of a supported type (netcdf3, netcdf4, netcdf5).";
  case EX_LOOKUPFAIL:
    return "Id lookup failed for specified entity type. Could not find entity with specified id.";
  case EX_BADFILENAME: return "Empty or null filename specified.";
  case EX_DUPLICATEID: return "Duplicate entity id found.";
  case EX_DUPLICATEOPEN: return "File is open multiple times for both read and write.";
  case EX_BADPARAM: return "Bad parameter.";
  case -EX_BADPARAM: return "Bad parameter.";
  case EX_INTERNAL: return "Internal logic error in exodus library.";
  case EX_NOTROOTID: return "File id is not the root id; it is a subgroup id.";
  case EX_NULLENTITY: return "Null entity found.";
  case EX_NOTFOUND: return "Could not find requested variable on database.";
  case EX_INTSIZEMISMATCH: return "Integer sizes must match for input and output file in ex_copy.";
  case EX_MSG: return "Message printed; no error implied.";
  default: return nc_strerror(err_num);
  }
}
