// ------------------------------------------------------------------------------------------------
#include "Library/String.hpp"
#include "Base/Shared.hpp"
#include "Base/Buffer.hpp"

// ------------------------------------------------------------------------------------------------
#include <cctype>
#include <cstdio>
#include <cstdlib>
#include <cstring>

// ------------------------------------------------------------------------------------------------
#include <algorithm>

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

// ------------------------------------------------------------------------------------------------
CSStr LeftStr(CSStr s, SQChar f, Uint32 w)
{
    // Is the specified width valid?
    if (!w)
    {
        return _SC(""); // Default to an empty string!
    }
    // Allocate a buffer with the requested width
    Buffer b(w + 1); // + null terminator
    // Is the specified string valid?
    if (!s || *s == '\0')
    {
        // Insert only the fill character
        std::memset(b.Data(), f, w);
    }
    else
    {
        // Calculate the string length
        const Uint32 n = std::strlen(s);
        // Insert only the fill character first
        std::memset(b.Data(), f, w);
        // Overwrite with the specified string
        std::strncpy(b.Data(), s, n);
    }
    // End the resulted string
    b.At(w) = '\0';
    // Return the resulted string
    return b.Get< SQChar >();
}

// ------------------------------------------------------------------------------------------------
static SQInteger SqLeftStr(HSQUIRRELVM vm)
{
    const Int32 top = sq_gettop(vm);
    // Was the fill character specified?
    if (top <= 1)
    {
        return sq_throwerror(vm, "Missing fill character");
    }
    // Was the string width specified?
    else if (top <= 2)
    {
        return sq_throwerror(vm, "Missing string boundaries");
    }
    // Was the string value specified?
    else if (top <= 3)
    {
        return sq_throwerror(vm, "Missing string value");
    }
    // Attempt to generate the string value
    StackStrF val(vm, 4);
    // Have we failed to retrieve the string?
    if (SQ_FAILED(val.mRes))
    {
        return val.mRes; // Propagate the error!
    }
    // The fill character and string boundaries
    SQChar fchar = 0;
    Uint32 width = 0;
    // Attempt to retrieve the remaining arguments from the stack
    try
    {
        fchar = Var< SQChar >(vm, 2).value;
        width = Var< Uint32 >(vm, 3).value;
    }
    catch (const Sqrat::Exception & e)
    {
        return sq_throwerror(vm, e.what());
    }
    catch (...)
    {
        return sq_throwerror(vm, "Unable to retrieve arguments");
    }
    // Forward the call to the actual implementation
    sq_pushstring(vm, LeftStr(val.mPtr, fchar, width), -1);
    // We have an argument on the stack
    return 1;
}

// ------------------------------------------------------------------------------------------------
CSStr LeftStr(CSStr s, SQChar f, Uint32 w, Uint32 o)
{
    // Is the specified width valid?
    if (!w)
    {
        return _SC(""); // Default to an empty string!
    }
    // Is the specified offset within width range?
    else if (o > w)
    {
        STHROWF("Offset is out of bounds");
    }
    // Allocate a buffer with the requested width
    Buffer b(w + 1); // + null terminator
    // Is the specified string valid?
    if (!s || *s == '\0')
    {
        // Insert only the fill character
        std::memset(b.Data(), f, w);
    }
    else
    {
        // Calculate the string length
        const Uint32 n = std::strlen(s);
        // Insert the fill character first
        std::memset(b.Data(), f, w);
        // Overwrite with the specified string
        if (n > (w - o))
        {
            std::strncpy(b.Data() + o, s, n);
        }
        else
        {
            std::memcpy(b.Data() + o, s, (n) * sizeof(SQChar));
        }
    }
    // End the resulted string
    b.At(w) = '\0';
    // Return the resulted string
    return b.Get();
}

// ------------------------------------------------------------------------------------------------
static SQInteger SqLeftOffsetStr(HSQUIRRELVM vm)
{
    const Int32 top = sq_gettop(vm);
    // Was the fill character specified?
    if (top <= 1)
    {
        return sq_throwerror(vm, "Missing fill character");
    }
    // Was the string width specified?
    else if (top <= 2)
    {
        return sq_throwerror(vm, "Missing string boundaries");
    }
    // Was the string offset specified?
    else if (top <= 3)
    {
        return sq_throwerror(vm, "Missing string offset");
    }
    // Was the string value specified?
    else if (top <= 4)
    {
        return sq_throwerror(vm, "Missing string value");
    }
    // Attempt to generate the string value
    StackStrF val(vm, 5);
    // Have we failed to retrieve the string?
    if (SQ_FAILED(val.mRes))
    {
        return val.mRes; // Propagate the error!
    }
    // The fill character and string boundaries
    SQChar fchar = 0;
    Uint32 width = 0;
    Uint32 offset = 0;
    // Attempt to retrieve the remaining arguments from the stack
    try
    {
        fchar = Var< SQChar >(vm, 2).value;
        width = Var< Uint32 >(vm, 3).value;
        offset = Var< Uint32 >(vm, 4).value;
    }
    catch (const Sqrat::Exception & e)
    {
        return sq_throwerror(vm, e.what());
    }
    catch (...)
    {
        return sq_throwerror(vm, "Unable to retrieve arguments");
    }
    // Is the specified offset within width range?
    if (offset > width)
    {
        return sq_throwerror(vm, "Offset is out of bounds");
    }
    // Forward the call to the actual implementation
    sq_pushstring(vm, LeftStr(val.mPtr, fchar, width, offset), -1);
    // We have an argument on the stack
    return 1;
}

// ------------------------------------------------------------------------------------------------
CSStr RightStr(CSStr s, SQChar f, Uint32 w)
{
    // Is the specified width valid?
    if (!w)
    {
        return _SC(""); // Default to an empty string!
    }
    // Allocate a buffer with the requested width
    Buffer b(w + 1); // + null terminator
    // Is the specified string valid?
    if (!s || *s == '\0')
    {
        // Insert only the fill character
        std::memset(b.Data(), f, w);
    }
    else
    {
        // Calculate the string length
        const Uint32 n = std::strlen(s);
        // Insert the fill character first
        std::memset(b.Data(), f, w);
        // Overwrite with the specified string
        if (n >= w)
        {
            std::strncpy(b.Data(), s, w);
        }
        else
        {
            std::strncpy(b.Data() + (w - n), s, n);
        }
    }
    // End the resulted string
    b.At(w) = '\0';
    // Return the resulted string
    return b.Get< SQChar >();
}

// ------------------------------------------------------------------------------------------------
static SQInteger SqRightStr(HSQUIRRELVM vm)
{
    const Int32 top = sq_gettop(vm);
    // Was the fill character specified?
    if (top <= 1)
    {
        return sq_throwerror(vm, "Missing fill character");
    }
    // Was the string width specified?
    else if (top <= 2)
    {
        return sq_throwerror(vm, "Missing string boundaries");
    }
    // Was the string value specified?
    else if (top <= 3)
    {
        return sq_throwerror(vm, "Missing string value");
    }
    // Attempt to generate the string value
    StackStrF val(vm, 4);
    // Have we failed to retrieve the string?
    if (SQ_FAILED(val.mRes))
    {
        return val.mRes; // Propagate the error!
    }
    // The fill character and string boundaries
    SQChar fchar = 0;
    Uint32 width = 0;
    // Attempt to retrieve the remaining arguments from the stack
    try
    {
        fchar = Var< SQChar >(vm, 2).value;
        width = Var< Uint32 >(vm, 3).value;
    }
    catch (const Sqrat::Exception & e)
    {
        return sq_throwerror(vm, e.what());
    }
    catch (...)
    {
        return sq_throwerror(vm, "Unable to retrieve arguments");
    }
    // Forward the call to the actual implementation
    sq_pushstring(vm, RightStr(val.mPtr, fchar, width), -1);
    // We have an argument on the stack
    return 1;
}

// ------------------------------------------------------------------------------------------------
CSStr RightStr(CSStr s, SQChar f, Uint32 w, Uint32 o)
{
    // Is the specified width valid?
    if (!w)
    {
        return _SC(""); // Default to an empty string!
    }
    // Is the specified offset within width range?
    else if (o > w)
    {
        STHROWF("Offset is out of bounds");
    }
    // Allocate a buffer with the requested width
    Buffer b(w + 1); // + null terminator
    // Is the specified string valid?
    if (!s || *s == '\0')
    {
        // Insert only the fill character
        std::memset(b.Data(), f, w);
    }
    else
    {
        // Calculate the string length
        const Uint32 n = std::strlen(s);
        // Insert the fill character first
        std::memset(b.Data(), f, w);
        // Overwrite with the specified string
        if (n >= w || (n + o) >= w)
        {
            std::strncpy(b.Data(), s, w - o);
        }
        else
        {
            std::strncpy(b.Data() + ((w - n) - o), s, n);
        }
    }
    // End the resulted string
    b.At(w) = '\0';
    // Return the resulted string
    return b.Get< SQChar >();
}

// ------------------------------------------------------------------------------------------------
static SQInteger SqRightOffsetStr(HSQUIRRELVM vm)
{
    const Int32 top = sq_gettop(vm);
    // Was the fill character specified?
    if (top <= 1)
    {
        return sq_throwerror(vm, "Missing fill character");
    }
    // Was the string width specified?
    else if (top <= 2)
    {
        return sq_throwerror(vm, "Missing string boundaries");
    }
    // Was the string offset specified?
    else if (top <= 3)
    {
        return sq_throwerror(vm, "Missing string offset");
    }
    // Was the string value specified?
    else if (top <= 4)
    {
        return sq_throwerror(vm, "Missing string value");
    }
    // Attempt to generate the string value
    StackStrF val(vm, 5);
    // Have we failed to retrieve the string?
    if (SQ_FAILED(val.mRes))
    {
        return val.mRes; // Propagate the error!
    }
    // The fill character and string boundaries
    SQChar fchar = 0;
    Uint32 width = 0;
    Uint32 offset = 0;
    // Attempt to retrieve the remaining arguments from the stack
    try
    {
        fchar = Var< SQChar >(vm, 2).value;
        width = Var< Uint32 >(vm, 3).value;
        offset = Var< Uint32 >(vm, 4).value;
    }
    catch (const Sqrat::Exception & e)
    {
        return sq_throwerror(vm, e.what());
    }
    catch (...)
    {
        return sq_throwerror(vm, "Unable to retrieve arguments");
    }
    // Is the specified offset within width range?
    if (offset > width)
    {
        return sq_throwerror(vm, "Offset is out of bounds");
    }
    // Forward the call to the actual implementation
    sq_pushstring(vm, RightStr(val.mPtr, fchar, width, offset), -1);
    // We have an argument on the stack
    return 1;
}

// ------------------------------------------------------------------------------------------------
CSStr CenterStr(CSStr s, SQChar f, Uint32 w)
{
    // Is the specified width valid?
    if (!w)
    {
        return _SC(""); // Default to an empty string!
    }
    // Allocate a buffer with the requested width
    Buffer b(w + 1); // + null terminator
    // Is the specified string valid?
    if (!s || *s == '\0')
    {
        // Insert only the fill character
        std::memset(b.Data(), f, w);
    }
    else
    {
        // Calculate the string length
        const Uint32 n = std::strlen(s);
        // Calculate the insert position
        const Int32 p = ((w/2) - (n/2));
        // Insert only the fill character first
        std::memset(b.Data(), f, w);
        // Overwrite with the specified string
        std::strncpy(b.Data() + (p < 0 ? 0 : p), s, n);
    }
    // End the resulted string
    b.At(w) = '\0';
    // Return the resulted string
    return b.Get< SQChar >();
}

// ------------------------------------------------------------------------------------------------
static SQInteger SqCenterStr(HSQUIRRELVM vm)
{
    const Int32 top = sq_gettop(vm);
    // Was the fill character specified?
    if (top <= 1)
    {
        return sq_throwerror(vm, "Missing fill character");
    }
    // Was the string width specified?
    else if (top <= 2)
    {
        return sq_throwerror(vm, "Missing string boundaries");
    }
    // Was the string value specified?
    else if (top <= 3)
    {
        return sq_throwerror(vm, "Missing string value");
    }
    // Attempt to generate the string value
    StackStrF val(vm, 4);
    // Have we failed to retrieve the string?
    if (SQ_FAILED(val.mRes))
    {
        return val.mRes; // Propagate the error!
    }
    // The fill character and string boundaries
    SQChar fchar = 0;
    Uint32 width = 0;
    // Attempt to retrieve the remaining arguments from the stack
    try
    {
        fchar = Var< SQChar >(vm, 2).value;
        width = Var< Uint32 >(vm, 3).value;
    }
    catch (const Sqrat::Exception & e)
    {
        return sq_throwerror(vm, e.what());
    }
    catch (...)
    {
        return sq_throwerror(vm, "Unable to retrieve arguments");
    }
    // Forward the call to the actual implementation
    sq_pushstring(vm, CenterStr(val.mPtr, fchar, width), -1);
    // We have an argument on the stack
    return 1;
}

// ------------------------------------------------------------------------------------------------
Buffer StrJustAlphaNumB(CSStr str)
{
    // See if we actually have something to search for
    if(!str || *str == '\0')
    {
        return Buffer(); // Default to an empty buffer!
    }
    // Calculate the string length
    Uint32 size = std::strlen(str);
    // Obtain a temporary buffer
    Buffer b(size + 1); // + null terminator
    // Resulted string size
    Uint32 n = 0;
    // Currently processed character
    SQChar c = 0;
    // Process characters
    while ((c = *(str++)) != '\0')
    {
        // Is this an alpha-numeric character?
        if (std::isalnum(c) != 0)
        {
            // Save it and move to the next one
            b.At(n++) = c;
        }
    }
    // End the resulted string
    b.At(n) = '\0';
    // Move the cursor to the end
    b.Move(n);
    // Return ownership of the buffer
    return std::move(b);
}

// ------------------------------------------------------------------------------------------------
CSStr StrJustAlphaNum(CSStr str)
{
    // See if we actually have something to search for
    if(!str || *str == '\0')
    {
        return _SC(""); // Default to an empty string!
    }
    // Attempt to convert and return the result
    return StrJustAlphaNumB(str).Get< SQChar >();
}

// ------------------------------------------------------------------------------------------------
static SQInteger SqJustAlphaNum(HSQUIRRELVM vm)
{
    // Attempt to retrieve the value from the stack as a string
    StackStrF val(vm, 2);
    // Have we failed to retrieve the string?
    if (SQ_FAILED(val.mRes))
    {
        return val.mRes; // Propagate the error!
    }
    // Forward the call to the actual implementation and store the buffer
    Buffer b(StrJustAlphaNumB(val.mPtr));
    // Is there anything in the buffer?
    if (!b)
    {
        // Default to an empty string
        sq_pushstring(vm, _SC(""), 0);
    }
    else
    {
        // Push the buffer data as a string
        sq_pushstring(vm, b.Data(), b.Position());
    }
    // We have a value to return on the stack
    return 1;
}

// ------------------------------------------------------------------------------------------------
Buffer StrToLowercaseB(CSStr str)
{
    // See if we actually have something to search for
    if(!str || *str == '\0')
    {
        return Buffer(); // Default to an empty buffer!
    }
    // Calculate the string length
    Uint32 size = std::strlen(str);
    // Obtain a temporary buffer
    Buffer b(size + 1); // + null terminator
    // Resulted string size
    Uint32 n = 0;
    // Process characters
    while (*str != '\0')
    {
        // Convert it and move to the next one
        b.At(n++) = std::tolower(*(str++));
    }
    // End the resulted string
    b.At(n) = '\0';
    // Move the cursor to the end
    b.Move(n);
    // Return ownership of the buffer
    return std::move(b);
}

// ------------------------------------------------------------------------------------------------
CSStr StrToLowercase(CSStr str)
{
    // See if we actually have something to search for
    if(!str || *str == '\0')
    {
        return _SC(""); // Default to an empty string!
    }
    // Attempt to convert and return the result
    return StrToLowercaseB(str).Get< SQChar >();
}

// ------------------------------------------------------------------------------------------------
static SQInteger SqToLowercase(HSQUIRRELVM vm)
{
    // Attempt to retrieve the value from the stack as a string
    StackStrF val(vm, 2);
    // Have we failed to retrieve the string?
    if (SQ_FAILED(val.mRes))
    {
        return val.mRes; // Propagate the error!
    }
    // Forward the call to the actual implementation and store the buffer
    Buffer b(StrToLowercaseB(val.mPtr));
    // Is there anything in the buffer?
    if (!b)
    {
        // Default to an empty string
        sq_pushstring(vm, _SC(""), 0);
    }
    else
    {
        // Push the buffer data as a string
        sq_pushstring(vm, b.Data(), b.Position());
    }
    // We have a value to return on the stack
    return 1;
}

// ------------------------------------------------------------------------------------------------
Buffer StrToUppercaseB(CSStr str)
{
    // See if we actually have something to search for
    if(!str || *str == '\0')
    {
        return Buffer(); // Default to an empty buffer!
    }
    // Calculate the string length
    Uint32 size = std::strlen(str); // + null terminator
    // Obtain a temporary buffer
    Buffer b(size + 1); // + null terminator
    // Resulted string size
    Uint32 n = 0;
    // Process characters
    while (*str != '\0')
    {
        // Convert it and move to the next one
        b.At(n++) = std::toupper(*(str++));
    }
    // End the resulted string
    b.At(n) = '\0';
    // Move the cursor to the end
    b.Move(n);
    // Return ownership of the buffer
    return std::move(b);
}

// ------------------------------------------------------------------------------------------------
CSStr StrToUppercase(CSStr str)
{
    // See if we actually have something to search for
    if(!str || *str == '\0')
    {
        return _SC(""); // Default to an empty string!
    }
    // Attempt to convert and return the result
    return StrToUppercaseB(str).Get< SQChar >();
}

// ------------------------------------------------------------------------------------------------
static SQInteger SqToUppercase(HSQUIRRELVM vm)
{
    // Attempt to retrieve the value from the stack as a string
    StackStrF val(vm, 2);
    // Have we failed to retrieve the string?
    if (SQ_FAILED(val.mRes))
    {
        return val.mRes; // Propagate the error!
    }
    // Forward the call to the actual implementation and store the buffer
    Buffer b(StrToUppercaseB(val.mPtr));
    // Is there anything in the buffer?
    if (!b)
    {
        // Default to an empty string
        sq_pushstring(vm, _SC(""), 0);
    }
    else
    {
        // Push the buffer data as a string
        sq_pushstring(vm, b.Data(), b.Position());
    }
    // We have a value to return on the stack
    return 1;
}

/* ------------------------------------------------------------------------------------------------
 * Known character classifications
*/
enum struct CharClass
{
    IsSpace = 0,
    IsPrint,
    IsCntrl,
    IsUpper,
    IsLower,
    IsAlpha,
    IsDigit,
    IsPunct,
    IsXdigit,
    IsAlnum,
    IsGraph,
    IsBlank
};

/* ------------------------------------------------------------------------------------------------
 * Specialization for the character classifications.
*/
template < CharClass > struct CharClassSpec;

// ------------------------------------------------------------------------------------------------
template <> struct CharClassSpec< CharClass::IsSpace >
{
    static inline int Fn(int c) { return std::isspace(c); }
    static inline bool Eq(int c) { return std::isspace(c) != 0; }
};

// ------------------------------------------------------------------------------------------------
template <> struct CharClassSpec< CharClass::IsPrint >
{
    static inline int Fn(int c) { return std::isprint(c); }
    static inline bool Eq(int c) { return std::isprint(c) != 0; }
};

// ------------------------------------------------------------------------------------------------
template <> struct CharClassSpec< CharClass::IsCntrl >
{
    static inline int Fn(int c) { return std::iscntrl(c); }
    static inline bool Eq(int c) { return std::iscntrl(c) != 0; }
};

// ------------------------------------------------------------------------------------------------
template <> struct CharClassSpec< CharClass::IsUpper >
{
    static inline int Fn(int c) { return std::isupper(c); }
    static inline bool Eq(int c) { return std::isupper(c) != 0; }
};

// ------------------------------------------------------------------------------------------------
template <> struct CharClassSpec< CharClass::IsLower >
{
    static inline int Fn(int c) { return std::islower(c); }
    static inline bool Eq(int c) { return std::islower(c) != 0; }
};

// ------------------------------------------------------------------------------------------------
template <> struct CharClassSpec< CharClass::IsAlpha >
{
    static inline int Fn(int c) { return std::isalpha(c); }
    static inline bool Eq(int c) { return std::isalpha(c) != 0; }
};

// ------------------------------------------------------------------------------------------------
template <> struct CharClassSpec< CharClass::IsDigit >
{
    static inline int Fn(int c) { return std::isdigit(c); }
    static inline bool Eq(int c) { return std::isdigit(c) != 0; }
};

// ------------------------------------------------------------------------------------------------
template <> struct CharClassSpec< CharClass::IsPunct >
{
    static inline int Fn(int c) { return std::ispunct(c); }
    static inline bool Eq(int c) { return std::ispunct(c) != 0; }
};

// ------------------------------------------------------------------------------------------------
template <> struct CharClassSpec< CharClass::IsXdigit >
{
    static inline int Fn(int c) { return std::isxdigit(c); }
    static inline bool Eq(int c) { return std::isxdigit(c) != 0; }
};

// ------------------------------------------------------------------------------------------------
template <> struct CharClassSpec< CharClass::IsAlnum >
{
    static inline int Fn(int c) { return std::isalnum(c); }
    static inline bool Eq(int c) { return std::isalnum(c) != 0; }
};

// ------------------------------------------------------------------------------------------------
template <> struct CharClassSpec< CharClass::IsGraph >
{
    static inline int Fn(int c) { return std::isgraph(c); }
    static inline bool Eq(int c) { return std::isgraph(c) != 0; }
};

// ------------------------------------------------------------------------------------------------
template <> struct CharClassSpec< CharClass::IsBlank >
{
    static inline int Fn(int c) { return std::isblank(c); }
    static inline bool Eq(int c) { return std::isblank(c) != 0; }
};

/* ------------------------------------------------------------------------------------------------
 * Templated functions for working with character classification functions.
*/
template < class T > struct StrCType
{
    /* --------------------------------------------------------------------------------------------
     * Checks if all the characters in the specified string are of the specified class.
    */
    static bool AllCharsImpl(CSStr str)
    {
        // See if we actually have something to search for
        if(!str || *str == '\0')
        {
            return false; // Subject to change!
        }
        // Start iterating characters and find the first that doesn't match
        while (*str != '\0')
        {
            // Call the predicate with the current character
            if (T::Fn(*(str++)) == 0)
            {
                return false; // This character did not pass the test
            }
        }
        // All characters passed the test
        return true;
    }

    /* --------------------------------------------------------------------------------------------
     * Extracts and forwards the string argument to the actual implementation.
    */
    static SQInteger AllChars(HSQUIRRELVM vm)
    {
        // Attempt to retrieve the value from the stack as a string
        StackStrF val(vm, 2);
        // Have we failed to retrieve the string?
        if (SQ_FAILED(val.mRes))
        {
            return val.mRes; // Propagate the error!
        }
        // Perform the actual check and push the result on the stack
        sq_pushbool(vm, AllCharsImpl(val.mPtr));
        // We have a value to return on the stack
        return 1;
    }

    /* --------------------------------------------------------------------------------------------
     * Find the position of the first character that matches the specified class.
    */
    static SQInteger FirstCharImpl(CSStr str)
    {
        // See if we actually have something to search for
        if(!str || *str == '\0')
        {
            return -1; // Subject to change!
        }
        // Start iterating characters and find the first that matches
        for (Uint32 n = 0; *str != '\0'; ++n, ++str)
        {
            // Call the predicate with the current character
            if (T::Fn(*str) != 0)
            {
                // This character passed our test
                return static_cast< SQInteger >(n);
            }
        }
        // Unable to locate such character
        return -1;
    }

    /* --------------------------------------------------------------------------------------------
     * Extracts and forwards the string argument to the actual implementation.
    */
    static SQInteger FirstChar(HSQUIRRELVM vm)
    {
        // Attempt to retrieve the value from the stack as a string
        StackStrF val(vm, 2);
        // Have we failed to retrieve the string?
        if (SQ_FAILED(val.mRes))
        {
            return val.mRes; // Propagate the error!
        }
        // Perform the actual check and store the resulted value
        SQInteger ret = FirstCharImpl(val.mPtr);
        // Have we found anything?
        if (ret >= 0)
        {
            sq_pushinteger(vm, ret);
        }
        else
        {
            sq_pushnull(vm);
        }
        // We have a value to return on the stack
        return 1;
    }

    /* --------------------------------------------------------------------------------------------
     * Find the position of the first character that doesn't match the specified class.
    */
    static SQInteger FirstNotCharImpl(CSStr str)
    {
        // See if we actually have something to search for
        if(!str || *str == '\0')
        {
            return -1; // Subject to change!
        }
        // Start iterating characters and find the first that doesn't match
        for (Uint32 n = 0; *str != '\0'; ++n, ++str)
        {
            // Call the predicate with the current character
            if (T::Fn(*str) == 0)
            {
                // This character passed our test
                return static_cast< SQInteger >(n);
            }
        }
        // Unable to locate such character
        return -1;
    }

    /* --------------------------------------------------------------------------------------------
     * Extracts and forwards the string argument to the actual implementation.
    */
    static SQInteger FirstNotChar(HSQUIRRELVM vm)
    {
        // Attempt to retrieve the value from the stack as a string
        StackStrF val(vm, 2);
        // Have we failed to retrieve the string?
        if (SQ_FAILED(val.mRes))
        {
            return val.mRes; // Propagate the error!
        }
        // Perform the actual check and store the resulted value
        SQInteger ret = FirstNotCharImpl(val.mPtr);
        // Have we found anything?
        if (ret >= 0)
        {
            sq_pushinteger(vm, ret);
        }
        else
        {
            sq_pushnull(vm);
        }
        // We have a value to return on the stack
        return 1;
    }

    /* --------------------------------------------------------------------------------------------
     * Find the position of the last character that matches the specified class.
    */
    static SQInteger LastCharImpl(CSStr str)
    {
        // See if we actually have something to search for
        if(!str || *str == '\0')
        {
            return -1; // Subject to change!
        }
        // Find the end of the string
        CSStr end = std::strchr(str, '\0') - 1;
        // Start iterating characters and find the first that matches
        for (; end >= str; --end)
        {
            // Call the predicate with the current character
            if (T::Fn(*end) != 0)
            {
                // This character passed our test
                return static_cast< SQInteger >(end - str);
            }
        }
        // Unable to locate such character
        return -1;
    }

    /* --------------------------------------------------------------------------------------------
     * Extracts and forwards the string argument to the actual implementation.
    */
    static SQInteger LastChar(HSQUIRRELVM vm)
    {
        // Attempt to retrieve the value from the stack as a string
        StackStrF val(vm, 2);
        // Have we failed to retrieve the string?
        if (SQ_FAILED(val.mRes))
        {
            return val.mRes; // Propagate the error!
        }
        // Perform the actual check and store the resulted value
        SQInteger ret = LastCharImpl(val.mPtr);
        // Have we found anything?
        if (ret >= 0)
        {
            sq_pushinteger(vm, ret);
        }
        else
        {
            sq_pushnull(vm);
        }
        // We have a value to return on the stack
        return 1;
    }

    /* --------------------------------------------------------------------------------------------
     * Find the position of the last character that doesn't match the specified class.
    */
    static SQInteger LastNotCharImpl(CSStr str)
    {
        // See if we actually have something to search for
        if(!str || *str == '\0')
        {
            return -1; // Subject to change!
        }
        // Find the end of the string
        CSStr end = std::strchr(str, '\0') - 1;
        // Start iterating characters and find the first that matches
        for (; end >= str; --end)
        {
            // Call the predicate with the current character
            if (T::Fn(*end) == 0)
            {
                // This character passed our test
                return static_cast< SQInteger >(end - str);
            }
        }
        // Unable to locate such character
        return -1;
    }

    /* --------------------------------------------------------------------------------------------
     * Extracts and forwards the string argument to the actual implementation.
    */
    static SQInteger LastNotChar(HSQUIRRELVM vm)
    {
        // Attempt to retrieve the value from the stack as a string
        StackStrF val(vm, 2);
        // Have we failed to retrieve the string?
        if (SQ_FAILED(val.mRes))
        {
            return val.mRes; // Propagate the error!
        }
        // Perform the actual check and store the resulted value
        SQInteger ret = LastNotCharImpl(val.mPtr);
        // Have we found anything?
        if (ret >= 0)
        {
            sq_pushinteger(vm, ret);
        }
        else
        {
            sq_pushnull(vm);
        }
        // We have a value to return on the stack
        return 1;
    }

};

// ------------------------------------------------------------------------------------------------
static bool OnlyDelimiter(CSStr str, SQChar chr)
{
    while (*str != '\0')
    {
        // Is this different from the delimiter?
        if (*(str++) != chr)
        {
            // Another character was found
            return false;
        }
    }
    // No other character was found
    return true;
}

// ------------------------------------------------------------------------------------------------
static SQInteger SqStrExplode(HSQUIRRELVM vm)
{
    const Int32 top = sq_gettop(vm);
    // Was the delimiter character specified?
    if (top <= 1)
    {
        return sq_throwerror(vm, _SC("Missing delimiter character"));
    }
    // Was the boolean empty specified?
    else if (top <= 2)
    {
        return sq_throwerror(vm, _SC("Missing boolean empty"));
    }
    // Was the string value specified?
    else if (top <= 3)
    {
        return sq_throwerror(vm, _SC("Missing string value"));
    }
    // Attempt to generate the string value
    StackStrF val(vm, 4);
    // Have we failed to retrieve the string?
    if (SQ_FAILED(val.mRes))
    {
        return val.mRes; // Propagate the error!
    }
    // The delimiter character and boolean empty
    SQChar delim = 0;
    bool empty = 0;
    // Attempt to retrieve the remaining arguments from the stack
    try
    {
        delim = Var< SQChar >(vm, 2).value;
        empty = Var< bool >(vm, 3).value;
    }
    catch (const Sqrat::Exception & e)
    {
        return sq_throwerror(vm, e.what());
    }
    catch (...)
    {
        return sq_throwerror(vm, _SC("Unable to retrieve arguments"));
    }
    // Grab the string value to a shorter name
    CSStr str = val.mPtr;
    // Create an empty array on the stack
    sq_newarray(vm, 0);
    // See if we actually have something to explode
    if(!str || *str == '\0')
    {
        // Specify that we have an argument on the stack
        return 1;
    }
    // Don't modify the specified string pointer
    CSStr itr = str, last = str;
    // The number of delimiter occurrences
    Uint32 num = 0;
    // Pre-count how many delimiters of this type exist
    while (*itr != '\0')
    {
        // Is this our delimiter?
        if (*(itr++) == delim)
        {
            // Are we allowed to include empty elements?
            if (empty || (itr - last) > 1)
            {
                // Increase the count
                ++num;
            }
            // Update the last delimiter position
            last = itr;
        }
    }
    // Were there no delimiters found and can we include empty elements?
    if (num == 0 && !empty && (str[1] == '\0' || OnlyDelimiter(str, delim)))
    {
        // Specify that we have an argument on the stack
        return 1;
    }
    // Have we found any delimiters?
    else if (num == 0)
    {
        // Test against strings with only delimiters
        if (str[1] == '\0' || OnlyDelimiter(str, delim))
        {
            sq_pushstring(vm, _SC(""), 0); // Add an empty string
        }
        else
        {
            sq_pushstring(vm, val.mPtr, val.mLen); // Add the whole string
        }
        // Append the string on the stack to the array
        const SQRESULT r = sq_arrayappend(vm, -2);
        // Check the result
        if (SQ_FAILED(r))
        {
            return r; // Propagate the error
        }
        // Specify that we have an argument on the stack
        return 1;
    }
    // Is there anything after the last delimiter?
    if (itr != last && *last != delim)
    {
        ++num; // Add it to the counter
    }
    SQRESULT r = SQ_OK;
    // Pre-allocate an array with the number of found delimiters
    r = sq_arrayresize(vm, -1, num);
    // Check the result
    if (SQ_FAILED(r))
    {
        return r; // Propagate the error
    }
    // Don't modify the specified string pointer
    itr = str, last = str;
    // Reset the counter and use it as the element index
    num = 0;
    // Process the string again, this time slicing the actual elements
    while (*itr != '\0')
    {
        // Is this our delimiter?
        if (*itr++ == delim)
        {
            // Are we allowed to include empty elements?
            if (empty || (itr - last) > 1)
            {
                // Push the element index on the stack and advance to the next one
                sq_pushinteger(vm, num++);
                // Push the string portion on the stack
                sq_pushstring(vm, last, itr - last - 1);
                // Assign the string onto the
                r = sq_set(vm, -3);
                // Check the result
                if (SQ_FAILED(r))
                {
                    return r; // Propagate the error
                }
            }
            // Update the last delimiter position
            last = itr;
        }
    }
    // Is there anything after the last delimiter?
    if (itr != last && *last != delim)
    {
        // Push the element index on the stack
        sq_pushinteger(vm, num);
        // Add the remaining string as an element
        sq_pushstring(vm, last, itr - last);
        // Assign the string onto the
        r = sq_set(vm, -3);
        // Check the result
        if (SQ_FAILED(r))
        {
            return r; // Propagate the error
        }
    }
    // Specify that we have an argument on the stack
    return 1;
}

// ------------------------------------------------------------------------------------------------
static CSStr StrImplode(SQChar chr, Array & arr)
{
    // Determine array size
    const Int32 length = static_cast< Int32 >(arr.Length());
    // Is there anything to implode?
    if (length <= 0)
    {
        return _SC(""); // Default to an empty string
    }
    // Obtain a temporary buffer
    Buffer b(length * 32);
    // Process array elements
    for (SQInteger i = 0; i < length; ++i)
    {
        // Retrieve the element value as string
        SharedPtr< String > str = arr.GetValue< String >(i);
        // Was there any value retrieved?
        if (!!str)
        {
            // Append the value to the buffer
            b.AppendS(str->data(), str->size());
        }
        // Append the delimiter
        b.Push(chr);
    }
    // Move the cursor back one element
    b.Retreat(1);
    // Set that as the null character
    b.Cursor() = '\0';
    // Return the string
    return b.Get< SQChar >();
}

// ------------------------------------------------------------------------------------------------
static CSStr FromArray(Array & arr)
{
    // Determine array size
    const Int32 length = ConvTo< Int32 >::From(arr.Length());
    // Obtain a temporary buffer
    Buffer b(length * sizeof(Int32));
    // Get array elements as integers
    arr.GetArray< Int32 >(b.Get< Int32 >(), length);
    // Overwrite integers with characters
    for (Int32 n = 0; n < length; ++n)
    {
        b.At(n) = ConvTo< SQChar >::From(b.At< Int32 >(n * sizeof(Int32)));
    }
    // Terminate the resulted string
    b.At(length) = '\0';
    // Return the string
    return b.Get< SQChar >();
}

// ------------------------------------------------------------------------------------------------
static SQInteger StdPrintF(HSQUIRRELVM vm)
{
    // Attempt to retrieve the value from the stack as a string
    StackStrF val(vm, 2);
    // Have we failed to retrieve the string?
    if (SQ_FAILED(val.mRes))
    {
        return val.mRes; // Propagate the error!
    }
    // Send the resulted string to console as a user message
    LogUsr("%s", val.mPtr);
    // This function doesn't return anything
    return 0;
}

// ================================================================================================
void Register_String(HSQUIRRELVM vm)
{
    Table strns(vm);

    strns.Func(_SC("FromArray"), &FromArray)
    .SquirrelFunc(_SC("Explode"), &SqStrExplode)
    .Func(_SC("Implode"), &StrImplode)
    .SquirrelFunc(_SC("Center"), &SqCenterStr)
    .SquirrelFunc(_SC("Left"), &SqLeftStr)
    .SquirrelFunc(_SC("Right"), &SqRightStr)
    .SquirrelFunc(_SC("LeftOffset"), &SqLeftOffsetStr)
    .SquirrelFunc(_SC("RightOffset"), &SqRightOffsetStr)
    .SquirrelFunc(_SC("ToLower"), &SqToLowercase)
    .SquirrelFunc(_SC("ToUpper"), &SqToUppercase)
    .SquirrelFunc(_SC("Lowercase"), &SqToLowercase)
    .SquirrelFunc(_SC("Uppercase"), &SqToUppercase)
    .SquirrelFunc(_SC("JustAlnum"), &SqJustAlphaNum)
    .SquirrelFunc(_SC("AreAllSpace"), &StrCType< CharClassSpec< CharClass::IsSpace > >::AllChars)
    .SquirrelFunc(_SC("AreAllPrint"), &StrCType< CharClassSpec< CharClass::IsPrint > >::AllChars)
    .SquirrelFunc(_SC("AreAllCntrl"), &StrCType< CharClassSpec< CharClass::IsCntrl > >::AllChars)
    .SquirrelFunc(_SC("AreAllUpper"), &StrCType< CharClassSpec< CharClass::IsUpper > >::AllChars)
    .SquirrelFunc(_SC("AreAllLower"), &StrCType< CharClassSpec< CharClass::IsLower > >::AllChars)
    .SquirrelFunc(_SC("AreAllAlpha"), &StrCType< CharClassSpec< CharClass::IsAlpha > >::AllChars)
    .SquirrelFunc(_SC("AreAllDigit"), &StrCType< CharClassSpec< CharClass::IsDigit > >::AllChars)
    .SquirrelFunc(_SC("AreAllPunct"), &StrCType< CharClassSpec< CharClass::IsPunct > >::AllChars)
    .SquirrelFunc(_SC("AreAllXdigit"), &StrCType< CharClassSpec< CharClass::IsXdigit > >::AllChars)
    .SquirrelFunc(_SC("AreAllAlnum"), &StrCType< CharClassSpec< CharClass::IsAlnum > >::AllChars)
    .SquirrelFunc(_SC("AreAllGraph"), &StrCType< CharClassSpec< CharClass::IsGraph > >::AllChars)
    .SquirrelFunc(_SC("AreAllBlank"), &StrCType< CharClassSpec< CharClass::IsBlank > >::AllChars)
    .SquirrelFunc(_SC("FirstSpace"), &StrCType< CharClassSpec< CharClass::IsSpace > >::FirstChar)
    .SquirrelFunc(_SC("FirstPrint"), &StrCType< CharClassSpec< CharClass::IsPrint > >::FirstChar)
    .SquirrelFunc(_SC("FirstCntrl"), &StrCType< CharClassSpec< CharClass::IsCntrl > >::FirstChar)
    .SquirrelFunc(_SC("FirstUpper"), &StrCType< CharClassSpec< CharClass::IsUpper > >::FirstChar)
    .SquirrelFunc(_SC("FirstLower"), &StrCType< CharClassSpec< CharClass::IsLower > >::FirstChar)
    .SquirrelFunc(_SC("FirstAlpha"), &StrCType< CharClassSpec< CharClass::IsAlpha > >::FirstChar)
    .SquirrelFunc(_SC("FirstDigit"), &StrCType< CharClassSpec< CharClass::IsDigit > >::FirstChar)
    .SquirrelFunc(_SC("FirstPunct"), &StrCType< CharClassSpec< CharClass::IsPunct > >::FirstChar)
    .SquirrelFunc(_SC("FirstXdigit"), &StrCType< CharClassSpec< CharClass::IsXdigit > >::FirstChar)
    .SquirrelFunc(_SC("FirstAlnum"), &StrCType< CharClassSpec< CharClass::IsAlnum > >::FirstChar)
    .SquirrelFunc(_SC("FirstGraph"), &StrCType< CharClassSpec< CharClass::IsGraph > >::FirstChar)
    .SquirrelFunc(_SC("FirstBlank"), &StrCType< CharClassSpec< CharClass::IsBlank > >::FirstChar)
    .SquirrelFunc(_SC("FirstNotSpace"), &StrCType< CharClassSpec< CharClass::IsSpace > >::FirstNotChar)
    .SquirrelFunc(_SC("FirstNotPrint"), &StrCType< CharClassSpec< CharClass::IsPrint > >::FirstNotChar)
    .SquirrelFunc(_SC("FirstNotCntrl"), &StrCType< CharClassSpec< CharClass::IsCntrl > >::FirstNotChar)
    .SquirrelFunc(_SC("FirstNotUpper"), &StrCType< CharClassSpec< CharClass::IsUpper > >::FirstNotChar)
    .SquirrelFunc(_SC("FirstNotLower"), &StrCType< CharClassSpec< CharClass::IsLower > >::FirstNotChar)
    .SquirrelFunc(_SC("FirstNotAlpha"), &StrCType< CharClassSpec< CharClass::IsAlpha > >::FirstNotChar)
    .SquirrelFunc(_SC("FirstNotDigit"), &StrCType< CharClassSpec< CharClass::IsDigit > >::FirstNotChar)
    .SquirrelFunc(_SC("FirstNotPunct"), &StrCType< CharClassSpec< CharClass::IsPunct > >::FirstNotChar)
    .SquirrelFunc(_SC("FirstNotXdigit"), &StrCType< CharClassSpec< CharClass::IsXdigit > >::FirstNotChar)
    .SquirrelFunc(_SC("FirstNotAlnum"), &StrCType< CharClassSpec< CharClass::IsAlnum > >::FirstNotChar)
    .SquirrelFunc(_SC("FirstNotGraph"), &StrCType< CharClassSpec< CharClass::IsGraph > >::FirstNotChar)
    .SquirrelFunc(_SC("FirstNotBlank"), &StrCType< CharClassSpec< CharClass::IsBlank > >::FirstNotChar)
    .SquirrelFunc(_SC("LastSpace"), &StrCType< CharClassSpec< CharClass::IsSpace > >::LastChar)
    .SquirrelFunc(_SC("LastPrint"), &StrCType< CharClassSpec< CharClass::IsPrint > >::LastChar)
    .SquirrelFunc(_SC("LastCntrl"), &StrCType< CharClassSpec< CharClass::IsCntrl > >::LastChar)
    .SquirrelFunc(_SC("LastUpper"), &StrCType< CharClassSpec< CharClass::IsUpper > >::LastChar)
    .SquirrelFunc(_SC("LastLower"), &StrCType< CharClassSpec< CharClass::IsLower > >::LastChar)
    .SquirrelFunc(_SC("LastAlpha"), &StrCType< CharClassSpec< CharClass::IsAlpha > >::LastChar)
    .SquirrelFunc(_SC("LastDigit"), &StrCType< CharClassSpec< CharClass::IsDigit > >::LastChar)
    .SquirrelFunc(_SC("LastPunct"), &StrCType< CharClassSpec< CharClass::IsPunct > >::LastChar)
    .SquirrelFunc(_SC("LastXdigit"), &StrCType< CharClassSpec< CharClass::IsXdigit > >::LastChar)
    .SquirrelFunc(_SC("LastAlnum"), &StrCType< CharClassSpec< CharClass::IsAlnum > >::LastChar)
    .SquirrelFunc(_SC("LastGraph"), &StrCType< CharClassSpec< CharClass::IsGraph > >::LastChar)
    .SquirrelFunc(_SC("LastBlank"), &StrCType< CharClassSpec< CharClass::IsBlank > >::LastChar)
    .SquirrelFunc(_SC("LastNotSpace"), &StrCType< CharClassSpec< CharClass::IsSpace > >::LastNotChar)
    .SquirrelFunc(_SC("LastNotPrint"), &StrCType< CharClassSpec< CharClass::IsPrint > >::LastNotChar)
    .SquirrelFunc(_SC("LastNotCntrl"), &StrCType< CharClassSpec< CharClass::IsCntrl > >::LastNotChar)
    .SquirrelFunc(_SC("LastNotUpper"), &StrCType< CharClassSpec< CharClass::IsUpper > >::LastNotChar)
    .SquirrelFunc(_SC("LastNotLower"), &StrCType< CharClassSpec< CharClass::IsLower > >::LastNotChar)
    .SquirrelFunc(_SC("LastNotAlpha"), &StrCType< CharClassSpec< CharClass::IsAlpha > >::LastNotChar)
    .SquirrelFunc(_SC("LastNotDigit"), &StrCType< CharClassSpec< CharClass::IsDigit > >::LastNotChar)
    .SquirrelFunc(_SC("LastNotPunct"), &StrCType< CharClassSpec< CharClass::IsPunct > >::LastNotChar)
    .SquirrelFunc(_SC("LastNotXdigit"), &StrCType< CharClassSpec< CharClass::IsXdigit > >::LastNotChar)
    .SquirrelFunc(_SC("LastNotAlnum"), &StrCType< CharClassSpec< CharClass::IsAlnum > >::LastNotChar)
    .SquirrelFunc(_SC("LastNotGraph"), &StrCType< CharClassSpec< CharClass::IsGraph > >::LastNotChar)
    .SquirrelFunc(_SC("LastNotBlank"), &StrCType< CharClassSpec< CharClass::IsBlank > >::LastNotChar);

    RootTable(vm).Bind(_SC("SqStr"), strns);
    RootTable(vm).SquirrelFunc(_SC("printf"), &StdPrintF);

    RootTable(vm)
    .Func(_SC("IsSpace"), &CharClassSpec< CharClass::IsSpace >::Eq)
    .Func(_SC("IsPrint"), &CharClassSpec< CharClass::IsPrint >::Eq)
    .Func(_SC("IsCntrl"), &CharClassSpec< CharClass::IsCntrl >::Eq)
    .Func(_SC("IsUpper"), &CharClassSpec< CharClass::IsUpper >::Eq)
    .Func(_SC("IsLower"), &CharClassSpec< CharClass::IsLower >::Eq)
    .Func(_SC("IsAlpha"), &CharClassSpec< CharClass::IsAlpha >::Eq)
    .Func(_SC("IsDigit"), &CharClassSpec< CharClass::IsDigit >::Eq)
    .Func(_SC("IsPunct"), &CharClassSpec< CharClass::IsPunct >::Eq)
    .Func(_SC("IsXdigit"), &CharClassSpec< CharClass::IsXdigit >::Eq)
    .Func(_SC("IsAlnum"), &CharClassSpec< CharClass::IsAlnum >::Eq)
    .Func(_SC("IsGraph"), &CharClassSpec< CharClass::IsGraph >::Eq)
    .Func(_SC("IsBlank"), &CharClassSpec< CharClass::IsBlank >::Eq)
    .Func(_SC("ToLower"), &tolower)
    .Func(_SC("ToUpper"), &toupper);
}

} // Namespace:: SqMod