/**
 * @file error.c
 * @brief Unit tests for the libakgl status band: reservation, ownership and names.
 *
 * The libakerror registry is process-global, so these tests assert against
 * whatever akgl_error_init() left behind rather than building their own state.
 */

#include <string.h>
#include <akerror.h>

#include <akgl/error.h>

#include "testutil.h"

/**
 * @brief akgl_error_init() must own the libakgl status band and name every code in it.
 *
 * A code whose name never registered degrades to "Unknown Error" in every stack
 * trace that carries it, and a band we never reserved is one another component
 * can name out from under us. Both stay silent until something has already gone
 * wrong, so assert them directly rather than waiting to read a useless trace.
 */
akerr_ErrorContext *test_error_init_owns_the_status_band(void)
{
    PREPARE_ERROR(e);
    static const struct {
	int status;
	const char *name;
    } expected[] = {
	{ AKGL_ERR_SDL,            "SDL Error"       },
	{ AKGL_ERR_REGISTRY,       "Registry Error"  },
	{ AKGL_ERR_HEAP,           "Heap Error"      },
	{ AKGL_ERR_BEHAVIOR,       "Behavior Error"  },
	{ AKGL_ERR_LOGICINTERRUPT, "Logic Interrupt" },
	{ AKGL_ERR_COLLISION,      "Collision Error" },
	{ AKGL_ERR_UI,             "UI Error"        }
    };
    bool named = true;
    int i = 0;

    ATTEMPT {
	CATCH(e, akgl_error_init());

	TEST_ASSERT(e, (int)(sizeof(expected) / sizeof(expected[0])) == AKGL_ERR_COUNT,
		    "the libakgl status band holds %d codes but %d are named here",
		    AKGL_ERR_COUNT, (int)(sizeof(expected) / sizeof(expected[0])));

	for ( i = 0; i < (int)(sizeof(expected) / sizeof(expected[0])); i++ ) {
	    TEST_ASSERT_FLAG(named,
			     strcmp(akerr_name_for_status(expected[i].status, NULL),
				    expected[i].name) == 0);
	}
	TEST_ASSERT(e, named,
		    "akgl_error_init did not register the expected name for every AKGL_ERR_* code");

	// The reservation is what makes those names ours. Without it the
	// registrations above would still succeed for anyone who asked.
	TEST_EXPECT_STATUS(e, AKERR_STATUS_NAME_FOREIGN,
			   akerr_register_status_name("not-libakgl", AKGL_ERR_HEAP, "Squatter"),
			   "a foreign owner was allowed to rename a libakgl status");
	TEST_EXPECT_STATUS(e, AKERR_STATUS_RANGE_OVERLAP,
			   akerr_reserve_status_range(AKGL_ERR_BASE, AKGL_ERR_COUNT, "not-libakgl"),
			   "a foreign owner was allowed to reserve the libakgl status band");
    } CLEANUP {
    } PROCESS(e) {
    } FINISH(e, true);
    SUCCEED_RETURN(e);
}

/**
 * @brief Calling akgl_error_init() twice must be a no-op, not a self-collision.
 *
 * Nothing in libakgl orders initialization for an embedding program, so a second
 * call has to be harmless. libakerror only treats an *identical* reservation as
 * a repeat -- a subset or superset raises -- which makes this a real constraint
 * on AKGL_ERR_BASE and AKGL_ERR_COUNT, not a triviality.
 */
akerr_ErrorContext *test_error_init_is_idempotent(void)
{
    PREPARE_ERROR(e);

    ATTEMPT {
	TEST_EXPECT_OK(e, akgl_error_init(), "the second akgl_error_init failed");
	TEST_EXPECT_OK(e, akgl_error_init(), "the third akgl_error_init failed");
	TEST_ASSERT(e, strcmp(akerr_name_for_status(AKGL_ERR_SDL, NULL), "SDL Error") == 0,
		    "re-running akgl_error_init lost the name for AKGL_ERR_SDL");
    } CLEANUP {
    } PROCESS(e) {
    } FINISH(e, true);
    SUCCEED_RETURN(e);
}

int main(void)
{
    PREPARE_ERROR(errctx);

    ATTEMPT {
	// Unlike every other suite, this one has no akgl_error_init() in
	// main() -- the first test is what brings the subsystem up, and
	// asserting that it does is the point of it.
	CATCH(errctx, test_error_init_owns_the_status_band());
	CATCH(errctx, test_error_init_is_idempotent());
    } CLEANUP {
    } PROCESS(errctx) {
    } FINISH_NORETURN(errctx);
}
