/*
 * This file is part of the ESO Pipelines
 * Copyright (C) 2016 European Southern Observatory
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 *  Created on: Jul 17, 2020
 *      Author: lluvaul
 */

#ifndef SBRM_H_
#define SBRM_H_

/*----------------------------------------------------------------------------*
 * This module implements, in C, a restricted form of the RAII (Resource Acqui-
 * sition Is Initialisation) concept from C++, although a better name for it is
 * SBRM (Scope Bound Resource Management).  Unlike true RAII/SBRM, C can't tie
 * dynamically-allocated object lifetimes to arbitrary scopes/levels, so instead
 * this module limits itself to just the outermost scope enclosed by a function.
 * Despite this limitation, it is a more readable alternative than the more gen-
 * erally recommended technique for combining resource management with error
 * handling in C, which is to use a "goto chain" (refer to MEM12-C from Carnegie
 * Mellon's SEI CERT C Coding Standard)
 *
 * There are two APIs offered, a pure C API and a Macro API.  Both APIs provide
 * the same functionality but the Macro API is both less verbose and safer to
 * use than the C API.  The Macro API is a type-safe wrapper around the C API.
 * The type-safety of the Macro API is expressed through "incompatible pointer
 * types" compiler warnings, hereby referred to as IPT warnings.  It is there-
 * fore recommended to compile with -Werror=incompatible-pointer-types (or your
 * compiler's equivalent) to turn this warning into an error because, if you get
 * one, you have an unsafe operation somewhere.  When using the Macro API,
 * strive for zero compiler warnings/errors.  C API examples below marked with
 * ~> are not type-safe.
 *
 * [This implementation was only designed to manage pointers to resources, not
 * non-pointer resources (the SBRM_SET macros, for example, will only work with
 * resource pointers).  You may try to use the C API directly to force SBRM to
 * manage non-pointer resources but, although it may work on your chosen compil-
 * er/OS combo, it may not be portable... caveat emptor.]
 *----------------------------------------------------------------------------*/

/*-----------------------------------------------------------------------------
                                   Includes
 -----------------------------------------------------------------------------*/

#include <cpl.h>
#include <string.h>  // for memcpy (on a Mac, memcpy is a macro so enclose any
                     // args that may use __VA_ARGS__ in parens to avoid a 'Too
                     // many arguments' error, which can happen when __VA_ARGS__
                     // contains commas)

CPL_BEGIN_DECLS

/*-----------------------------------------------------------------------------
                                Private Defines
 -----------------------------------------------------------------------------*/

/*----------------------------------------------------------------------------*
 * private helper macros (not to be used outside this file)
 *----------------------------------------------------------------------------*/
#define SBRM_PRIV_HERE_SIG const char * func, const char * file, const int line
#define SBRM_PRIV_ATOM(statements) do { statements; } while (0)
#define SBRM_PRIV_NARGS___(_5, _4, _3, _2, _1, N, ...) N
#define SBRM_PRIV_NARGS__(...) SBRM_PRIV_NARGS___(__VA_ARGS__, 5, 4, 3, 2, 1, 0)
#define SBRM_PRIV_NARGS_0(...) SBRM_PRIV_NARGS__(__VA_ARGS__)
#define SBRM_PRIV_NARGS_1(...) 0
#define SBRM_PRIV_NARGS(...)                                                   \
    SBRM_PRIV_CAT(SBRM_PRIV_NARGS_, SBRM_PRIV_ISEMPTY(__VA_ARGS__))(__VA_ARGS__)
#define SBRM_PRIV_CAT_(x, y) x ## y
#define SBRM_PRIV_CAT(x, y) SBRM_PRIV_CAT_(x, y)
#define SBRM_PRIV_CASSERT(cond, msg)  /* compile-time assertion */             \
    typedef char STATIC_ASSERT__##msg[2 * !!(cond) - 1]
#define SBRM_PRIV_DECONSTIFY(name) ((__sbrm_deconst_t*)&name)->mut
// credit for the following goes to Jens Gustedt (see:
// https://gustedt.wordpress.com/2010/06/08/detect-empty-macro-arguments/)
#define SBRM_PRIV_ARG16(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
    _13, _14, _15, ...) _15
#define SBRM_PRIV_HAS_COMMA(...) SBRM_PRIV_ARG16(__VA_ARGS__, 1, 1, 1, 1, 1, 1,\
    1, 1, 1, 1, 1, 1, 1, 1, 0)
#define SBRM_PRIV_TRIGGER_PAREN(...) ,
#define SBRM_PRIV_EMPTY_CASE_0001 ,
#define SBRM_PRIV_PASTE5(_0, _1, _2, _3, _4) _0 ## _1 ## _2 ## _3 ## _4
#define SBRM_PRIV_ISEMPTY_(_0, _1, _2, _3) SBRM_PRIV_HAS_COMMA(SBRM_PRIV_PASTE5\
    (SBRM_PRIV_EMPTY_CASE_, _0, _1, _2, _3))
#define SBRM_PRIV_ISEMPTY(...) SBRM_PRIV_ISEMPTY_(                             \
    SBRM_PRIV_HAS_COMMA(__VA_ARGS__),                                          \
    SBRM_PRIV_HAS_COMMA(SBRM_PRIV_TRIGGER_PAREN __VA_ARGS__),                  \
    SBRM_PRIV_HAS_COMMA(__VA_ARGS__ (/*empty*/)),                              \
    SBRM_PRIV_HAS_COMMA(SBRM_PRIV_TRIGGER_PAREN __VA_ARGS__ (/*empty*/)))

/*-----------------------------------------------------------------------------
                                Public Defines
 -----------------------------------------------------------------------------*/

/*----------------------------------------------------------------------------*
 * REGISTRY_VARNAME: defines a custom registry name; useful when using the C API
 * in conjunction with the _ macro (if left undefined, and you wish to use _,
 * you must name your registry __sbrm_registry).
 *----------------------------------------------------------------------------*/
#if defined(SBRM_REGISTRY_VARNAME)
#    define SBRM_PRIV_REGISTRY SBRM_REGISTRY_VARNAME
#else
#    define SBRM_PRIV_REGISTRY __sbrm_registry
#endif

/*----------------------------------------------------------------------------*
 * HERE: convenience macro expanding to usual trio of location params
 *----------------------------------------------------------------------------*/
#define SBRM_HERE cpl_func, __FILE__, __LINE__

/*----------------------------------------------------------------------------*
 * HANDLE_{POINTR,STRUCT}: these can help make the C API easier to read; use
 * them as you would typedefs.  Variables of these types represent typed handles
 * to the managed objects and should be initialised with the return value from
 * the C API routine ->set().  HANDLE_STRUCT expands to a type that is a ptr to
 * a registry slot managing an object of type @type (the slot's first member .o
 * points to this object and thus has type @type*).  HANDLE_POINTR expands to a
 * type that is a ptr to the first member of a slot, which has type @type*.  The
 * two are the same because the C standard guarantees that the address of a
 * struct is identical to the address of its first member.  It only makes a
 * difference in how the user accesses the managed object: 'name->o' using
 * HANDLE_STRUCT, and '*name' using HANDLE_POINTR.  The macro API exclusively
 * uses HANDLE_STRUCT as the ->o makes it clear that the user is dealing with a
 * managed object.
 *
 * Hybrid* API (*so-called because use of these macros means it's not pure C):
 *     #define HANDLE SBRM_HANDLE_STRUCT  // shortened for brevity
 *     HANDLE(type) * name = reg->set(reg, fp, dtor, 0, 0); name->o = initval;
 *  ~> HANDLE(type) * name = reg->set(reg, fp, dtor, initval, 0);
 *  ~> HANDLE(type) * const cname = reg->set(reg, fp, dtor, initval, 0);
 *     cname = name;  // won't compile (assignment of read-only variable)
 *----------------------------------------------------------------------------*/
#define SBRM_HANDLE_POINTR(type) type *
#define SBRM_HANDLE_STRUCT(type) struct { type * o; }

/*----------------------------------------------------------------------------*
 * INITV/RESET: convenience macros providing aliases for overloaded method modes
 * (see INIT & SET, respectively for more details)
 *----------------------------------------------------------------------------*/
#define SBRM_INITV(size, name, ...) SBRM_INIT(size, name, ##__VA_ARGS__)
#define SBRM_RESET(name) SBRM_SET(name)

/*----------------------------------------------------------------------------*
 * INIT: sets up the object registry and optionally designates a return object.
 * Call it near the beginning of your function, in the outermost scope.  Ensure
 * @size is bigger than the combined number of calls to [C]SET that follow (0 is
 * fine if there are no such calls: initialising a zero-sized registry is often
 * done just to gain access to the _ macro (described below)).
 *
 * Any arguments supplied after @size are passed verbatim to SET* to declare/in-
 * itialise a managed object which is then designated the return object by in-
 * voking RVAL on it.  Usage of this object within the function is exactly the
 * same as for any other object defined via SET except that _ returns (a pointer
 * to) it and ABORT/CLEANUP both evaluate to the same... that is unless RVAL
 * nominates a different object before ABORT, CLEANUP, or _ are encountered.
 * (See below for detailed descriptions of SET, RVAL, ABORT, CLEANUP, and _.) If
 * your function's return type doesn't match the designated object type, the
 * Macro API will generate an IPT warning but the C API will *not* warn you, so
 * take extra care to ensure that they match.
 *
 * If INIT is _not_ used to define a return object, _ will instead return NULL
 * and ABORT/CLEANUP will evaluate to the same (again assuming RVAL is not later
 * used to nominate some other object) in which case it is recommended that your
 * function's return type be void*.
 *
 * Note that, when using SBRM, if you exit the function without returning ABORT
 * or CLEANUP or invoking _, you risk a memory leak as only these routines are
 * gauranteed to invoke registry deallocation.
 *
 * (*INIT will generate a non-IPT warning [-Wunused-value] when @defval is miss-
 * ing from the INITV form [described below].  Refer to SET to understand why.)
 *
 * Macro API:
 *     #define INIT  SBRM_INIT   // shortened for brevity
 *     #define INITV SBRM_INITV  // shortened for brevity
 *     INIT(size);                                      // non-INITV mode
 *     INIT(size, name, type [,fp [,dtor]]) = defval;   // INITV mode
 *     INITV(size, name, type [,fp [,dtor]]) = defval;  // same as above
 *     INITV(size);  // won't compile: INIT aliases INITV, not vice versa
 *
 * C API:
 *     #define HERE cpl_func, __FILE__, __LINE__  // or use SBRM_HERE
 *     sbrm_registry * reg = sbrm_init(size, HERE);  // mandatory
 *     // the following optional lines define an alternate return value/object
 *     // other than the default (NULL)
 *  ~> struct { type * o; } * name = reg->set(reg, fp, dtor, defval, 0);
 *     reg->rval(reg, name);
 *
 *
 * Methods A & B below show two different approaches to working with SBRM-
 * enhanced functions.  Assume for each that the SBRM-enhanced function looks
 * like the following:
 *
 *     int * sbrm_enhanced_func(...) {
 *         SBRM_INIT(5, rv, int, v, cpl_free) = ALLOC(int, -1);
 *         if (cond) SBRM_RESET(rv) = ALLOC(int, 42);
 *         return SBRM_CLEANUP();  // pointer to -1 returned if cond is false
 *     }
 *
 * A)  if the API demands a non-pointer return value, write a short wrapper
 *     function calling the SBRM-enhanced function to catch & deref the returned
 *     object, free the space holding it, and return its value (the LOCAL macro
 *     does all this for you):
 *
 *     int func_wrapper(...) {
 *         return *SBRM_LOCAL(int, sbrm_enhanced_func(...));
 *     }
 *     whatever_t caller(...) {
 *         const int val = func_wrapper(...);  // val is -1 or 42
 *         return my_whatever;
 *     }
 *
 * B)  or you could enhance the calling routine with SBRM and use it to auto-
 *     free the allocated return value itself (which you do by declaring a man-
 *     aged ptr to the returned object which you then use/reuse throughout the
 *     caller):
 *
 *     whatever * sbrm_enhanced_caller(...) {
 *         SBRM_INIT(5, rv, whatever) = NULL;  // auto-uses dtor whatever_delete
 *         SBRM_SET(val, int, v, cpl_free) = sbrm_enhanced_func(...);
 *         // access via val->o, which now points to -1 or 42
 *         const int tmp = *val->o + 1;  // tmp is 0 or 43
 *         return SBRM_CLEANUP();  // val->o auto-freed & rv->o returned
 *     }
 *----------------------------------------------------------------------------*/
#define SBRM_PRIV_INIT4(size, name, type, fp, dtor) SBRM_PRIV_INIT0(size + 1); \
    SBRM_SET(name, type, fp, dtor) = NULL; SBRM_RVAL(name); name->o
#define SBRM_PRIV_INIT3(size, name, type, fp, ...)                             \
    SBRM_PRIV_INIT4(size, name, type, fp, SBRM_PRIV_CAT(type, _delete))
#define SBRM_PRIV_INIT2(size, name, type, ...)                                 \
    SBRM_PRIV_INIT4(size, name, type, v, SBRM_PRIV_CAT(type, _delete))
#define SBRM_PRIV_INIT1(size, name, ...)                                       \
    SBRM_PRIV_CASSERT(0, Wrong_number_of_arguments_given_to_SBRM_INIT)
#define SBRM_PRIV_INIT0(size, ...)                                             \
    sbrm_registry * SBRM_PRIV_REGISTRY = sbrm_init(size, SBRM_HERE)
#define SBRM_INIT(size, ...) SBRM_PRIV_CAT(SBRM_PRIV_INIT,                     \
    SBRM_PRIV_NARGS(__VA_ARGS__)) (size, ##__VA_ARGS__)

/*----------------------------------------------------------------------------*
 * [C]SET, "set" mode (2 or more args): passes (a ptr to) an object (@initval)
 * and its destructor (@fp+@dtor) to the management infrastructure, to be stored
 * in the slot @ident, whose content (if any) is first freed.  If slot @ident is
 * 0 or not found, @initval, @fp, @dtor, and @ident are stored in the next unoc-
 * cupied slot, now known as @ident (unless @ident is 0 in which case the slot
 * is unidentifiable).  Once this is all done a handle, @name, will be declared
 * locally and initialised with a ptr to the slot, whose .o member is of type
 * @type* and points to @initval (if @initval is not of type @type, the C API
 * will *not* warn you, whereas the Macro API will generate an IPT warning).
 * @name is the typed handle to the registry-managed object, accessed thusly:
 * @name->o
 *
 * The object is now managed by SBRM and will be destroyed by calling @dtor on
 * it whenever _, ABORT, or CLEANUP are invoked: _ calls ABORT which in turn
 * calls CLEANUP, but of course you may invoke ABORT or CLEANUP directly.  The
 * CSET variant will declare the managed object as const.  All instances of
 * [C]SET within a function must be preceded with a call to INIT.
 *
 * Unless you provide them, the Macro API will automatically try to set @fp to
 * 'v', @dtor to <@type>_delete (or to <@type>_unwrap if you set @fp to 'p'),
 * and @ident to __LINE__.  A missing @initval is a bit more complicated: the C
 * API takes @initval as an argument, but the Macro API expects it to be given
 * as a trailing assignment.  If this assignment is missing, the Macro API gen-
 * erates a non-IPT warning (-Wunused-value) yet still proceeds to use NULL in
 * place of the missing @initval.  You may silence the warning with a trailing,
 * though redundant, assignment to NULL.
 *
 * The @fp argument, a single char, describes @dtor's signature thus informing
 * SBRM how it should be invoked.  'v' means @dtor returns nothing, 'i' means it
 * returns an int, 'f' a float, 'd' a double, and 'p' a pointer to some object.
 * These also indicate that @dtor takes a pointer to the managed object as its
 * argument.  For all but 'v', SBRM discards the return value by prefixing the
 * @dtor call with (void).  Yet another value, 'w', indicates that @dtor takes
 * a pointer^ to the object pointer and returns nothing (^this is usually done
 * to set the pointer to NULL after releasing the memory... something SBRM al-
 * ready does for *all* object pointers).  The 'w' value allows SBRM to support
 * this style of destructor even though it means the object pointer is set to
 * NULL twice, once by @dtor and again by SBRM immediately following the @dtor
 * call.
 *
 * The @ident argument is handy for declaring handles within loops: give a uni-
 * que number (or __LINE__ as the Macro API does) and, in the first pass thru
 * the loop, an empty slot gets filled (with @initval) and tagged with @ident;
 * then, in subsequent passes, @ident is used to lookup & assign the same slot
 * back to the handle, after first freeing the previous pass's @initval.  The
 * slot (through the handle) is then used to store the current pass's @initval.
 *
 * [C]SET, "reset" mode (1 arg): calls FREE on @name (frees & NULL's @name->o)
 * then sets it to @newval (if provided) thus changing the object (but _not_ the
 * type) managed by the slot that @name points to.  If @newval isn't given, both
 * APIs will generate a non-IPT warning (-Wunused-value).  Additionally, the Ma-
 * cro API will generate an IPT warning if @newval is not of type @type*, but
 * the C API will *not* warn you.  The C API offers two variants, ->reset1() and
 * ->reset2(), depending on the style of access you prefer.
 *
 * [A word of caution about using _ with @newval in "reset" mode: keep in mind
 * that, because the left hand side of the assignment always runs first,
 * @name->o is always freed regardless of what happens on the right-hand side.
 * If _ detects a CPL error on the right, whatever value results from the failed
 * routine (usually NULL) is very briefly assigned to @name->o before being re-
 * turned.  This is important to understand when @name is the designated return
 * value (see RVAL): in this case, it is tempting to think that whatever is in
 * @name->o _before_ the right-side executes will be returned by _ if there is
 * an error (after all, this is what happens in all other cases); however, it is
 * _not_ true.  (I thought of issuing an informative warning when SET is used on
 * the designated RVAL, but it would be unwarranted unless the _ macro is also
 * in use, as it's only then that the situation is deceptive.)]
 *
 *
 * Macro API ("set" mode: 2+ args):
 *     #define SET SBRM_SET  // shortened for brevity
 *     //SET(name, type [,fp [,dtor]]);         // sets to NULL, but warns(non-IPT)
 *     SET(name, type [,fp [,dtor]]) = NULL;    // same as above, no warning
 *     SET(name, type [,fp [,dtor]]) = initval; // warns(IPT) iff initval wrong type
 *     SET(name, type [,fp [,dtor]]) = func _(...);  // _ in initval is ok
 *     SBRM_CSET(cname, type [,fp [,dtor]]) = initval;
 *
 *     // a handy trick is to define your type's dtor in the .h file like this:
 *     #define foolist_delete cpl_free                  // then the dtor is...
 *     SET(listA, foolist) = VALLOC(foolist, foo, 17);  // ...inferred, always
 *     SET(myfooz, foolist) = VALLOC(foolist, foo, 4);  // ...and everywhere
 *
 * 1st C API (clearly indicates managed object, but visually ugly):
 *     struct { type * o; } * name = reg->set(reg, fp, dtor, 0, 0); name->o = initval;
 *  ~> struct { type * o; } * name = reg->set(reg, fp, dtor, initval, 0);
 *     struct { type * o; } * name = reg->set(reg, fp, dtor, 0, ident);
 *
 * 2nd C API (terse, but error prone):
 *     type ** name = reg->set(reg, fp, dtor, 0, 0); *name = initval;
 *  ~> type ** name = reg->set(reg, fp, dtor, initval, 0);
 *     type ** name = reg->set(reg, fp, dtor, 0, ident);
 *
 * Hybrid API (same advantage as 1st C API, but minor macro hides ugliness):
 *     #define HANDLE SBRM_HANDLE_STRUCT  // shortened for brevity
 *     HANDLE(type) * name = reg->set(reg, fp, dtor, 0, 0); name->o = initval;
 *  ~> HANDLE(type) * name = reg->set(reg, fp, dtor, initval, 0);
 *     HANDLE(type) * name = reg->set(reg, fp, dtor, 0, ident);
 *
 *
 * Macro API ("reset" mode: 1 arg):
 *     #define SET   SBRM_SET    // shortened for brevity
 *     #define RESET SBRM_RESET  // shortened for brevity
 *
 *     SET(name) = newval;               // RESET mode
 *     RESET(name) = newval;             // same as above
 *     RESET(name, type [,fp [,dtor]]);  // won't compile: SET aliases RESET,
 *                                       // not vice versa
 *
 *     //RESET(name);         // frees & NULLifies, but warns(non-IPT)
 *     RESET(name) = NULL;    // same as above, no warning; or use FREE(name)
 *     RESET(name) = newval;  // warns(IPT) iff newval wrong type
 *
 *     RESET(name) = func _(...);               // _ in newval is ok
 *     if (...) RESET(name) = func(...);        // no {} req'd in conds/loops...
 *     if (...) { RESET(name) = func _(...); }  // ...unless _ is present
 *
 *     cname = name;            // won't compile (CSET defines const handles)
 *     name = cname;            // won't compile (SET also defines const handles)
 *     cname->o->member = ...;  // won't compile (*o is const)
 *     cname->o = newval;       // ok (o is not const), but may leak mem
 *     RESET(cname) = newval;   // better, no mem leak
 *
 * 1st C API:
 *  ~> reg->reset1(reg, name)->o = newval;
 *     reg->free(reg, name); name->o = newval;
 *
 * 2nd C API:
 *  ~> *reg->reset2(reg, name) = newval;
 *     reg->free(reg, name); *name = newval;
 *----------------------------------------------------------------------------*/
#define SBRM_PRIV_SETv(type) SBRM_PRIV_CAT(type, _delete)
#define SBRM_PRIV_SETi(type) SBRM_PRIV_CAT(type, _delete)
#define SBRM_PRIV_SETf(type) SBRM_PRIV_CAT(type, _delete)
#define SBRM_PRIV_SETd(type) SBRM_PRIV_CAT(type, _delete)
#define SBRM_PRIV_SETp(type) SBRM_PRIV_CAT(type, _unwrap)
#define SBRM_PRIV_SETw(type) SBRM_PRIV_CAT(type, _delete)

#define SBRM_PRIV_SET3(name, type, fp, dtor)                                   \
    SBRM_HANDLE_STRUCT(type) * const name = SBRM_PRIV_REGISTRY->set(           \
    SBRM_PRIV_REGISTRY, #fp[0], dtor, NULL, __LINE__); name->o
#define SBRM_PRIV_SET2(name, type, fp, ...)                                    \
    SBRM_PRIV_SET3(name, type, fp, SBRM_PRIV_CAT(SBRM_PRIV_SET, fp)(type))
#define SBRM_PRIV_SET1(name, type, ...)                                        \
    SBRM_PRIV_SET3(name, type, v, SBRM_PRIV_CAT(SBRM_PRIV_SET, v)(type))
#define SBRM_PRIV_SET0(name, ...) *(SBRM_FREE(name), &name->o)
#define SBRM_SET(name, ...) SBRM_PRIV_CAT(SBRM_PRIV_SET,                       \
    SBRM_PRIV_NARGS(__VA_ARGS__)) (name, ##__VA_ARGS__)

#define SBRM_PRIV_CSET3(name, type, fp, dtor)                                  \
    SBRM_HANDLE_STRUCT(const type) * const name = SBRM_PRIV_REGISTRY->set(     \
    SBRM_PRIV_REGISTRY, #fp[0], dtor, NULL, __LINE__); name->o
#define SBRM_PRIV_CSET2(name, type, fp, ...)                                   \
    SBRM_PRIV_CSET3(name, type, fp, SBRM_PRIV_CAT(SBRM_PRIV_SET, fp)(type))
#define SBRM_PRIV_CSET1(name, type, ...)                                       \
    SBRM_PRIV_CSET3(name, type, v, SBRM_PRIV_CAT(SBRM_PRIV_SET, v)(type))
#define SBRM_PRIV_CSET0(name, ...) *(SBRM_FREE(name), &name->o)
#define SBRM_CSET(name, ...) SBRM_PRIV_CAT(SBRM_PRIV_CSET,                     \
    SBRM_PRIV_NARGS(__VA_ARGS__)) (name, ##__VA_ARGS__)

/*----------------------------------------------------------------------------*
 * RVAL: redefines which object is to be the new return value.  This must be one
 * of those managed by SBRM, indicated by @name, previously declared via INIT or
 * [C]SET (passing anything else will result in a runtime assertion during the
 * registry deallocation routine).  If the type of @name->o is not the same as
 * the function's declared return type, the Macro API will cause an IPT warning
 * to be generated but the C API will not generate any warning at all, so take
 * extra care to pass a handle (pointing to a registry slot that manages an obj-
 * ect) of the same type as the function's return type.
 *
 * Macro API:
 *     SBRM_RVAL(name)
 * C API:
 *  ~> reg->rval(reg, name);
 *----------------------------------------------------------------------------*/
#define SBRM_RVAL(name) SBRM_PRIV_ATOM(                                        \
    SBRM_PRIV_REGISTRY->rval(SBRM_PRIV_REGISTRY, name); if (0) return name->o; )

/*----------------------------------------------------------------------------*
 * FREE: destroys the object tracked by the registry slot pointed to by the
 * handle @name by invoking the registered destructor on @name->o (unless it is
 * NULL), then sets @name->o to NULL.
 *
 * Macro API:
 *     SBRM_FREE(name);
 *     assert(name->o == NULL);  // ptr to object is auto-NULLed
 * C API:
 *     reg->free(reg, name);
 *     assert(name->o == NULL);  // ptr to object is auto-NULLed
 *----------------------------------------------------------------------------*/
#define SBRM_FREE(name) SBRM_PRIV_REGISTRY->free(SBRM_PRIV_REGISTRY, name)

/*----------------------------------------------------------------------------*
 * CLEANUP: destroys all* objects in the registry in the reverse order that they
 * were registered by invoking their registered destructors on them (unless they
 * or their destructors are NULL), and frees all memory used by the registry it-
 * self.  It returns a pointer to an object that is meant to be returned by the
 * function.  The returned object should be freed by the function's caller.
 *
 * *By default, CLEANUP returns NULL, but there are two ways it will preserve an
 * object from the registry and return that instead:
 *     1) if a registry-managed object has been designated as the function's
 *        return value via RVAL or INIT (which internally calls RVAL), then
 *        CLEANUP will return it instead; or
 *     2) if a handle @name is passed to CLEANUP, the object (managed by the
 *        registry slot) it points to will instead be returned while any prev-
 *        iously RVAL- or INIT-designated objects will be destroyed along with
 *        the rest of the registered objects; this actually works by internally
 *        calling RVAL on @name immediately before calling the ->cleanup() rou-
 *        tine, converting this case into just another instance of case #1
 *
 * In case #2, if the type of @name->o is not the same as the function's declar-
 * ed return type, the Macro API will generate an IPT warning but the C API will
 * *not* warn you, so take extra care to pass a handle (pointing to a registry
 * slot managing an object) of the same type as the function's return type.
 *
 * Macro API:
 *     return SBRM_CLEANUP([name]);
 * C API:
 *     return reg->cleanup(reg, 0);     // free everything, return NULL
 *  ~> return reg->cleanup(reg, name);  // don't free @name->o, return it
 *----------------------------------------------------------------------------*/
#define SBRM_PRIV_CLEAN1(name) (SBRM_PRIV_REGISTRY->rv = (void*)name,          \
    SBRM_PRIV_DECONSTIFY(name) = SBRM_LCP(__sbrm_entry_t, name),               \
    SBRM_PRIV_REGISTRY->cleanup(SBRM_PRIV_REGISTRY, 0), name->o)
// this _may_ be more portable than the DECONSTIFY(...) = LCP(...) construct:
//memcpy((void*)&name, &(void*){SBRM_LCP(__sbrm_entry_t, name)}, sizeof(void*))
#define SBRM_PRIV_CLEAN0() SBRM_PRIV_REGISTRY->cleanup(SBRM_PRIV_REGISTRY, 0)
#define SBRM_CLEANUP(...)                                                      \
    SBRM_PRIV_CAT(SBRM_PRIV_CLEAN, SBRM_PRIV_NARGS(__VA_ARGS__)) (__VA_ARGS__)

/*----------------------------------------------------------------------------*
 * YANK: writes the ptr to the managed object into dest and de-registers it so
 * that the FREE, CLEANUP, ABORT, and _ commands will not free it when called.
 * The .o member of the slot it occupied (pointed to by @name) is set to NULL,
 * but the slot (and the @name handle pointing to it) can still be reused for
 * other objects of the same type.  Future work: make YANK return the slot to
 * the usable pool so it can be reused with objects of different types.
 *
 * Macro API:
 *     type * dest;
 *  ~> if (...) dest = SBRM_YANK(name);
 *     if (...) dest = SBRM_YANK(name, type);        // type-safe alternative
 * C API:
 *     type * dest;
 *  ~> if (...) dest = reg->yank(reg, name);
 *     if (...) { dest = name->o; name->o = NULL; }  // type-safe alternative
 *----------------------------------------------------------------------------*/
#define SBRM_PRIV_YANK1(name, type) (type*)SBRM_PRIV_REGISTRY->yank(name)
#define SBRM_PRIV_YANK0(name, ...) SBRM_PRIV_REGISTRY->yank(name)
#define SBRM_YANK(name, ...) SBRM_PRIV_CAT(SBRM_PRIV_YANK,                     \
    SBRM_PRIV_NARGS(__VA_ARGS__)) (name, ##__VA_ARGS__)

/*----------------------------------------------------------------------------*
 * ABORT: propagates any existing error to the ABORT call site (or sets code if
 * supplied, or UNSPECIFIED if not), prints the error stack, then invokes
 * CLEANUP and returns its result (either NULL or a pointer to the return object
 * established by INIT or RVAL) and so can and should be used with 'return'.  A
 * printf-style format string and trailing arguments may optionally accompany
 * the supplied error code.
 *
 * Macro API:
 *     if (!cpl_error_get_code()) return SBRM_ABORT();  // CPL_ERROR_UNSPECIFIED
 *     if (cpl_error_get_code())  return SBRM_ABORT();  // propagates
 *     if (something_bad)         return SBRM_ABORT(code);
 *     if (really_bad)            return SBRM_ABORT(code, "Not %s", "good");
 * C API:
 *     #define HERE cpl_func, __FILE__, __LINE__  // or use SBRM_HERE
 *     if (!cpl_error_get_code()) return reg->abort(reg, HERE, 0, 0);  // UNSPEC
 *     if (cpl_error_get_code())  return reg->abort(reg, HERE, 0, 0);  // propgt
 *     if (something_bad)         return reg->abort(reg, HERE, code, 0);
 *     if (really_bad)            return reg->abort(reg, HERE, code, "Not %s",
 *                                                   "good");
 *----------------------------------------------------------------------------*/
#define SBRM_PRIV_ABORT11(code, ...) 0, 0
#define SBRM_PRIV_ABORT10(code, ...) 0, ##__VA_ARGS__
#define SBRM_PRIV_ABORT01(code, ...) code, 0
#define SBRM_PRIV_ABORT00(code, ...) code, ##__VA_ARGS__
#define SBRM_ABORT(code, ...) SBRM_PRIV_REGISTRY->abort(SBRM_PRIV_REGISTRY,    \
    SBRM_HERE, SBRM_PRIV_CAT(SBRM_PRIV_ABORT, SBRM_PRIV_CAT(SBRM_PRIV_ISEMPTY( \
    code), SBRM_PRIV_ISEMPTY(__VA_ARGS__))) (code, ##__VA_ARGS__))

/*----------------------------------------------------------------------------*
 * DBG/INFO/WARN: propagates any existing error to the DBG/INFO/WARN call site
 * (or sets UNSPECIFIED if none), prints the error stack and any supplied addi-
 * tional details using cpl_msg_debug/info/warning, then clears the error stack.
 * Additional details are optionally given as a printf-style format string and
 * trailing arguments.  You can think of these routines as converting what would
 * otherwise be a fatal CPL error into a non-fatal debugging, informational, or
 * warning message.
 *
 * Macro API:
 *     if (!cpl_error_get_code()) SBRM_WARN();  // UNSPECIFIED, log, reset
 *     if (cpl_error_get_code()) SBRM_WARN();   // propagate, log, reset
 *     SBRM_WARN("additional %s", "details");   // UNSP|prop, log+details, reset
 * C API:
 *     #define HERE cpl_func, __FILE__, __LINE__  // or use SBRM_HERE
 *     if (!cpl_error_get_code()) reg->warn(reg, HERE, 0);
 *     if (cpl_error_get_code()) reg->warn(reg, HERE, 0);
 *     if (...) reg->warn(reg, HERE, "additional %s", "details");
 *
 * See Also: the X macro
 *----------------------------------------------------------------------------*/
#define SBRM_PRIV_DBG0(...) __VA_ARGS__
#define SBRM_PRIV_DBG1(...) 0
#define SBRM_DBG(...) SBRM_PRIV_REGISTRY->debug(SBRM_PRIV_REGISTRY, SBRM_HERE, \
    SBRM_PRIV_CAT(SBRM_PRIV_DBG, SBRM_PRIV_ISEMPTY(__VA_ARGS__)) (__VA_ARGS__))

#define SBRM_PRIV_INFO0(...) __VA_ARGS__
#define SBRM_PRIV_INFO1(...) 0
#define SBRM_INFO(...) SBRM_PRIV_REGISTRY->info(SBRM_PRIV_REGISTRY, SBRM_HERE, \
    SBRM_PRIV_CAT(SBRM_PRIV_INFO, SBRM_PRIV_ISEMPTY(__VA_ARGS__)) (__VA_ARGS__))

#define SBRM_PRIV_WARN0(...) __VA_ARGS__
#define SBRM_PRIV_WARN1(...) 0
#define SBRM_WARN(...) SBRM_PRIV_REGISTRY->warn(SBRM_PRIV_REGISTRY, SBRM_HERE, \
    SBRM_PRIV_CAT(SBRM_PRIV_WARN, SBRM_PRIV_ISEMPTY(__VA_ARGS__)) (__VA_ARGS__))

/*----------------------------------------------------------------------------*
 * _: this macro is syntactic suger to discretely inject error checking and re-
 * source handling in a way that doesn't visually clutter the program or detract
 * from the meaning of the code.  It simply adds a CPL error check after any
 * function it's used on and, if there is one, invokes ABORT and returns its re-
 * sult (either NULL or a ptr to the object established by INIT or RVAL).  ABORT
 * of course frees all memory buffers pointed to by the registered objects so no
 * memory is leaked.  _ is disabled by default so you must add
 * '#define SBRM_UNDERSCORE_MACRO' before '#include "sbrm.h"' if you want to use
 * it.
 *
 * An easy way to disable it after adding it throughout your code is to simply
 * add '#define _' after '#include "sbrm.h"'.  This is usually sufficient, but
 * you may need to put '#define _(...) (__VA_ARGS__)' after '#include "sbrm.h"'
 * instead, especially if your code uses '_' elsewhere (as a variable name per-
 * haps).
 *
 * X: this macro operates similarly to _ except that it simply logs and clears
 * the error via a call to WARN, allowing execution to continue.
 *
 * Macro API:
 *     The _ & X macros only work in two simple forms:
 *         1) var = function _(...);
 *         2) function _(...);
 *
 *     For example, functionA(functionB _(...)); won't compile unless you decom-
 *     pose it into the above forms: ret = functionB _(...); functionA(ret);
 *
 *     The above forms must be enclosed in {} if used as the only statement in a
 *     conditional or loop block:
 *         - if (test) var = function(); -> if (test) { var = function _(); }
 *         - while (test) function(...); -> while (test) { function _(...); }
 *
 *     Explicitly call ABORT for anything more complex than the above.
 *
 * C API:
 *     #define HERE cpl_func, __FILE__, __LINE__  // or use SBRM_HERE
 *
 *     // the following is equivalent to: [var =] function _(...);
 *     [var =] function(...);
 *     if (cpl_error_get_code()) return reg->abort(reg, HERE-1, 0, 0);
 *
 *     // the following is equivalent to: [var =] function X(...);
 *     [var =] function(...);
 *     if (cpl_error_get_code()) reg->warn(reg, HERE-1, 0);
 *----------------------------------------------------------------------------*/
#if defined(_)
#    if defined(SBRM_UNDERSCORE_MACRO)
#        error "Sorry, SBRM's _ macro cannot be used: the identifier is taken"
#    endif
#else
#    if defined(SBRM_UNDERSCORE_MACRO)
#        define _(...) (__VA_ARGS__);/*detect, log, & return*/                 \
             SBRM_PRIV_ATOM( if (cpl_error_get_code()) return SBRM_ABORT(); )
#        define X(...) (__VA_ARGS__);/*detect, log, & reset (for known errs)*/ \
             SBRM_PRIV_ATOM( if (cpl_error_get_code()) SBRM_WARN(); )
#    endif
#endif

/*-----------------------------------------------------------------------------
                                    Typedefs
 -----------------------------------------------------------------------------*/

typedef union { void * mut; void * const immut; } __sbrm_deconst_t;
typedef SBRM_HANDLE_STRUCT(void) * __sbrm_void_wrapper_t;
typedef struct { void * o; char fp; void * dtor; int ident; } __sbrm_entry_t;
typedef struct __sbrm_registry_s sbrm_registry;  // forward declare
struct __sbrm_registry_s {
    const int sz;  // this must be first in the struct
    int avail;
    __sbrm_entry_t * rv;
    cpl_errorstate estate;
    void * (*set)(sbrm_registry * r, char fp, void * dtor, void * initval,
        const int ident);
    __sbrm_void_wrapper_t (*reset1)(sbrm_registry * r, void * target);
    void ** (*reset2)(sbrm_registry * r, void * target);
    void (*free)(sbrm_registry * r, void * target);
    void * (*cleanup)(sbrm_registry * r, void * target);
    void (*debug)(sbrm_registry * r, SBRM_PRIV_HERE_SIG, const char * fmt, ...);
    void (*info)(sbrm_registry * r, SBRM_PRIV_HERE_SIG, const char * fmt, ...);
    void (*warn)(sbrm_registry * r, SBRM_PRIV_HERE_SIG, const char * fmt, ...);
    void * (*abort)(sbrm_registry * r, SBRM_PRIV_HERE_SIG, const unsigned code,
        const char * fmt, ...);
    void (*rval)(sbrm_registry * r, void * target);
    void * (*yank)(void * target);
    __sbrm_entry_t list[];  // this must be last in the struct
};

/*-----------------------------------------------------------------------------
                              Function Prototypes
 -----------------------------------------------------------------------------*/

void * __sbrm_cp( void * dest, void * src, size_t sz, int release );
sbrm_registry * sbrm_init(const int sz, SBRM_PRIV_HERE_SIG);

/*-----------------------------------------------------------------------------
                             Other Public Defines
 -----------------------------------------------------------------------------*/

/*-----------------------------------------------------------------------------
 * These are useful on their own and so should probably be factored out into
 * a separate file.  Some are used by SBRM.
 *----------------------------------------------------------------------------*/

/*----------------------------------------------------------------------------*
 * ALLOC: in-place dynamic allocation & initialisation of n POD types, n>=1, re-
 * turned as a ptr to a contiguous array of type @type and size @n.  @n can be
 * supplied in one of 2 ways:
 *     1 - n initial values specified after @type, or
 *     2 - inside brackets as part of @type, for example: int[n]
 *
 * Because the array is first created on the stack before being copied into dy-
 * namically allocated memory, there is a risk of overflowing the stack... so
 * only use ALLOC to create smallish items.  Initialisation values, if provided,
 * are evaluated more than once so it is strongly recommended that they be sup-
 * plied as literals or variables: expressions will have side effects (if any)
 * incurred multiple times.
 *
 * Macro API:
 *     #define ALLOC SBRM_ALLOC
 *     int * buf = ALLOC(int, 5);  // ptr to a single int, initialised to 5
 *     double * buf = ALLOC(double[50]);  // 50 doubles, uninitialised
 *     float * buf = ALLOC(float[], 2.3, 3.4, 4.5); // 3 floats, initialised
 *     char * buf = ALLOC(char, 'a', 'b');  // 1 char init'd to 'a', 'b' ignored
 *     size_t * buf = ALLOC(size_t[10], 56, 12) // 10 size_t's, partly init'd
 *     int var = 7; int * buf = ALLOC(int[var]);  // won't compile: no VLAs
 *     typedef struct { int x,y; } P;
 *         P * p = ALLOC(P, 10, 20);
 *         P * p = ALLOC(P, .y = 7);  // x defaults to 0
 *         P * p = ALLOC(P[], {10,20}, {.y=7});  // 2 initialised P's
 *----------------------------------------------------------------------------*/
#define SBRM_ALLOC(type, ...) memcpy(cpl_malloc(sizeof((type){__VA_ARGS__})),  \
    (&(type){__VA_ARGS__}), sizeof((type){__VA_ARGS__}))

/*----------------------------------------------------------------------------*
 * VLIST/VALLOC/NALLOC: these macros operate on special structs whose first mem-
 * ber is an integral type (preferrably const and unsigned) and whose last mem-
 * ber is a flexible array member (FAM) of type @scalar[].  VLIST declares the
 * simplest of such structs (here referred to as a vlist) containing only the 2
 * required members: 'sz' (a const size_t) and 'data' (an array of @scalar's).
 * VALLOC dynamically allocates & initialises vlists and more complex variants
 * with additional members between the first & last.  It sizes the last member
 * (the FAM) to hold @n @scalar's, initialises the first member to @n, and op-
 * tionally initialises members between them (if any) with arguments supplied
 * after @n (if any).  The FAM itself is not initialised except for very basic
 * initialisation provided by the use of cpl_calloc (setting all bits to zero),
 * so you should do your own array element initialisation after the VALLOC call
 * or, if using a simple vlist generated by VLIST, use NALLOC instead of VALLOC:
 * it takes the same args as VALLOC and behaves almost the same except that any
 * args after @n are used to initialise the FAM instead of the members between
 * it and @n (since there are none).  As a result, there should be at least @n
 * scalar initialisers after @n or else you'll get Undefined Behaviour.  VALLOC
 * and NALLOC both allocate the struct and the FAM as a single block of memory
 * which means a single cpl_free call is sufficient to release its memory.  FAM
 * structs provide an easy way to define "list-of" types where not already pro-
 * vided.
 *
 * Macro API:
 *     typedef SBRM_VLIST(scalar) list;  // define a list of scalar's
 *     list * ls = SBRM_VALLOC(list, scalar, n);  // instantiate the list
 *
 * Examples:
 *     #define VALLOC SBRM_VALLOC  // shortened for brevity
 *     #define VLIST  SBRM_VLIST   // shortened for brevity
 *
 *     // list of PODs & simple structs
 *     // ----------------------------------------------------------------------
 *     typedef struct { int i; float f; char * s; } foo;
 *     typedef VLIST(foo) foolist;
 *
 *     // with SBRM
 *     SBRM_SET(list1, foolist, cpl_free) = VALLOC(foolist, foo, 17);
 *     for (size_t i = 0; i < list1->o->sz; ++i)  // initialise / access
 *         list1->o->data[i] = { .f=7.1, .i=42, .s="bar" };
 *     SBRM_SET(list2, foolist, cpl_free) = NALLOC(foolist, foo, 2,
 *         { .f=7.1, .i=42, .s="bar" }, { 0, 3.3, "foo" }, { 7, 0.0, "baz" });
 *
 *     // without SBRM
 *     foolist * list1 = VALLOC(foolist, foo, 17);  // instantiate
 *     for (size_t i = 0; i < list1->sz; ++i)  // initialise / access
 *         list1->data[i] = { .f=7.1, .i=42, .s="bar" };
 *     foolist * list2 = NALLOC(foolist, foo, 1, { .f=7.1, .i=42, .s="bar" });
 *     cpl_free(list1);  // destroy
 *     cpl_free(list2);  // destroy
 *
 *     // list of pointers to dynamically allocated objects
 *     // ----------------------------------------------------------------------
 *     typedef VLIST(cpl_mask*) masklist;
 *     void masklist_delete(masklist * ls) {  // custom dtor
 *         for (size_t i = 0; i < ls->sz; ++i) cpl_mask_delete(ls->data[i]);
 *         cpl_free(ls); }
 *
 *     // with SBRM
 *     SBRM_SET(list, masklist) = VALLOC(masklist, cpl_mask*, 17);
 *     for (size_t i = 0; i < list->o->sz; ++i)  // initialise / access
 *         list->o->data[i] = cpl_mask_new(...);
 *
 *     // without SBRM
 *     masklist * list = VALLOC(masklist, cpl_mask*, 17);  // instantiate
 *     for (size_t i = 0; i < list->sz; ++i)  // initialise / access
 *         list->data[i] = cpl_mask_new(...);
 *     masklist_delete(list);  // destroy
 *----------------------------------------------------------------------------*/
#define SBRM_PRIV_VALLOC1(...)
#define SBRM_PRIV_VALLOC0(...) , ##__VA_ARGS__
#define SBRM_VALLOC(list, scalar, n, ...) memcpy(cpl_calloc(1, sizeof(list) +  \
    (n) * sizeof(scalar)), (&(list){(n) SBRM_PRIV_CAT(SBRM_PRIV_VALLOC,        \
    SBRM_PRIV_ISEMPTY(__VA_ARGS__))(__VA_ARGS__)}), sizeof(list))
#define SBRM_NALLOC(list, scalar, n, ...) memcpy(SBRM_VALLOC(list, scalar, n) +\
    offsetof(list, data), (&(scalar[]){__VA_ARGS__}), (n) * sizeof(scalar)) -  \
    offsetof(list, data)
#define SBRM_VLIST(scalar) struct { const size_t sz; scalar data[]; }

/*----------------------------------------------------------------------------*
 * LOCAL: in-place localisation of a dynamic object, which is then freed; ass-
 * umes the object was created using the CPL allocators (cpl_malloc, etc); re-
 * turns a ptr to the local value just created: it takes a ptr to a heap-based
 * object and returns a ptr to a stack-based object of the same value... but the
 * new stack-based instance is only a shallow copy of the heap-based instance,
 * so should probably only be used to localise simple types; because it copies
 * an object from the heap to the stack, you must be careful not to localise
 * very large objects or arrays that may overflow the stack; intended to be used
 * in functions that must return values rather than ptrs to values.  I haven't
 * found a way yet to make it work with _, ABORT, or CLEANUP... unfortunately.
 *
 * LCP: similar to LOCAL but doesn't release the 'src' object; useful for making
 * in-place copies of local (on the stack) objects: int x, *y = LCP(int, &x);
 * Also doesn't typecast the resulting pointer (handy for type punning).
 *----------------------------------------------------------------------------*/
#define SBRM_LOCAL(type, src) (type*)__sbrm_cp(&(type){0},(src),sizeof(type),1)
#define SBRM_LCP(type, src) __sbrm_cp(&(type){0}, (src), sizeof(type), 0)


CPL_END_DECLS

#endif /* SBRM_H_ */
