// ------------------------------------------------------------------------------------------------
#include "Library/Format.hpp"

// ------------------------------------------------------------------------------------------------
namespace SqMod {

/* ------------------------------------------------------------------------------------------------
 * Common implementation of formattable script types.
*/
struct SqObjectFmt
{
    /* --------------------------------------------------------------------------------------------
     * Object VM.
    */
    HSQUIRRELVM mVM{};

    /* --------------------------------------------------------------------------------------------
     * Script table object.
    */
    HSQOBJECT   mObj{};

    /* --------------------------------------------------------------------------------------------
     * Base constructor.
    */
    SqObjectFmt(HSQUIRRELVM vm, SQInteger idx, SQInteger & res)
        : mVM(vm), mObj()
    {
        res = sq_getstackobj(vm, idx, &mObj);
        // Not checking if this is the correct type because it shouldn't get here if it wasn't
    }

    /* --------------------------------------------------------------------------------------------
     * Copy constructor.
    */
    SqObjectFmt(const SqObjectFmt & o) = default;

    /* --------------------------------------------------------------------------------------------
     * Move constructor.
    */
    SqObjectFmt(SqObjectFmt && o) noexcept = default;

    /* --------------------------------------------------------------------------------------------
     * Destructor.
    */
    ~SqObjectFmt() =  default;

    /* --------------------------------------------------------------------------------------------
     * Copy assignment operator.
    */
    SqObjectFmt & operator = (const SqObjectFmt & o) = default;

    /* --------------------------------------------------------------------------------------------
     * Move assignment operator.
    */
    SqObjectFmt & operator = (SqObjectFmt && o) noexcept = default;
};

/* ------------------------------------------------------------------------------------------------
 * Specialization of SqObjectFmt for array type.
*/
struct SqArrayFmt : public SqObjectFmt
{
    using SqObjectFmt::SqObjectFmt;
    using SqObjectFmt::operator =;
};

/* ------------------------------------------------------------------------------------------------
 * Specialization of SqObjectFmt for table type.
*/
struct SqTableFmt : public SqObjectFmt
{
    using SqObjectFmt::SqObjectFmt;
    using SqObjectFmt::operator =;
};

/* ------------------------------------------------------------------------------------------------
 * Specialization of SqObjectFmt for class type.
*/
struct SqClassFmt : public SqObjectFmt
{
    using SqObjectFmt::SqObjectFmt;
    using SqObjectFmt::operator =;
};

/* ------------------------------------------------------------------------------------------------
 * Specialization of SqObjectFmt for instance type.
*/
struct SqInstanceFmt : public SqObjectFmt
{
    using SqObjectFmt::SqObjectFmt;
    using SqObjectFmt::operator =;
};

/* ------------------------------------------------------------------------------------------------
 * Specialization of SqObjectFmt for closure type.
*/
struct SqClosureFmt : public SqObjectFmt
{
    using SqObjectFmt::SqObjectFmt;
    using SqObjectFmt::operator =;
};

/* ------------------------------------------------------------------------------------------------
 * Specialization of SqObjectFmt for generator type.
*/
struct SqGeneratorFmt : public SqObjectFmt
{
    using SqObjectFmt::SqObjectFmt;
    using SqObjectFmt::operator =;
};

/* ------------------------------------------------------------------------------------------------
 * Specialization of SqObjectFmt for weak reference type.
*/
struct SqWeakReferenceFmt : public SqObjectFmt
{
    using SqObjectFmt::SqObjectFmt;
    using SqObjectFmt::operator =;
};

// ------------------------------------------------------------------------------------------------
SQInteger FormatContext::Process(HSQUIRRELVM vm, SQInteger text, SQInteger args, SQInteger end)
{
    // Is there any argument limit?
    if (end < 0) end = sq_gettop(vm);
    // Common value storage
    SQInteger i = 0;
    SQBool b = 0;
    SQFloat f = 0;
    const SQChar * s = nullptr;
    // Attempt to retrieve the string value from the stack
    mRes = sq_getstringandsize(vm, text, &s, &i);
    // By any chance we failed to retrieve string?
    if (SQ_FAILED(mRes))
    {
        return mRes;
    // Drop previous arguments, if any
    } else mArgs.clear();
    // Remember the format string and assume it lives on the stack
    mStr = fmt::basic_string_view< SQChar >(s, static_cast< size_t >(i));
    // Process the arguments in the specified range
    for(SQInteger idx = args; SQ_SUCCEEDED(mRes) && idx <= end; ++idx)
    {
        switch(sq_gettype(vm, idx))
        {
            // Null?
            case OT_NULL: {
                mArgs.push_back(nullptr);
            } break;
            // Integer?
            case OT_INTEGER: {
                mRes = sq_getinteger(vm, idx, &i);
                mArgs.push_back(i);
            } break;
            // Float?
            case OT_FLOAT: {
                mRes = sq_getfloat(vm, idx, &f);
                mArgs.push_back(f);
            } break;
            // Boolean?
            case OT_BOOL: {
                mRes = sq_getbool(vm, idx, &b);
                mArgs.push_back(static_cast< bool >(b));
            } break;
            // String?
            case OT_STRING: {
                mRes = sq_getstringandsize(vm, idx, &s, &i);
                if (SQ_SUCCEEDED(mRes)) mArgs.push_back(fmt::basic_string_view< SQChar >(s, static_cast< size_t >(i)));
            } break;
            // Table?
            case OT_TABLE: {
                SqTableFmt o(vm, idx, mRes);
                if (SQ_SUCCEEDED(mRes)) mArgs.push_back(o);
            } break;
            // Array?
            case OT_ARRAY: {
                SqArrayFmt o(vm, idx, mRes);
                if (SQ_SUCCEEDED(mRes)) mArgs.push_back(o);
            } break;
            // User data?
            case OT_USERDATA: {
                SQUserPointer p = nullptr;
                mRes = sq_getuserdata(vm, idx, &p, nullptr);
                if (SQ_SUCCEEDED(mRes)) mArgs.push_back(static_cast< void * >(p));
            } break;
            // Closure?
            case OT_CLOSURE:
            case OT_NATIVECLOSURE: {
                SqClosureFmt o(vm, idx, mRes);
                if (SQ_SUCCEEDED(mRes)) mArgs.push_back(o);
            } break;
            // Generator?
            case OT_GENERATOR: {
                SqGeneratorFmt o(vm, idx, mRes);
                if (SQ_SUCCEEDED(mRes)) mArgs.push_back(o);
            } break;
            // User pointer?
            case OT_USERPOINTER: {
                SQUserPointer p = nullptr;
                mRes = sq_getuserpointer(vm, idx, &p);
                if (SQ_SUCCEEDED(mRes)) mArgs.push_back(static_cast< void * >(p));
            } break;
            // Class?
            case OT_CLASS: {
                SqClassFmt o(vm, idx, mRes);
                if (SQ_SUCCEEDED(mRes)) mArgs.push_back(o);
            } break;
            // Instance?
            case OT_INSTANCE: {
                SqInstanceFmt o(vm, idx, mRes);
                if (SQ_SUCCEEDED(mRes)) mArgs.push_back(o);
            } break;
            // Weak reference?
            case OT_WEAKREF: {
                SqWeakReferenceFmt o(vm, idx, mRes);
                if (SQ_SUCCEEDED(mRes)) mArgs.push_back(o);
            } break;
            // Unknown!
            default: mRes = sq_throwerror(vm, "Unknown or unsupported object type");
        }
    }
    // Return result
    return mRes;
}

// ------------------------------------------------------------------------------------------------
template < class FormatContext >
inline auto FormatObjectInContext(HSQUIRRELVM vm, SQInteger idx, SQInteger src, FormatContext & ctx) -> decltype(ctx.out())
{
    // Identify what type of value we should format
    switch(sq_gettype(vm, idx))
    {
        // Null?
        case OT_NULL: {
            return fmt::format_to(ctx.out(), "0x0");
        }
        // Integer?
        case OT_INTEGER: {
            SQInteger i = 0;
            sq_getinteger(vm, idx, &i);
            return fmt::format_to(ctx.out(), "{}", i);
        }
        // Float?
        case OT_FLOAT: {
            SQFloat f = 0;
            sq_getfloat(vm, idx, &f);
            return fmt::format_to(ctx.out(), "{}", f);
        }
        // Boolean?
        case OT_BOOL: {
            SQBool b = 0;
            sq_getbool(vm, idx, &b);
            return fmt::format_to(ctx.out(), "{}", b ? "true" : "false");
        }
        // String?
        case OT_STRING: {
            SQInteger i = 0;
            const SQChar * s = nullptr;
            sq_getstringandsize(vm, idx, &s, &i);
            return fmt::format_to(ctx.out(), "{}", fmt::basic_string_view< SQChar >(s, static_cast< size_t >(i)));
        }
        // Table? TODO: generate json-style string!
        case OT_TABLE: throw fmt::format_error("no viable conversion from table to string");
        // Array? TODO: generate json-style string!
        case OT_ARRAY: throw fmt::format_error("no viable conversion from array to string");
        case OT_USERDATA: {
            SQUserPointer p = nullptr;
            sq_getuserdata(vm, idx, &p, nullptr);
            return fmt::format_to(ctx.out(), "{:p}", p);
        }
        // Closure?
        case OT_CLOSURE:
        case OT_NATIVECLOSURE: {
            SQInteger n = 0, x;
            // Retrieve the number of parameters that this closure expects
            if (SQ_SUCCEEDED(sq_getclosureinfo(vm, idx, &n, &x)))
            {
                // Does this closure need any parameters?
                if (n <= 1)
                {
                    const SQObjectType st = sq_gettype(vm, src);
                    // is the source is a class or instance?
                    if (st == OT_CLASS || st == OT_INSTANCE)
                    {
                        sq_push(vm, src); // Use that as environment
                    }
                    else
                    {
                        sq_push(vm, 1); // Use the current environment
                    }
                    // Attempt to invoke the function
                    if (SQ_SUCCEEDED(sq_call(vm, 1, SQTrue, SQFalse)))
                    {
                        // At this point we have a new object on the stack that must be removed
                        SqPopTopGuard tpg(vm);
                        // Invoke this function again on the returned value
                        return FormatObjectInContext(vm, -1, src, ctx);
                    } else throw fmt::format_error(LastErrorString(vm));
                }
                else
                {
                    std::string s("closure requires ");
                    fmt::format_int f(n);
                    s.append(f.data(), f.size());
                    s.append(" parameters and cannot be invoked");
                    throw fmt::format_error(s);
                }
            } else throw fmt::format_error("failed to retrieve closure information");
        }
        // Generator?
        case OT_GENERATOR: {
            // Guard the stack size
            StackGuard sg(vm);
            // Is the generator at the top of the stack?
            if (idx != -1 && idx != sq_gettop(vm))
            {
                sq_push(vm, idx); // Then push it
            }
            // Keep resuming the generator until null is returned
            while (true)
            {
                if (SQ_SUCCEEDED(sq_resume(vm, SQTrue, SQFalse)))
                {
                    // At this point we have a new object on the stack that must be removed
                    ::SqMod::SqPopTopGuard rpg(vm);
                    // Is the returned value not null?
                    if (sq_gettype(vm, -1) != OT_NULL)
                    {
                        // Invoke this function again on the returned value
                        return FormatObjectInContext(vm, -1, src, ctx);
                    } else break; // Stop
                } else throw fmt::format_error(LastErrorString(vm));
            }
        }
        // user pointer?
        case OT_USERPOINTER: {
            SQUserPointer p = nullptr;
            sq_getuserpointer(vm, idx, &p);
            return fmt::format_to(ctx.out(), "{:p}", p);
        }
        // Class?
        case OT_CLASS: {
            // Attempt to retrieve the type name of the specified value
            if (SQ_SUCCEEDED(sq_typeof(vm, idx)))
            {
                // At this point we have a new object on the stack that must be removed
                SqPopTopGuard tpg(vm);
                // The value on the stack may not be a string
                if (SQ_SUCCEEDED(sq_tostring(vm, -1)))
                {
                    SQInteger i = 0;
                    const SQChar * s = nullptr;
                    // At this point we have a new object on the stack that must be removed
                    SqPopTopGuard spg(vm);
                    // Attempt to obtain the string pointer
                    if (SQ_SUCCEEDED(sq_getstringandsize(vm, -1, &s, &i)))
                    {
                        return fmt::format_to(ctx.out(), "{}", fmt::basic_string_view< SQChar >(s, static_cast< size_t >(i)));
                    } else throw fmt::format_error("failed to retrieve type name string from stack");
                } else throw fmt::format_error("failed to convert class type name to string");
            } else throw fmt::format_error("no viable conversion from class to string");
        }
        // Instance?
        case OT_INSTANCE: {
            // Attempt to convert the value from the stack to a string
            if (SQ_SUCCEEDED(sq_tostring(vm, idx)))
            {
                SQInteger i = 0;
                const SQChar * s = nullptr;
                // At this point we have a new object on the stack that must be removed
                SqPopTopGuard spg(vm);
                // Attempt to obtain the string pointer
                if (SQ_SUCCEEDED(sq_getstringandsize(vm, -1, &s, &i)))
                {
                    return fmt::format_to(ctx.out(), "{}", fmt::basic_string_view< SQChar >(s, static_cast< size_t >(i)));
                } else throw fmt::format_error("failed to retrieve instance string representation from stack");
            } else throw fmt::format_error("no viable conversion from instance to string");
        }
        // Weak reference?
        case OT_WEAKREF: {
            // Push the referenced object on the stack
            if (SQ_SUCCEEDED(sq_getweakrefval(vm, idx)))
            {
                // At this point we have a new object on the stack that must be removed
                SqPopTopGuard vpg(vm);
                // Invoke this function again on the referenced object
                return FormatObjectInContext(vm, -1, src, ctx);
            } else throw fmt::format_error("failed to retrieve weak reference value");
        }
        // Unknown
        default: break;
    }
    // Unknown type
    throw fmt::format_error("unknown or unsupported value type");
}

// ------------------------------------------------------------------------------------------------
void ExtendedFormatProcess(StackStrF & ss, SQInteger top)
{
    FormatContext ctx;
    // Attempt to perform the format
    ss.mRes = ctx.Proc(ss.mVM, ss.mIdx, ss.mIdx + 1, top);
    // Did format succeed?
    if (SQ_SUCCEEDED(ss.mRes))
    {
        // Transform the string into a script object
        sq_pushstring(ss.mVM, ctx.mOut.data(), static_cast<SQInteger>(ctx.mOut.size()));
        // At this point we have a new object on the stack that must be removed
        SqPopTopGuard spg(ss.mVM);
        // Obtain a reference to the string object
        ss.mRes = sq_getstackobj(ss.mVM, -1, &ss.mObj);
        // Could we retrieve the object from the stack?
        if (SQ_SUCCEEDED(ss.mRes))
        {
            // Keep a strong reference to the object
            sq_addref(ss.mVM, &ss.mObj);
            // Attempt to retrieve the string value from the stack
            ss.mRes = sq_getstringandsize(ss.mVM, -1, &ss.mPtr, &ss.mLen);
        }
        // Did the retrieval succeeded but ended up with a null string pointer?
        if (SQ_SUCCEEDED(ss.mRes) && !ss.mPtr)
        {
            ss.mRes = sq_throwerror(ss.mVM, "Unable to retrieve the string");
        }
    }
}

// ------------------------------------------------------------------------------------------------
static SQInteger SqFormat(HSQUIRRELVM vm)
{
    const SQInteger top = sq_gettop(vm);
    // Make sure we have enough parameters
    if (top < 2)
    {
        return sq_throwerror(vm, "Insufficient parameters");
    }

    FormatContext ctx;
    // Attempt to generate the formatted string
    if (SQ_FAILED(ctx.Proc(vm, 2, 3, top)) || SQ_FAILED(ctx.Generate(vm)))
    {
        return ctx.mRes;
    }
    // Push it on the stack
    sq_pushstring(vm, ctx.mOut.data(), static_cast< SQInteger >(ctx.mOut.size()));
    // Specify that we returned a value
    return 1;
}

// ------------------------------------------------------------------------------------------------
static SQInteger SqLocaleFormat(HSQUIRRELVM vm)
{
    const SQInteger top = sq_gettop(vm);
    // Make sure we have enough parameters
    if (top < 3)
    {
        return sq_throwerror(vm, "Insufficient parameters");
    }

    StackStrF loc(vm, 2);
    // Attempt to generate the locale string
    if (SQ_FAILED(loc.Proc(false)))
    {
        return loc.mRes;
    }
    FormatContext ctx;
    // Attempt to generate the formatted string
    if (SQ_FAILED(ctx.Proc(vm, 3, 4, top)) || SQ_FAILED(ctx.GenerateLoc(vm, loc.mPtr)))
    {
        return ctx.mRes;
    }
    // Push it on the stack
    sq_pushstring(vm, ctx.mOut.data(), static_cast< SQInteger >(ctx.mOut.size()));
    // Specify that we returned a value
    return 1;
}

// ================================================================================================
void Register_Format(HSQUIRRELVM vm)
{
    RootTable(vm).SquirrelFunc(_SC("fmt"), SqFormat);
    RootTable(vm).SquirrelFunc(_SC("locale_fmt"), SqLocaleFormat);
}

} // Namespace:: SqMod

// ------------------------------------------------------------------------------------------------
namespace fmt {

/* ------------------------------------------------------------------------------------------------
 * Implements formatting for script arrays.
*/
template < > struct formatter< ::SqMod::SqArrayFmt >
{
    size_t mIndex{};

    /* --------------------------------------------------------------------------------------------
     * Parse the specified string range.
    */
    auto parse(format_parse_context & ctx) -> decltype(ctx.begin())
    {
        // Retrieve the range that to be parsed
        auto it = ctx.begin(), begin = it, end = ctx.end();
        // Find the closing brace
        while (it != end && *it != '}') ++it;
        // Check if closing brace was found
        if (it != end && *it != '}')
        {
            throw format_error("missing curly brace }");
        }
        // Check if an index was specified
        else if (it == begin)
        {
            mIndex = 0; // Default to first element
        }
        else
        {
            char * p_end = nullptr;
            // Transform the value into an index
        #ifdef _SQ64
            mIndex = std::strtoull(&(*begin), &p_end, 10);
        #else
            mIndex = std::strtoul(&(*begin), &p_end, 10);
        #endif
            // Validate index range
            if (errno == ERANGE)
            {
                throw format_error("index out of range");
            }
            // Validate index value
            else if (*p_end != '}')
            {
                throw format_error("invalid integer index");
            }
        }
        // Return an iterator past the end of the parsed range:
        return it;
    }

    /* --------------------------------------------------------------------------------------------
     * Perform the requested format.
    */
    template < class FormatContext >
    auto format(const ::SqMod::SqArrayFmt & o, FormatContext & ctx) -> decltype(ctx.out())
    {
        // Remember the stack size
        SQInteger top = sq_gettop(o.mVM);
        // Push the array object on the stack
        sq_pushobject(o.mVM, o.mObj);
        // At this point we have a new object on the stack that must be removed
        ::SqMod::SqPopTopGuard apg(o.mVM);
        // Push the index on the stack
        sq_pushinteger(o.mVM, static_cast< SQInteger >(mIndex));
        // Attempt to retrieve the index at the specified index
        if (SQ_FAILED(sq_get(o.mVM, -2)))
        {
            std::string s("no such index in array: ");
            fmt::format_int f(mIndex);
            s.append(f.data(), f.size());
            throw format_error(s);
        }
        // At this point we have a new object on the stack that must be removed
        ::SqMod::SqPopTopGuard epg(o.mVM);
        // Run the format on the object that was pushed on the stack
        return ::SqMod::FormatObjectInContext(o.mVM, -1, top + 1, ctx);
    }
};

/* ------------------------------------------------------------------------------------------------
 * Implements formatting for script tables.
*/
template < > struct formatter< ::SqMod::SqTableFmt >
{
    std::string mIndex{};

    /* --------------------------------------------------------------------------------------------
     * Parse the specified string range.
    */
    auto parse(format_parse_context & ctx) -> decltype(ctx.begin())
    {
        // Retrieve the range that to be parsed
        auto it = ctx.begin(), begin = it, end = ctx.end();
        // Find the closing brace
        while (it != end && *it != '}') ++it;
        // Check if closing brace was found
        if (it != end && *it != '}')
        {
            throw format_error("missing curly brace }");
        }
        // Check if a name was specified
        else if (it == begin)
        {
            throw format_error("must specify a table index");
        }
        // Store the index name
        mIndex.assign(begin, it);
        // Return an iterator past the end of the parsed range:
        return it;
    }

    /* --------------------------------------------------------------------------------------------
     * Perform the requested format.
    */
    template < class FormatContext >
    auto format(const ::SqMod::SqTableFmt & o, FormatContext & ctx) -> decltype(ctx.out())
    {
        // Remember the stack size
        SQInteger top = sq_gettop(o.mVM);
        // Push the array object on the stack
        sq_pushobject(o.mVM, o.mObj);
        // At this point we have a new object on the stack that must be removed
        ::SqMod::SqPopTopGuard apg(o.mVM);
        // Push the index on the stack (only works with string keys/elements)
        sq_pushstring(o.mVM, mIndex.data(), static_cast< SQInteger >(mIndex.size()));
        // Attempt to retrieve the index at the specified index
        if (SQ_FAILED(sq_get(o.mVM, -2)))
        {
            std::string s("no such index in table: ");
            s.append(mIndex);
            throw format_error(s);
        }
        // At this point we have a new object on the stack that must be removed
        ::SqMod::SqPopTopGuard epg(o.mVM);
        // Run the format on the object that was pushed on the stack
        return ::SqMod::FormatObjectInContext(o.mVM, -1, top + 1, ctx);
    }
};

/* ------------------------------------------------------------------------------------------------
 * Implements formatting for script classes.
*/
template < > struct formatter< ::SqMod::SqClassFmt >
{
    std::string mIndex{};

    /* --------------------------------------------------------------------------------------------
     * Parse the specified string range.
    */
    auto parse(format_parse_context & ctx) -> decltype(ctx.begin())
    {
        // Retrieve the range that to be parsed
        auto it = ctx.begin(), begin = it, end = ctx.end();
        // Find the closing brace
        while (it != end && *it != '}') ++it;
        // Check if closing brace was found
        if (it != end && *it != '}')
        {
            throw format_error("missing curly brace }");
        }
        // Check if a identifier was specified
        else if (it == begin)
        {
            throw format_error("must specify a member identifier");
        }
        // Validate first character
        else if (std::isalpha(*begin) == 0 && *begin != '_')
        {
            throw format_error("member names must start with an alphabetic or underscore character");
        }
        // Validate member identifier
        else if (!std::all_of(begin, it, [](int c) { return std::isalnum(c) != 0 || c == '_'; }))
        {
            throw format_error("member names can only contain alpha-numeric or underscore characters");
        }
        // Store the member identifier
        mIndex.assign(begin, it);
        // Return an iterator past the end of the parsed range:
        return it;
    }

    /* --------------------------------------------------------------------------------------------
     * Perform the requested format.
    */
    template < class FormatContext >
    auto format(const ::SqMod::SqClassFmt & o, FormatContext & ctx) -> decltype(ctx.out())
    {
        // Remember the stack size
        SQInteger top = sq_gettop(o.mVM);
        // Push the class object on the stack
        sq_pushobject(o.mVM, o.mObj);
        // At this point we have a new object on the stack that must be removed
        ::SqMod::SqPopTopGuard apg(o.mVM);
        // Push the index on the stack (only works with string keys/elements)
        sq_pushstring(o.mVM, mIndex.data(), static_cast< SQInteger >(mIndex.size()));
        // Attempt to retrieve the index at the specified index
        if (SQ_FAILED(sq_get(o.mVM, -2)))
        {
            std::string s("no such identifier in class: ");
            s.append(mIndex);
            throw format_error(s);
        }
        // At this point we have a new object on the stack that must be removed
        ::SqMod::SqPopTopGuard epg(o.mVM);
        // Run the format on the object that was pushed on the stack
        return ::SqMod::FormatObjectInContext(o.mVM, -1, top + 1, ctx);
    }
};

/* ------------------------------------------------------------------------------------------------
 * Implements formatting for script instances.
*/
template < > struct formatter< ::SqMod::SqInstanceFmt >
{
    std::string mIndex{};

    /* --------------------------------------------------------------------------------------------
     * Parse the specified string range.
    */
    auto parse(format_parse_context & ctx) -> decltype(ctx.begin())
    {
        // Retrieve the range that to be parsed
        auto it = ctx.begin(), begin = it, end = ctx.end();
        // Find the closing brace
        while (it != end && *it != '}') ++it;
        // Check if closing brace was found
        if (it != end && *it != '}')
        {
            throw format_error("missing curly brace }");
        }
        // Check if a identifier was specified
        else if (it == begin)
        {
            throw format_error("must specify a member identifier");
        }
        // Validate first character
        else if (std::isalpha(*begin) == 0 && *begin != '_')
        {
            throw format_error("member names must start with an alphabetic or underscore character");
        }
        // Validate member identifier
        else if (!std::all_of(begin, it, [](int c) { return std::isalnum(c) != 0 || c == '_'; }))
        {
            throw format_error("member names can only contain alpha-numeric or underscore characters");
        }
        // Store the member identifier
        mIndex.assign(begin, it);
        // Return an iterator past the end of the parsed range:
        return it;
    }

    /* --------------------------------------------------------------------------------------------
     * Perform the requested format.
    */
    template < class FormatContext >
    auto format(const ::SqMod::SqInstanceFmt & o, FormatContext & ctx) -> decltype(ctx.out())
    {
        // Remember the stack size
        SQInteger top = sq_gettop(o.mVM);
        // Push the class object on the stack
        sq_pushobject(o.mVM, o.mObj);
        // At this point we have a new object on the stack that must be removed
        ::SqMod::SqPopTopGuard apg(o.mVM);
        // Push the index on the stack (only works with string keys/elements)
        sq_pushstring(o.mVM, mIndex.data(), static_cast< SQInteger >(mIndex.size()));
        // Attempt to retrieve the index at the specified index
        if (SQ_FAILED(sq_get(o.mVM, -2)))
        {
            std::string s("no such identifier in instance: ");
            s.append(mIndex);
            throw format_error(s);
        }
        // At this point we have a new object on the stack that must be removed
        ::SqMod::SqPopTopGuard epg(o.mVM);
        // Run the format on the object that was pushed on the stack
        return ::SqMod::FormatObjectInContext(o.mVM, -1, top + 1, ctx);
    }
};

/* ------------------------------------------------------------------------------------------------
 * Implements formatting for script closures.
*/
template < > struct formatter< ::SqMod::SqClosureFmt >
{
    std::string mParameter{};

    /* --------------------------------------------------------------------------------------------
     * Parse the specified string range.
    */
    auto parse(format_parse_context & ctx) -> decltype(ctx.begin())
    {
        // Retrieve the range that to be parsed
        auto it = ctx.begin(), begin = it, end = ctx.end();
        // Find the closing brace
        while (it != end && *it != '}') ++it;
        // Check if closing brace was found
        if (it != end && *it != '}')
        {
            throw format_error("missing curly brace }");
        }
        // Check if a parameter was specified
        else if (it != begin)
        {
            mParameter.assign(begin, it); // Store the parameter
        }
        // Return an iterator past the end of the parsed range:
        return it;
    }

    /* --------------------------------------------------------------------------------------------
     * Perform the requested format.
    */
    template < class FormatContext >
    auto format(const ::SqMod::SqClosureFmt & o, FormatContext & ctx) -> decltype(ctx.out())
    {
        SQInteger n = 0, x;
        // Push the closure object on the stack
        sq_pushobject(o.mVM, o.mObj);
        // At this point we have a new object on the stack that must be removed
        ::SqMod::SqPopTopGuard opg(o.mVM);
        // Retrieve the number of parameters that this closure expects
        if (SQ_SUCCEEDED(sq_getclosureinfo(o.mVM, -1, &n, &x)))
        {
            // Does this closure need any parameters?
            if (n <= 2)
            {
                // Use the current environment
                sq_push(o.mVM, 1);
                // If it requires a parameter then we push the string anyway
                if (n == 2 || !mParameter.empty())
                {
                    sq_pushstring(o.mVM, mParameter.data(), static_cast< SQInteger >(mParameter.size()));
                    x = 2; // Include string in parameter count
                } else x = 1; // Exclude string from parameter count
                // Attempt to invoke the function
                if (SQ_SUCCEEDED(sq_call(o.mVM, x, SQTrue, SQFalse)))
                {
                    // At this point we have a new object on the stack that must be removed
                    ::SqMod::SqPopTopGuard tpg(o.mVM);
                    // Invoke this function again on the returned value
                    ::SqMod::FormatObjectInContext(o.mVM, -1, 1, ctx);
                } else throw fmt::format_error(::SqMod::LastErrorString(o.mVM));
            }
            else
            {
                std::string s("closure requires ");
                fmt::format_int f(n);
                s.append(f.data(), f.size());
                s.append(" parameters and cannot be invoked");
                throw fmt::format_error(s);
            }
        } else throw fmt::format_error("failed to retrieve closure information");
        // Return the output iterator
        return ctx.out();
    }
};

/* ------------------------------------------------------------------------------------------------
 * Implements formatting for script generators.
*/
template < > struct formatter< ::SqMod::SqGeneratorFmt >
{
    std::string mDelimiter{};

    /* --------------------------------------------------------------------------------------------
     * Parse the specified string range.
    */
    auto parse(format_parse_context & ctx) -> decltype(ctx.begin())
    {
        // Retrieve the range that to be parsed
        auto it = ctx.begin(), begin = it, end = ctx.end();
        // Find the closing brace
        while (it != end && *it != '}') ++it;
        // Check if closing brace was found
        if (it != end && *it != '}')
        {
            throw format_error("missing curly brace }");
        }
        // Check if a delimiter was specified
        else if (it != begin)
        {
            mDelimiter.assign(begin, it); // Store the delimiter
        }
        // Return an iterator past the end of the parsed range:
        return it;
    }

    /* --------------------------------------------------------------------------------------------
     * Perform the requested format.
    */
    template < class FormatContext >
    auto format(const ::SqMod::SqGeneratorFmt & o, FormatContext & ctx) -> decltype(ctx.out())
    {
        // Push the generator object on the stack
        sq_pushobject(o.mVM, o.mObj);
        // At this point we have a new object on the stack that must be removed
        ::SqMod::SqPopTopGuard opg(o.mVM);
        // Keep resuming the generator until null is returned
        for (SQInteger n = 0; /*...*/ ; ++n)
        {
            if (SQ_SUCCEEDED(sq_resume(o.mVM, SQTrue, SQFalse)))
            {
                // At this point we have a new object on the stack that must be removed
                ::SqMod::SqPopTopGuard rpg(o.mVM);
                // Is the returned value not null?
                if (sq_gettype(o.mVM, -1) != OT_NULL)
                {
                    // Do not include the delimiter before the first or after last value
                    if (n != 0) format_to(ctx.out(), "{:s}", mDelimiter);
                    // Run the format on the object that was pushed on the stack
                    ::SqMod::FormatObjectInContext(o.mVM, -1, 1, ctx);
                } else break; // Stop
            } else throw format_error(::SqMod::LastErrorString(o.mVM));
        }
        // Return the output iterator
        return ctx.out();
    }
};

/* ------------------------------------------------------------------------------------------------
 * Implements formatting for script generators.
*/
template < > struct formatter< ::SqMod::SqWeakReferenceFmt >
{
    /* --------------------------------------------------------------------------------------------
     * Parse the specified string range.
    */
    auto parse(format_parse_context & ctx) -> decltype(ctx.begin()) // NOLINT(readability-convert-member-functions-to-static)
    {
        // Retrieve the range that to be parsed
        auto it = ctx.begin(), begin = it, end = ctx.end();
        // Find the closing brace
        while (it != end && *it != '}') ++it;
        // Check if closing brace was found
        if (it != end && *it != '}')
        {
            throw format_error("missing curly brace }");
        }
        // Check if a delimiter was specified
        else if (it != begin)
        {
            throw format_error("weak references don't accept format specifiers");
        }
        // Return an iterator past the end of the parsed range:
        return it;
    }

    /* --------------------------------------------------------------------------------------------
     * Perform the requested format.
    */
    template < class FormatContext >
    auto format(const ::SqMod::SqWeakReferenceFmt & o, FormatContext & ctx) -> decltype(ctx.out())
    {
        // Push the reference object on the stack
        sq_pushobject(o.mVM, o.mObj);
        // At this point we have a new object on the stack that must be removed
        ::SqMod::SqPopTopGuard opg(o.mVM);
        // Push the referenced object on the stack
        if (SQ_FAILED(sq_getweakrefval(o.mVM, -1)))
        {
            throw fmt::format_error("failed to retrieve weak reference value");
        }
        // At this point we have a new object on the stack that must be removed
        ::SqMod::SqPopTopGuard vpg(o.mVM);
        // Run the format on the object that was pushed on the stack
        return ::SqMod::FormatObjectInContext(o.mVM, -1, 1, ctx);
    }
};

} // Namespace:: fmt