/**
 * @file error.h
 * @brief Declares the public error API.
 */

#ifndef _AKGL_ERROR_H_
#define _AKGL_ERROR_H_

#include <akerror.h>

/*
 * libakerror 1.0.0 is the floor. That release moved the status-name table into a
 * private registry -- AKERR_MAX_ERR_VALUE and __AKERR_ERROR_NAMES are gone, the
 * registry entry points raise akerr_ErrorContext * instead of returning int, and
 * the library gained an soname -- so a translation unit that pairs this header
 * with a pre-1.0.0 akerror.h is an ABI mismatch, not just a compile problem.
 *
 * libakerror publishes no version macro, so this feature-tests on
 * AKERR_FIRST_CONSUMER_STATUS, which that release introduced, rather than on a
 * version number that does not exist. Without the guard an embedded build is
 * fine but a stale installed header fails much further in, on the AKGL_ERR_*
 * codes below and again inside src/heap.c.
 *
 * See deps/libakerror/UPGRADING.md.
 */
#ifndef AKERR_FIRST_CONSUMER_STATUS
#error "libakgl requires libakerror >= 2.0.1: the akerror.h on the include path predates the status registry. Rebuild and reinstall libakerror."
#endif
// 2.0.0 is an ABI break -- akerr_next_error() returns a context that already
// holds its reference, and __akerr_last_ignored is thread-local -- and both of
// those expand at *this* library's call sites through IGNORE and the FAIL
// macros. A libakgl built against a 1.x header and linked against 2.x
// double-counts every reference and never returns a slot to the pool. The
// soname moved to libakerror.so.2 so the two cannot be mixed by accident, but
// the header can still be stale in an install tree, so check it here too.
//
// AKERR_EXIT_STATUS_UNREPRESENTABLE arrived in 2.0.1 and is the narrowest thing
// to probe for: libakerror publishes no version macro.
#ifndef AKERR_EXIT_STATUS_UNREPRESENTABLE
#error "libakgl requires libakerror >= 2.0.1: the akerror.h on the include path predates akerr_exit(). Rebuild and reinstall libakerror."
#endif

// Silences -Wformat-truncation on a string concatenation that genuinely may not
// fit -- e.g. joining two PATH_MAX strings into one AKGL_MAX_STRING_LENGTH
// buffer. The line has to be drawn somewhere or the buffers grow forever to
// keep the compiler happy.
//
// **Nothing in libakgl uses these any more.** Both sites -- the path join in
// akgl_path_relative and the tileset image join in
// akgl_tilemap_load_layer_image -- now go through aksl_snprintf, which raises
// AKERR_OUTOFBOUNDS on truncation instead. That is the better answer: the
// compiler was right both times, and silencing it left the truncation
// happening and unreported. Kept because they are public and a consumer may
// have them; see issue #20.
#define DISABLE_GCC_WARNING_FORMAT_TRUNCATION			\
    _Pragma("GCC diagnostic push")				\
    _Pragma("GCC diagnostic ignored \"-Wformat-truncation\"")

#define RESTORE_GCC_WARNINGS			\
    _Pragma("GCC diagnostic pop")

// libakerror reserves statuses 0-255 for the host's errno values and its own
// AKERR_* codes; consumers allocate from AKERR_FIRST_CONSUMER_STATUS upward.
// These are fixed offsets from that base rather than from AKERR_LAST_ERRNO_VALUE
// so that a libc which grows an errno cannot move them out from under us.
//
// akgl_error_init() reserves this whole band in one call and registers a name
// for every code below. Add a code here and you must name it there, or it
// prints as "Unknown Error" in every stack trace that carries it.
#define AKGL_ERR_OWNER             "libakgl"
#define AKGL_ERR_BASE              AKERR_FIRST_CONSUMER_STATUS

#define AKGL_ERR_SDL               (AKGL_ERR_BASE + 0)   /**< An SDL call failed; the message carries SDL_GetError() */
#define AKGL_ERR_REGISTRY          (AKGL_ERR_BASE + 1)   /**< A registry property or lookup operation failed */
#define AKGL_ERR_HEAP              (AKGL_ERR_BASE + 2)   /**< A heap pool has no free object left to hand out */
#define AKGL_ERR_BEHAVIOR          (AKGL_ERR_BASE + 3)   /**< A component did not behave the way its contract requires */
#define AKGL_ERR_LOGICINTERRUPT    (AKGL_ERR_BASE + 4)   /**< Actor logic is telling the physics simulator to skip it */
#define AKGL_ERR_COLLISION         (AKGL_ERR_BASE + 5)   /**< A collision query could not be answered; the message says why */
#define AKGL_ERR_UI                (AKGL_ERR_BASE + 6)   /**< The UI subsystem refused, or clay reported a layout error; the message says which */

// One past the last libakgl status. The reservation is all-or-nothing -- a
// subset or superset of an existing one is refused -- so this must stay one
// past the highest code above.
#define AKGL_ERR_LIMIT             (AKGL_ERR_BASE + 7)
#define AKGL_ERR_COUNT             (AKGL_ERR_LIMIT - AKGL_ERR_BASE)

/**
 * @brief Claim the libakgl status band and register a name for every code in it.
 *
 * Call this before anything else in libakgl. Every other subsystem raises
 * AKGL_ERR_* codes, and a code raised before this runs carries no name into its
 * stack trace. Repeating the call is a no-op, so a program that cannot order its
 * initialization precisely may call it more than once.
 *
 * @throws AKERR_STATUS_RANGE_OVERLAP When another component already owns part of the band.
 * @throws AKERR_STATUS_RANGE_FULL When libakerror has no reservation slots left.
 * @throws AKERR_STATUS_NAME_FULL When libakerror's name registry is full.
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_error_init(void);

#endif // _AKGL_ERROR_H_
