// Chemfiles, a modern library for chemistry file reading and writing
// Copyright (C) Guillaume Fraux and contributors -- BSD license

#ifndef CHEMFILES_CAPI_UTILS_H
#define CHEMFILES_CAPI_UTILS_H

#include <cstdint>
#include <string>
#include <exception>

#include <fmt/format.h>

#include "chemfiles/capi/types.h"
#include "chemfiles/warnings.hpp"
#include "chemfiles/types.hpp"

#include "chemfiles/Error.hpp"


namespace chemfiles {

void set_last_error(const std::string& message);

inline size_t checked_cast(uint64_t value) {
    // Cast SIZE_MAX to uint64_t to be able to do the comparaison, even on
    // 32-bits systems
    if (value > static_cast<uint64_t>(SIZE_MAX)) {
        throw chemfiles::Error("got a value too big to be represented by a size_t on this system");
    }
    return static_cast<size_t>(value);
}

/// Convert a C chfl_vector3d to a C++ Vector3D
inline Vector3D vector3d(const chfl_vector3d vector) {
    return {vector[0], vector[1], vector[2]};
}

#define CATCH_AND_RETURN(_exception_, _retval_)                                \
    catch (const chemfiles::_exception_& e) {                                  \
        set_last_error(e.what());                                              \
        chemfiles::send_warning(e.what());                                     \
        return _retval_;                                                       \
    }

#define CHECK_POINTER(_ptr_)                                                   \
    do {                                                                       \
        if ((_ptr_) == nullptr) {                                              \
            std::string message = fmt::format(                                 \
                "parameter '{}' cannot be NULL in {}", #_ptr_, __func__        \
            );                                                                 \
            set_last_error(message);                                           \
            chemfiles::send_warning(message.c_str());                          \
            return CHFL_MEMORY_ERROR;                                          \
        }                                                                      \
    } while (false)

#define CHECK_POINTER_GOTO(_ptr_)                                              \
    do {                                                                       \
        if ((_ptr_) == nullptr) {                                              \
            std::string message = fmt::format(                                 \
                "parameter '{}' cannot be NULL in {}", #_ptr_, __func__        \
            );                                                                 \
            set_last_error(message);                                           \
            chemfiles::send_warning(message.c_str());                          \
            goto error;  /* NOLINT: goto is OK here */                         \
        }                                                                      \
    } while (false)


/// Wrap `instructions` in a try/catch bloc automatically, and return a status
/// code
#define CHFL_ERROR_CATCH(_instructions_)                                       \
    try {                                                                      \
        _instructions_                                                         \
    }                                                                          \
    CATCH_AND_RETURN(FileError, CHFL_FILE_ERROR)                               \
    CATCH_AND_RETURN(MemoryError, CHFL_MEMORY_ERROR)                           \
    CATCH_AND_RETURN(FormatError, CHFL_FORMAT_ERROR)                           \
    CATCH_AND_RETURN(SelectionError, CHFL_SELECTION_ERROR)                     \
    CATCH_AND_RETURN(ConfigurationError, CHFL_CONFIGURATION_ERROR)             \
    CATCH_AND_RETURN(OutOfBounds, CHFL_OUT_OF_BOUNDS)                          \
    CATCH_AND_RETURN(PropertyError, CHFL_PROPERTY_ERROR)                       \
    CATCH_AND_RETURN(Error, CHFL_GENERIC_ERROR)                                \
    catch (const std::exception& e) {                                          \
        set_last_error(e.what());                                              \
        return CHFL_CXX_ERROR;                                                 \
    } catch (...) {                                                            \
        set_last_error("UNKNOWN ERROR");                                       \
        return CHFL_CXX_ERROR;                                                 \
    }                                                                          \
    return CHFL_SUCCESS;

/// Wrap `instructions` in a try/catch bloc automatically, and goto the
/// `error` label in case of error.
#define CHFL_ERROR_GOTO(_instructions_)                                        \
    try {                                                                      \
        _instructions_                                                         \
    } catch (const chemfiles::Error& e) {                                      \
        set_last_error(e.what());                                              \
        chemfiles::send_warning(e.what());                                     \
        goto error;  /* NOLINT: goto is OK here */                             \
    } catch (const std::exception& e) {                                        \
        set_last_error(e.what());                                              \
        goto error;  /* NOLINT: goto is OK here */                             \
    } catch (...) {                                                            \
        set_last_error("UNKNOWN ERROR");                                       \
        goto error;  /* NOLINT: goto is OK here */                             \
    }


} // namespace chemfiles

#endif
