#include "Command.hpp"
#include "Register.hpp"
#include "Core.hpp"

// ------------------------------------------------------------------------------------------------
#include <cstdlib>

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

// ------------------------------------------------------------------------------------------------
const CmdManager::Pointer _Cmd = CmdManager::Inst();

// ------------------------------------------------------------------------------------------------
void CmdManager::_Finalizer(CmdManager * ptr)
{
    delete ptr; /* Assuming 'delete' checks for NULL */
}

// ------------------------------------------------------------------------------------------------
CmdManager::Pointer CmdManager::Inst()
{
    if (!_Cmd)
    {
        return Pointer(new CmdManager(), &CmdManager::_Finalizer);
    }

    return Pointer(nullptr, &CmdManager::_Finalizer);
}

// ------------------------------------------------------------------------------------------------
CmdManager::CmdManager()
    : m_Commands(), m_Invoker(SQMOD_UNKNOWN), m_Argv()
{
    /* ... */
}

// ------------------------------------------------------------------------------------------------
CmdManager::~CmdManager()
{
    /* ... */
}

// ------------------------------------------------------------------------------------------------
void CmdManager::VMClose()
{
    // Release the reference to the specified callbacks
    m_OnError.Release2();
    // We must not keep arguments
    m_Argv.fill(CmdArgs::value_type(CMDARG_ANY, NullData()));
    // Reset the argument counter
    m_Argc = 0;
}

// ------------------------------------------------------------------------------------------------
void CmdManager::Bind(const String & name, CmdListener * cmd)
{
    // If it doesn't exist then it will be created
    m_Commands[name] = cmd;
}

// ------------------------------------------------------------------------------------------------
void CmdManager::Unbind(const String & name)
{
    m_Commands.erase(name);
}

// ------------------------------------------------------------------------------------------------
Function & CmdManager::GetOnError()
{
    return m_OnError;
}

// ------------------------------------------------------------------------------------------------
void CmdManager::SetOnError(Function & func)
{
    m_OnError = func;
}

// ------------------------------------------------------------------------------------------------
Function & CmdManager::GetOnAuth()
{
    return m_OnAuth;
}

// ------------------------------------------------------------------------------------------------
void CmdManager::SetOnAuth(Function & func)
{
    m_OnAuth = func;
}

// ------------------------------------------------------------------------------------------------
const CPlayer & CmdManager::GetInvoker() const
{
    return m_Invoker;
}

// ------------------------------------------------------------------------------------------------
SQInt32 CmdManager::GetInvokerID() const
{
    return m_Invoker;
}

// ------------------------------------------------------------------------------------------------
const SQChar * CmdManager::GetName()
{
    return m_Name.c_str();
}

// ------------------------------------------------------------------------------------------------
const SQChar * CmdManager::GetText()
{
    return m_Text.c_str();
}

// ------------------------------------------------------------------------------------------------
void CmdManager::Execute(SQInt32 invoker, const String & str)
{
    // Save the invoker identifier
    m_Invoker.SetID(invoker);
    // Find where the command name ends
    String::size_type split_pos = str.find_first_of(' ');
    // Attempt to separate the command name from arguments
    if (split_pos != String::npos)
    {
        m_Name = str.substr(0, split_pos);
        m_Text = str.substr(split_pos+1);
    }
    // No arguments specified
    else
    {
        m_Name = str;
        m_Text = _SC("");
    }
    // Attempt to find a command with this name
    CmdPool::iterator itr = m_Commands.find(m_Name);
    // See if a command with this name could be found
    if (itr != m_Commands.end())
    {
        // Place a lock on the command
        itr->second->m_Lock = true;
        // Attempt to execute the command
        Exec(*itr->second);
        // Take the lock from the command
        itr->second->m_Lock = false;
    }
    else
    {
        Error(CMDERR_UNKNOWN_COMMAND, _SC("No such command exists: %s"), m_Name.c_str());
    }
}

// ------------------------------------------------------------------------------------------------
void CmdManager::Exec(CmdListener & cmd)
{
    // See if the invoker has enough authority to execute this command
    if (!cmd.AuthCheck(m_Invoker))
    {
        Error(CMDERR_INSUFFICIENT_AUTH, _SC("Insufficient authority to execute command"));
        // Command failed
        return;
    }
    // See if an executer was specified
    if (cmd.GetOnExec().IsNull())
    {
        Error(CMDERR_MISSING_EXECUTER, _SC("No executer was specified for this command"));
        // Command failed
        return;
    }
    // See if there are any arguments to parse
    if (!m_Text.empty() && !Parse(cmd.GetMaxArgC()))
    {
        // The error message was reported while parsing
        return;
    }
    // See if we have enough arguments specified
    if (cmd.GetMinArgC() > m_Argc)
    {
        Error(CMDERR_INCOMPLETE_ARGS, _SC("Incomplete command arguments: %u < %u"),
                                            m_Argc, cmd.GetMinArgC());
        // Command failed
        return;
    }
    // The check during the parsing may not count the last argument
    else if (cmd.GetMaxArgC() < m_Argc)
    {
        Error(CMDERR_EXTRANEOUS_ARGS, _SC("Extraneous command arguments: %u < %u"),
                                    cmd.GetMaxArgC(), m_Argc);
        // Command failed
        return;
    }
    // Check argument types against the command specifiers
    for (Uint32 arg = 0; arg < m_Argc; ++arg)
    {
        if (!cmd.ArgCheck(arg, m_Argv[arg].first))
        {
            Error(CMDERR_UNSUPPORTED_ARG, _SC("Unsupported command argument: %u -> %s"),
                                    arg, CmdArgSpecToStr(m_Argv[arg].first));
            // Command failed
            return;
        }
    }
    // Reserve an array for the extracted arguments
    Array args(DefaultVM::Get(), m_Argc);
    // Copy the arguments into the array
    for (SQUint32 arg = 0; arg < m_Argc; ++arg)
    {
        args.Bind(arg, m_Argv[arg].second);
    }
    // Attempt to execute the command with the specified arguments
    bool result = cmd.Execute(m_Invoker, args);
    // See if an error occurred
    if (Error::Occurred(DefaultVM::Get()))
    {
        Error(CMDERR_EXECUTION_FAILED, _SC("Command execution failed: %s"), Error::Message(DefaultVM::Get()).c_str());
    }
    // See if the command failed
    else if (!result)
    {
        Error(CMDERR_EXECUTION_FAILED, _SC("Command execution failed"));
    }
}

// ------------------------------------------------------------------------------------------------
bool CmdManager::Parse(SQUint32 max)
{
    // Clear previous arguments
    m_Argv.fill(CmdArgs::value_type(CMDARG_ANY, NullData()));
    // Reset the argument counter
    m_Argc = 0;
    // Request a temporary buffer
    Buffer buffer = _Core->PullBuffer(128);
    // Internal start and end of the temporary buffer
    SQChar * cur = nullptr, * end = nullptr;
    // The pointer to the currently processed character
    const SQChar * str = m_Text.c_str();
    // Currently and previously processed character
    SQChar ch = 0, pr = 0;
    // When parsing may continue
    bool good = true;
    // Process the specified command text
    while (good)
    {
        // Extract the current character before advancing
        ch = *(str++);
        // See if there's anything left to parse
        if (ch == 0)
        {
            // Finished parsing
            break;
        }
        // Early check to prevent parsing unneeded arguments
        else if (max < m_Argc)
        {
            Error(CMDERR_EXTRANEOUS_ARGS, _SC("Extraneous command arguments: %u < %u"),
                                                max, m_Argc);
            // Parsing failed
            good = false;
            // Stop parsing
            break;
        }
        // See if we have to extract a string argument
        else if ((ch == '\'' || ch == '"') && pr != '\\')
        {
            // Obtain the internal beginning and ending of the temporary buffer
            cur = buffer.Begin(), end = (buffer.End()-1); /* + null */
            // Attempt to consume the string argument
            while (good)
            {
                // Extract the current character before advancing
                ch = *(str++);
                // See if there's anything left to parse
                if (ch == 0)
                {
                    Error(CMDERR_SYNTAX_ERROR, _SC("String not closed properly near command argument: %u"), m_Argc);
                    // Parsing failed
                    good = false;
                    // Stop parsing
                    break;
                }
                // First un-escaped single or double quote character ends argument
                if (ch == '\'' || ch == '"')
                {
                    // Was this not escaped?
                    if (pr != '\\')
                    {
                        // Terminate the string value in the temporary buffer
                        *cur = 0;
                        // Stop parsing
                        break;
                    }
                    else
                    {
                        // Overwrite last character when replicating
                        --cur;
                    }
                }
                // See if the temporary buffer needs to scale
                else if  (cur == end)
                {
                    // Attempt to increase the size of the temporary buffer
                    buffer.Increase(512);
                    // Obtain the internal beginning and ending of the temporary buffer again
                    cur = (buffer.Begin() + (buffer.Size()-512)), end = (buffer.End()-1);  /* + null */
                }
                // Simply replicate the character to the temporary buffer
                *(cur++) = ch;
                // Save current argument as the previous one
                pr = ch;
            }
            // See if the argument was valid
            if (!good)
            {
                // Propagate failure
                break;
            }
            // Transform it into a squirrel object
            sq_pushstring(DefaultVM::Get(), buffer.Data(), cur - buffer.Begin());
            // Get the object from the squirrel VM
            Var< SqObj > var(DefaultVM::Get(), -1);
            // Pop the created object from the stack
            if (!var.value.IsNull())
            {
                sq_pop(DefaultVM::Get(), 1);
            }
            // Add it to the argument list along with it's type
            m_Argv[m_Argc].first = CMDARG_STRING;
            m_Argv[m_Argc].second = var.value;
            // Move to the next argument
            ++m_Argc;
        }
        // Ignore space until another valid argument is found
        else if (ch != ' ' && (pr == ' ' || pr == 0))
        {
            // Obtain the internal beginning and ending of the temporary buffer
            cur = buffer.Begin(), end = (buffer.End()-1); /* + null */
            // Move back to the previous character before extracting
            --str;
            // Attempt to consume the argument value
            while (good)
            {
                // Extract the current character before advancing
                ch = *(str++);
                // See if there's anything left to parse
                if (ch == 0)
                {
                    // Move to the previous character so the main loop can stop
                    --str;
                    // Argument ended
                    break;
                }
                else if (ch == ' ')
                {
                    // Argument ended
                    break;
                }
                // See if the buffer needs to scale
                else if  (cur == end)
                {
                    // Attempt to increase the size of the temporary buffer
                    buffer.Increase(512);
                    // Obtain the internal beginning and ending of the temporary buffer again
                    cur = buffer.Begin() + (buffer.Size()-512), end = (buffer.End()-1); /* + null */
                }
                // Simply replicate the character to the temporary buffer
                *(cur++) = ch;
                // Save current argument as the previous one
                pr = ch;
            }
            // Terminate the string value in the temporary buffer
            *cur = 0;
            // Used to exclude all other checks when a valid type was found
            bool found = false;
            // Attempt to treat the value as an integer number
            if (!found)
            {
                SQChar * next = NULL;
                // Attempt to extract the integer value
                Int64 value = strtol(buffer.Data(), &next, 10);
                // See if this whole string was an integer
                if (next == cur)
                {
                    // Transform it into a squirrel object
                    Var< SQInteger >::push(DefaultVM::Get(), _SCSQI(value));
                    // Get the object from the squirrel VM
                    Var< SqObj > var(DefaultVM::Get(), -1);
                    // Pop the created object from the stack
                    if (!var.value.IsNull())
                    {
                        sq_pop(DefaultVM::Get(), 1);
                    }
                    // Add it to the argument list along with it's type
                    m_Argv[m_Argc].first = CMDARG_INTEGER;
                    m_Argv[m_Argc].second = var.value;
                    // We've found the correct value
                    found = true;
                }
            }
            // Attempt to treat the value as a float number
            if (!found)
            {
                SQChar * next = 0;
                // Attempt to extract the floating point value
                Float64 value = strtod(buffer.Data(), &next);
                // See if this whole string was a float value
                if (next == cur)
                {
                    // Transform it into a squirrel object
                    Var< SQFloat >::push(DefaultVM::Get(), _SCSQF(value));
                    // Get the object from the squirrel VM
                    Var< SqObj > var(DefaultVM::Get(), -1);
                    // Pop the created object from the stack
                    if (!var.value.IsNull())
                    {
                        sq_pop(DefaultVM::Get(), 1);
                    }
                    // Add it to the argument list along with it's type
                    m_Argv[m_Argc].first = CMDARG_FLOAT;
                    m_Argv[m_Argc].second = var.value;
                    // We've found the correct value
                    found = true;
                }
            }
            // Attempt to treat the value as a boolean
            if (!found)
            {
                if (strcmp(buffer.Data(), "true") == 0 || strcmp(buffer.Data(), "on") == 0)
                {
                    // Transform it into a squirrel object
                    Var< bool >::push(DefaultVM::Get(), true);
                    // Get the object from the squirrel VM
                    Var< SqObj > var(DefaultVM::Get(), -1);
                    // Pop the created object from the stack
                    if (!var.value.IsNull())
                    {
                        sq_pop(DefaultVM::Get(), 1);
                    }
                    // Add it to the argument list along with it's type
                    m_Argv[m_Argc].first = CMDARG_BOOLEAN;
                    m_Argv[m_Argc].second = var.value;
                    // We've found the correct value
                    found = true;
                }
                else if (strcmp(buffer.Data(), "false") == 0 || strcmp(buffer.Data(), "off") == 0)
                {
                    // Transform it into a squirrel object
                    Var< bool >::push(DefaultVM::Get(), false);
                    // Get the object from the squirrel VM
                    Var< SqObj > var(DefaultVM::Get(), -1);
                    // Pop the created object from the stack
                    if (!var.value.IsNull())
                    {
                        sq_pop(DefaultVM::Get(), 1);
                    }
                    // Add it to the argument list along with it's type
                    m_Argv[m_Argc].first = CMDARG_BOOLEAN;
                    m_Argv[m_Argc].second = var.value;
                    // We've found the correct value
                    found = true;
                }
            }
            // If everything else failed then simply treat the value as a string
            if (!found)
            {
                // Transform it into a squirrel object
                Var< const SQChar * >::push(DefaultVM::Get(), buffer.Data(), (cur - buffer.Begin()));
                // Get the object from the squirrel VM
                Var< SqObj > var(DefaultVM::Get(), -1);
                // Pop the created object from the stack
                if (!var.value.IsNull())
                {
                    sq_pop(DefaultVM::Get(), 1);
                }
                // Add it to the argument list along with it's type
                m_Argv[m_Argc].first = CMDARG_STRING;
                m_Argv[m_Argc].second = var.value;
            }
            // Move to the next argument
            ++m_Argc;
        }
        // Save current argument as the previous one
        pr = ch;
    }
    // Return the borrowed buffer
    _Core->PushBuffer(std::move(buffer));
    // Return whether the parsing was successful
    return good;
}

// ------------------------------------------------------------------------------------------------
CmdListener::CmdListener()
    : CmdListener(_SC(""), _SC(""))
{
    /* ... */
}

// ------------------------------------------------------------------------------------------------
CmdListener::CmdListener(const SQChar * name)
    : CmdListener(name, _SC(""), NullArray(), 0, MAX_CMD_ARGS)
{
    /* ... */
}

// ------------------------------------------------------------------------------------------------
CmdListener::CmdListener(const SQChar * name, const SQChar * spec)
    : CmdListener(name, spec, NullArray(), 0, MAX_CMD_ARGS)
{
    /* ... */
}

// ------------------------------------------------------------------------------------------------
CmdListener::CmdListener(const SQChar * name, const SQChar * spec, Array & tags)
    : CmdListener(name, spec, tags, 0, MAX_CMD_ARGS)
{
    /* ... */
}

// ------------------------------------------------------------------------------------------------
CmdListener::CmdListener(const SQChar * name, const SQChar * spec, Uint8 min, Uint8 max)
    : CmdListener(name, spec, NullArray(), min, max)
{
    /* ... */
}

// ------------------------------------------------------------------------------------------------
CmdListener::CmdListener(const SQChar * name, const SQChar * spec, Array & tags, Uint8 min, Uint8 max)
    : m_Args({{CMDARG_ANY}})
    , m_Argt({{_SC("")}})
    , m_MinArgc(0)
    , m_MaxArgc(MAX_CMD_ARGS)
    , m_Name()
    , m_Spec()
    , m_Help()
    , m_Info()
    , m_OnExec()
    , m_OnAuth(_Cmd->GetOnAuth())
    , m_Tag()
    , m_Data()
    , m_Level(SQMOD_UNKNOWN)
    , m_Authority(false)
    , m_Suspended(false)
    , m_Lock(false)
{
    // Set the minimum and maximum allowed arguments
    SetMinArgC(min);
    SetMaxArgC(max);
    // Extract the specified argument tags
    SetArgTags(tags);
    // Bind to the specified command name
    SetName(name);
    // Apply the specified argument rules
    SetSpec(spec);
    // Receive notification when the VM is about to be closed to release object references
    _Core->VMClose.Connect< CmdListener, &CmdListener::VMClose >(this);
}

// ------------------------------------------------------------------------------------------------
CmdListener::~CmdListener()
{
    // Stop receiving notification when the VM is about to be closed
    _Core->VMClose.Disconnect< CmdListener, &CmdListener::VMClose >(this);
}

// ------------------------------------------------------------------------------------------------
void CmdListener::VMClose()
{
    // Release the reference to the specified callbacks
    m_OnAuth.Release2();
    m_OnExec.Release2();
    // Release the reference to the specified user data
    m_Data.Release();
}

// ------------------------------------------------------------------------------------------------
SQInt32 CmdListener::Cmp(const CmdListener & o) const
{
    if (m_Name == o.m_Name)
    {
        return 0;
    }
    else if (m_Name.size() > o.m_Name.size())
    {
        return 1;
    }
    else
    {
        return -1;
    }
}

// ------------------------------------------------------------------------------------------------
const SQChar * CmdListener::ToString() const
{
    return m_Name.c_str();
}

// ------------------------------------------------------------------------------------------------
const SQChar * CmdListener::GetTag() const
{
    return m_Tag.c_str();
}

// ------------------------------------------------------------------------------------------------
void CmdListener::SetTag(const SQChar * tag)
{
    m_Tag.assign(tag);
}

// ------------------------------------------------------------------------------------------------
SqObj & CmdListener::GetData()
{
    return m_Data;
}

// ------------------------------------------------------------------------------------------------
void CmdListener::SetData(SqObj & data)
{
    m_Data = data;
}

// ------------------------------------------------------------------------------------------------
const SQChar * CmdListener::GetName() const
{
    return m_Name.c_str();
}

// ------------------------------------------------------------------------------------------------
void CmdListener::SetName(const SQChar * name)
{
    if (!m_Lock)
    {
        // If there's a name then we're already binded
        if (!m_Name.empty())
        {
            _Cmd->Unbind(name);
        }
        // Assign the new name
        m_Name.assign(name);
        // If the new name is valid then bind to it
        if (!m_Name.empty())
        {
            _Cmd->Bind(name, this);
        }
    }
    else
    {
        LogWrn("Attempting to <change command name> while command is under active lock: %s", m_Name.c_str());
    }
}

// ------------------------------------------------------------------------------------------------
const SQChar * CmdListener::GetSpec() const
{
    return m_Spec.c_str();
}

// ------------------------------------------------------------------------------------------------
void CmdListener::SetSpec(const SQChar * spec)
{
    if (ProcSpec(spec))
    {
        m_Spec.assign(spec);
    }
}

// ------------------------------------------------------------------------------------------------
const SQChar * CmdListener::GetArgTag(SQUint32 arg) const
{
    if (arg < MAX_CMD_ARGS)
    {
        return m_Argt[arg].c_str();
    }
    else
    {
        LogErr("Unable to <get command argument tag/name> because : argument is out of bounds %u > %u",
                arg, MAX_CMD_ARGS);
    }
    // Argument failed inspection
    return _SC("");
}

// ------------------------------------------------------------------------------------------------
void CmdListener::SetArgTag(SQUint32 arg, const SQChar * name)
{
    if (arg < MAX_CMD_ARGS)
    {
        m_Argt[arg].assign(name);
    }
    else
    {
        LogErr("Unable to <set command argument tag/name> because : argument is out of bounds %u > %u",
                arg, MAX_CMD_ARGS);
    }
}

// ------------------------------------------------------------------------------------------------
Array CmdListener::GetArgTags() const
{
    // Allocate an array to encapsulate all tags
    Array arr(DefaultVM::Get(), MAX_CMD_ARGS);
    // Put the tags to the allocated array
    for (SQUint32 arg = 0; arg < MAX_CMD_ARGS; ++arg)
    {
        arr.SetValue(arg, m_Argt[arg]);
    }
    // Return the array with the tags
    return arr;
}

// ------------------------------------------------------------------------------------------------
void CmdListener::SetArgTags(Array & tags)
{
    // Attempt to retrieve the number of specified tags
    const SQUint32 max = _SCU32(tags.Length());
    // If no tags were specified then clear current tags
    if (max == 0)
    {
        m_Argt.fill(Argt::value_type());
    }
    // See if we're in range
    else if (max < MAX_CMD_ARGS)
    {
        // Attempt to get all arguments in one go
        tags.GetArray(m_Argt.data(), max);
    }
    else
    {
        LogErr("Unable to <set command argument tag/name> because : argument is out of bounds %u > %u",
                max, MAX_CMD_ARGS);
    }
}

// ------------------------------------------------------------------------------------------------
const SQChar * CmdListener::GetHelp() const
{
    return m_Help.c_str();
}

// ------------------------------------------------------------------------------------------------
void CmdListener::SetHelp(const SQChar * help)
{
    m_Help.assign(help);
}

// ------------------------------------------------------------------------------------------------
const SQChar * CmdListener::GetInfo() const
{
    return m_Info.c_str();
}

// ------------------------------------------------------------------------------------------------
void CmdListener::SetInfo(const SQChar * info)
{
    m_Info.assign(info);
}

// ------------------------------------------------------------------------------------------------
Function & CmdListener::GetOnExec()
{
    return m_OnExec;
}

// ------------------------------------------------------------------------------------------------
void CmdListener::SetOnExec(Function & func)
{
    m_OnExec = func;
}

// ------------------------------------------------------------------------------------------------
void CmdListener::SetOnExec_Env(SqObj & env, Function & func)
{
    m_OnExec = Function(env.GetVM(), env, func.GetFunc());
}

// ------------------------------------------------------------------------------------------------
Function & CmdListener::GetOnAuth()
{
    return m_OnAuth;
}

// ------------------------------------------------------------------------------------------------
void CmdListener::SetOnAuth(Function & func)
{
    m_OnAuth = func;
}

// ------------------------------------------------------------------------------------------------
void CmdListener::SetOnAuth_Env(SqObj & env, Function & func)
{
    m_OnAuth = Function(env.GetVM(), env, func.GetFunc());
}

// ------------------------------------------------------------------------------------------------
SQInt32 CmdListener::GetLevel() const
{
    return m_Level;
}

// ------------------------------------------------------------------------------------------------
void CmdListener::SetLevel(SQInt32 level)
{
    m_Level = level;
}

// ------------------------------------------------------------------------------------------------
bool CmdListener::GetAuthority() const
{
    return m_Authority;
}

// ------------------------------------------------------------------------------------------------
void CmdListener::SetAuthority(bool toggle)
{
    m_Authority = toggle;
}

// ------------------------------------------------------------------------------------------------
bool CmdListener::GetSuspended() const
{
    return m_Suspended;
}

// ------------------------------------------------------------------------------------------------
void CmdListener::SetSuspended(bool toggle)
{
    m_Suspended = toggle;
}

// ------------------------------------------------------------------------------------------------
Uint8 CmdListener::GetMinArgC() const
{
    return m_MinArgc;
}

// ------------------------------------------------------------------------------------------------
void CmdListener::SetMinArgC(Uint8 val)
{
    if (val < MAX_CMD_ARGS && val <= m_MaxArgc)
    {
        m_MinArgc = val;
    }
    else if (val > m_MaxArgc)
    {
        LogWrn("Attempting to <set maximum command arguments> using a value bigger then maximum: %u > %u",
                val, m_MaxArgc);
    }
    else
    {
        LogWrn("Attempting to <set minimum command arguments> using an out of bounds value %u > %u",
                val, MAX_CMD_ARGS);
    }
}

// ------------------------------------------------------------------------------------------------
Uint8 CmdListener::GetMaxArgC() const
{
    return m_MaxArgc;
}

// ------------------------------------------------------------------------------------------------
void CmdListener::SetMaxArgC(Uint8 val)
{
    if (val < MAX_CMD_ARGS && val >= m_MinArgc)
    {
        m_MaxArgc = val;
    }
    else if (val < m_MinArgc)
    {
        LogWrn("Attempting to <set maximum command arguments> using a value smaller then minimum: %u < %u",
                val, m_MinArgc);
    }
    else
    {
        LogWrn("Attempting to <set maximum command arguments> using an out of bounds value: %u > %d",
                val, MAX_CMD_ARGS);
    }
}

// ------------------------------------------------------------------------------------------------
void CmdListener::GenerateInfo(bool full)
{
    // Clear any previously generated informational message
    m_Info.clear();
    // Process each supported command argument
    for (SQUint32 arg = 0; arg < m_MaxArgc; ++arg)
    {
        // If this is not a full command request then see if we must stop
        if (!full)
        {
            // Default to stop if criteria are not meet
            bool stop = true;
            // Check all arguments after this and see if there's any left
            for (Uint32 idx = arg; idx < m_MaxArgc; ++idx)
            {
                // If the argument has a name or a type specifier then it's valid
                if (!m_Argt[idx].empty() || m_Args[idx] != CMDARG_ANY)
                {
                    // We have more arguments that need to be parsed
                    stop = false;
                    // Go back to the main loop
                    break;
                }
            }
            // Is there any argument left?
            if (stop)
            {
                // Stop the main loop as well
                break;
            }
        }
        // Begin the argument block
        m_Info += '<';
        // If the current argument is beyond minimum then mark it as optional
        if (arg >= m_MinArgc)
        {
            m_Info += '*';
        }
        // If the argument has a tag/name associated then add it as well
        if (!m_Argt[arg].empty())
        {
            // Add the name first
            m_Info += m_Argt[arg];
            // Separate the name from the specifiers
            m_Info += ':';
        }
        // If the argument has any explicit types specified
        if (m_Args[arg] != CMDARG_ANY)
        {
            // Does it support integers?
            if (m_Args[arg] & CMDARG_INTEGER)
            {
                m_Info += _SC("integer");
            }
            // Does it support floats?
            if (m_Args[arg] & CMDARG_FLOAT)
            {
                // Add a separator if this is not the first enabled type?
                if (m_Info.back() != ':' && m_Info.back() != '<')
                {
                    m_Info += ',';
                }
                // Now add the type name
                m_Info += _SC("float");
            }
            // Does it support booleans?
            if (m_Args[arg] & CMDARG_BOOLEAN)
            {
                // Add a separator if this is not the first enabled type?
                if (m_Info.back() != ':' && m_Info.back() != '<')
                {
                    m_Info += ',';
                }
                // Now add the type name
                m_Info += _SC("boolean");
            }
            // Does it support strings?
            if (m_Args[arg] & CMDARG_STRING)
            {
                // Add a separator if this is not the first enabled type?
                if (m_Info.back() != ':' && m_Info.back() != '<')
                {
                    m_Info += ',';
                }
                // Now add the type name
                m_Info += _SC("string");
            }
        }
        // Any kind of value is supported by this argument
        else
        {
            m_Info += _SC("any");
        }
        // Stop the argument block
        m_Info += '>';
        // If this is not the last argument then add a spacer
        if (arg+1 != m_MaxArgc)
        {
            m_Info += ' ';
        }
    }
}

// ------------------------------------------------------------------------------------------------
bool CmdListener::ArgCheck(SQUint32 arg, Uint8 mask) const
{
    if (arg < MAX_CMD_ARGS)
    {
        return (m_Args[arg] & mask) || m_Args[arg] == CMDARG_ANY;
    }
    else
    {
        LogErr("Unable to <check command argument specifier> because : argument is out of bounds %u > %u",
                arg, MAX_CMD_ARGS);
    }
    // Argument failed inspection
    return false;
}

// ------------------------------------------------------------------------------------------------
bool CmdListener::AuthCheck(Reference< CPlayer > & player)
{
    return AuthCheckID(player);
}

// ------------------------------------------------------------------------------------------------
bool CmdListener::AuthCheckID(SQInt32 id)
{
    // Allow execution by default
    bool allow = true;
    // Do we need explicit authority verification?
    if (m_Authority)
    {
        // Was there a custom authority inspector specified?
        if (!m_OnAuth.IsNull())
        {
            // Ask the specified authority inspector if this execution should be allowed
            SharedPtr< bool > ret = m_OnAuth.Evaluate< bool, SQInt32 >(id);
            // See what the custom authority inspector said or default to disallow
            allow = (!ret ? false : *ret);
        }
        // Can we use the default authority system?
        else if (m_Level >= 0)
        {
            allow = (Reference< CPlayer >::Get(id).Level >= m_Level);
        }
    }
    // Return result
    return allow;
}

// ------------------------------------------------------------------------------------------------
bool CmdListener::Execute(CPlayer & invoker, Array & args)
{
    bool result = false;
    // Was there an executer specified?
    if (!m_OnExec.IsNull())
    {
        // Attempt to evaluate the specified executer
        SharedPtr< bool > ret = m_OnExec.Evaluate< bool, CPlayer &, Array & >(invoker, args);
        // See if the executer succeeded or default to failed
        result = !ret ? false : *ret;
    }
    // Return result
    return result;
}

// ------------------------------------------------------------------------------------------------
bool CmdListener::ProcSpec(const SQChar * str)
{
    // Currently processed character
    SQChar ch = 0;
    // When parsing may continue
    bool good = true;
    // Currently processed argument
    SQUint32 idx = 0;
    // Process each character in the specified command
    while (good)
    {
        // Extract the current character before advancing
        ch = *(str++);
        // See if there are still things left to parse
        if (ch == 0)
        {
            // Finished parsing successfully
            break;
        }
        // See if we need to move to the next argument
        else if (ch == '|')
        {
            if (idx > MAX_CMD_ARGS)
            {
                LogErr("Unable to <parse command specifier> because : argument is out of bounds %u > %u",
                        idx, MAX_CMD_ARGS);
                // Parsing failed
                good = false;
                // Stop parsing
                break;
            }
            // Move to the next argument
            ++idx;
        }
        // Simply ignore a type specifier delimiter
        else if (ch != ',')
        {
            // Consume space when found
            if (ch == ' ')
            {
                while (good)
                {
                    ch = *(str++);
                    // Stop when the text ends or on the first non-space character
                    if (ch == 0 || ch != ' ')
                    {
                        break;
                    }
                }
            }
            // See if there is a specifier left
            if (!good)
            {
                // Propagate stop
                break;
            }
            // Apply the type specifier
            switch(ch)
            {
                // Enable the integer type
                case 'i': m_Args[idx] |= CMDARG_INTEGER; break;
                // Enable the float type
                case 'f': m_Args[idx] |= CMDARG_FLOAT; break;
                // Enable the boolean type
                case 'b': m_Args[idx] |= CMDARG_BOOLEAN; break;
                // Enable the string type
                case 's': m_Args[idx] |= CMDARG_STRING; break;
                // Unknown type!
                default:
                    {
                        LogErr("Unable to <parse command specifier> because : unknown type specifier %u -> %c",
                                idx, ch);
                        // Parsing failed
                        good = false;
                    }
                break;
            }
        }
    }
    // Reset all argument specifiers if failed
    if (!good)
    {
        m_Args.fill(CMDARG_ANY);
    }
    // Attempt to generate an informational message
    GenerateInfo(false);
    // Return whether the parsing was successful
    return good;
}

// ------------------------------------------------------------------------------------------------
const SQChar * CmdArgSpecToStr(Uint8 spec)
{
    switch (spec)
    {
        case CMDARG_ANY:       return _SC("any");
        case CMDARG_INTEGER:   return _SC("integer");
        case CMDARG_FLOAT:     return _SC("float");
        case CMDARG_BOOLEAN:   return _SC("boolean");
        case CMDARG_STRING:    return _SC("string");
        default:            return _SC("unknown");
    }
}

// ------------------------------------------------------------------------------------------------
static Function & Cmd_GetOnError()
{
    return _Cmd->GetOnError();
}

// ------------------------------------------------------------------------------------------------
static void Cmd_SetOnError(Function & func)
{
    _Cmd->SetOnError(func);
}

// ------------------------------------------------------------------------------------------------
static Function & Cmd_GetOnAuth()
{
    return _Cmd->GetOnAuth();
}

// ------------------------------------------------------------------------------------------------
static void Cmd_SetOnAuth(Function & func)
{
    _Cmd->SetOnAuth(func);
}

// ------------------------------------------------------------------------------------------------
static const CPlayer & Cmd_GetInvoker()
{
    return _Cmd->GetInvoker();
}

// ------------------------------------------------------------------------------------------------
static SQInt32 Cmd_GetInvokerID()
{
    return _Cmd->GetInvokerID();
}

// ------------------------------------------------------------------------------------------------
static const SQChar * Cmd_GetName()
{
    return _Cmd->GetName();
}

// ------------------------------------------------------------------------------------------------
static const SQChar * Cmd_GetText()
{
    return _Cmd->GetText();
}

// ================================================================================================
bool Register_Cmd(HSQUIRRELVM vm)
{
    // // Attempt to create the Cmd namespace
    Sqrat::Table cmdns(vm);

    // Output debugging information
    LogDbg("Beginning registration of <Cmd Listener> type");
    // Attempt to register the specified type
    cmdns.Bind(_SC("Listener"), Sqrat::Class< CmdListener, NoCopy< CmdListener > >(vm, _SC("Listener"))
        /* Constructors */
        .Ctor()
        .Ctor< const SQChar * >()
        .Ctor< const SQChar *, const SQChar * >()
        .Ctor< const SQChar *, const SQChar *, Array & >()
        .Ctor< const SQChar *, const SQChar *, Uint8, Uint8 >()
        .Ctor< const SQChar *, const SQChar *, Array &, Uint8, Uint8 >()
        /* Metamethods */
        .Func(_SC("_cmp"), &CmdListener::Cmp)
        .Func(_SC("_tostring"), &CmdListener::ToString)
        /* Properties */
        .Prop(_SC("ltag"), &CmdListener::GetTag, &CmdListener::SetTag)
        .Prop(_SC("ldata"), &CmdListener::GetData, &CmdListener::SetData)
        .Prop(_SC("name"), &CmdListener::GetName, &CmdListener::SetName)
        .Prop(_SC("spec"), &CmdListener::GetSpec, &CmdListener::SetSpec)
        .Prop(_SC("tags"), &CmdListener::GetArgTags, &CmdListener::SetArgTags)
        .Prop(_SC("help"), &CmdListener::GetHelp, &CmdListener::SetHelp)
        .Prop(_SC("info"), &CmdListener::GetInfo, &CmdListener::SetInfo)
        .Prop(_SC("on_exec"), &CmdListener::GetOnExec, &CmdListener::SetOnExec)
        .Prop(_SC("on_auth"), &CmdListener::GetOnAuth, &CmdListener::SetOnAuth)
        .Prop(_SC("level"), &CmdListener::GetLevel, &CmdListener::SetLevel)
        .Prop(_SC("auth"), &CmdListener::GetAuthority, &CmdListener::SetAuthority)
        .Prop(_SC("authority"), &CmdListener::GetAuthority, &CmdListener::SetAuthority)
        .Prop(_SC("suspended"), &CmdListener::GetSuspended, &CmdListener::SetSuspended)
        .Prop(_SC("min_args"), &CmdListener::GetMinArgC, &CmdListener::SetMinArgC)
        .Prop(_SC("max_args"), &CmdListener::GetMaxArgC, &CmdListener::SetMaxArgC)
        /* Functions */
        .Func(_SC("get_arg_tag"), &CmdListener::GetArgTag)
        .Func(_SC("set_arg_tag"), &CmdListener::SetArgTag)
        .Func(_SC("set_on_exec"), &CmdListener::SetOnExec_Env)
        .Func(_SC("set_on_auth"), &CmdListener::SetOnAuth_Env)
        .Func(_SC("generate_info"), &CmdListener::GenerateInfo)
        .Func(_SC("arg_check"), &CmdListener::ArgCheck)
        .Func(_SC("auth_check"), &CmdListener::AuthCheck)
        .Func(_SC("auth_check_id"), &CmdListener::AuthCheckID)
    );

    // Output debugging information
    LogDbg("Registration of <Cmd Listener> type was successful");

    // Output debugging information
    LogDbg("Beginning registration of <Cmd functions> type");
    // Attempt to register the free functions
    cmdns.Func(_SC("GetOnError"), &Cmd_GetOnError);
    cmdns.Func(_SC("SetOnError"), &Cmd_SetOnError);
    cmdns.Func(_SC("GetOnAuth"), &Cmd_GetOnAuth);
    cmdns.Func(_SC("SetOnAuth"), &Cmd_SetOnAuth);
    cmdns.Func(_SC("GetInvoker"), &Cmd_GetInvoker);
    cmdns.Func(_SC("GetInvokerID"), &Cmd_GetInvokerID);
    cmdns.Func(_SC("GetName"), &Cmd_GetName);
    cmdns.Func(_SC("getText"), &Cmd_GetText);
    // Output debugging information
    LogDbg("Registration of <Cmd functions> type was successful");

    // Attempt to bind the namespace to the root table
    Sqrat::RootTable(vm).Bind(_SC("Cmd"), cmdns);

    // Output debugging information
    LogDbg("Beginning registration of <Cmd Constants> type");
    // Attempt to register the argument types enumeration
    Sqrat::ConstTable(vm).Enum(_SC("ECMDARG"), Sqrat::Enumeration(vm)
        .Const(_SC("ANY"),                  CMDARG_ANY)
        .Const(_SC("INTEGER"),              CMDARG_INTEGER)
        .Const(_SC("FLOAT"),                CMDARG_FLOAT)
        .Const(_SC("BOOLEAN"),              CMDARG_BOOLEAN)
        .Const(_SC("STRING"),               CMDARG_STRING)
    );
    // Attempt to register the error codes enumeration
    Sqrat::ConstTable(vm).Enum(_SC("ECMDERR"), Sqrat::Enumeration(vm)
        .Const(_SC("UNKNOWN"),              CMDERR_UNKNOWN)
        .Const(_SC("SYNTAX_ERROR"),         CMDERR_SYNTAX_ERROR)
        .Const(_SC("UNKNOWN_COMMAND"),      CMDERR_UNKNOWN_COMMAND)
        .Const(_SC("MISSING_EXECUTER"),     CMDERR_MISSING_EXECUTER)
        .Const(_SC("INSUFFICIENT_AUTH"),    CMDERR_INSUFFICIENT_AUTH)
        .Const(_SC("INCOMPLETE_ARGS"),      CMDERR_INCOMPLETE_ARGS)
        .Const(_SC("EXTRANEOUS_ARGS"),      CMDERR_EXTRANEOUS_ARGS)
        .Const(_SC("UNSUPPORTED_ARG"),      CMDERR_UNSUPPORTED_ARG)
        .Const(_SC("EXECUTION_FAILED"),     CMDERR_EXECUTION_FAILED)
    );
    // Output debugging information
    LogDbg("Registration of <IRC Constants> type was successful");
    // Registration succeeded
    return true;
}


} // Namespace:: SqMod
