#ifndef _COMMAND_HPP_
#define _COMMAND_HPP_

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

// ------------------------------------------------------------------------------------------------
#include <cctype>
#include <cstdlib>
#include <cstring>
#include <map>
#include <vector>
#include <iterator>
#include <algorithm>

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

/* ------------------------------------------------------------------------------------------------
 * Forward declarations.
*/
class Context;
class Guard;
class Command;
class Controller;
class Manager;
class Listener;

/* ------------------------------------------------------------------------------------------------
 * Helper typedefs.
*/

// ------------------------------------------------------------------------------------------------
typedef SharedPtr< Context >        CtxRef; // Shared reference to an execution context.
typedef WeakPtr< Context >          CtxWRef; // Shared reference to an execution context.

// ------------------------------------------------------------------------------------------------
typedef SharedPtr< Controller >     CtrRef; // Shared reference to a command controller.
typedef WeakPtr< Controller >       CtrWRef; // Shared reference to a command controller.

// ------------------------------------------------------------------------------------------------
typedef std::pair< Uint8, Object >  Argument; // Can hold the argument value and type.
typedef std::vector< Argument >     Arguments; // A list of extracted arguments.

// ------------------------------------------------------------------------------------------------
typedef std::vector< Command >      Commands; // List of attached command instances.
typedef std::vector< Controller * > Controllers; // List of active controllers.

/* ------------------------------------------------------------------------------------------------
 * Types of arguments supported by the command system.
*/
enum CmdArgType
{
    CMDARG_ANY         = 0,
    CMDARG_INTEGER     = (1 << 1),
    CMDARG_FLOAT       = (1 << 2),
    CMDARG_BOOLEAN     = (1 << 3),
    CMDARG_STRING      = (1 << 4),
    CMDARG_LOWER       = (1 << 5),
    CMDARG_UPPER       = (1 << 6),
    CMDARG_GREEDY      = (1 << 7)
};

/* ------------------------------------------------------------------------------------------------
 * Types of errors reported by the command system.
*/
enum CmdError
{
    // The command failed for unknown reasons
    CMDERR_UNKNOWN = 0,
    // The command failed to execute because there was nothing to execute
    CMDERR_EMPTY_COMMAND,
    // The command failed to execute because the command name was invalid after processing
    CMDERR_INVALID_COMMAND,
    // The command failed to execute because there was a syntax error in the arguments
    CMDERR_SYNTAX_ERROR,
    // The command failed to execute because there was no such command
    CMDERR_UNKNOWN_COMMAND,
    // The command failed to execute because the it's currently suspended
    CMDERR_COMMAND_SUSPENDED,
    // The command failed to execute because the invoker does not have the proper authority
    CMDERR_INSUFFICIENT_AUTH,
    // The command failed to execute because there was no callback to handle the execution
    CMDERR_MISSING_EXECUTER,
    // The command was unable to execute because the argument limit was not reached
    CMDERR_INCOMPLETE_ARGS,
    // The command was unable to execute because the argument limit was exceeded
    CMDERR_EXTRANEOUS_ARGS,
    // Command was unable to execute due to argument type mismatch
    CMDERR_UNSUPPORTED_ARG,
    // The command arguments contained more data than the internal buffer can handle
    CMDERR_BUFFER_OVERFLOW,
    // The command failed to complete execution due to a runtime exception
    CMDERR_EXECUTION_FAILED,
    // The command completed the execution but returned a negative result
    CMDERR_EXECUTION_ABORTED,
    // The post execution callback failed to execute due to a runtime exception
    CMDERR_POST_PROCESSING_FAILED,
    // The callback that was supposed to deal with the failure also failed due to a runtime exception
    CMDERR_UNRESOLVED_FAILURE,
    // Maximum command error identifier
    CMDERR_MAX
};

// ------------------------------------------------------------------------------------------------
inline CSStr ValidateName(CSStr name)
{
    // Is the name empty?
    if (!name || *name == '\0')
    {
        STHROWF("Invalid or empty command name");
    }
    // Create iterator to name start
    CSStr str = name;
    // Inspect name characters
    while (*str != '\0')
    {
        // Does it contain spaces?
        if (std::isspace(*str) != 0)
        {
            STHROWF("Command names cannot contain spaces");
        }
        // Move to the next character
        ++str;
    }
    // Return the name
    return name;
}

// ------------------------------------------------------------------------------------------------
inline CSStr ArgSpecToStr(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:
        case CMDARG_LOWER:
        case CMDARG_UPPER:
        case CMDARG_GREEDY:     return _SC("string");
        default:                return _SC("unknown");
    }
}

/* ------------------------------------------------------------------------------------------------
 * Holds the context of a command execution.
*/
struct Context
{
public:

    // --------------------------------------------------------------------------------------------
    Buffer          mBuffer; // Shared buffer used to extract arguments and process data.

    // --------------------------------------------------------------------------------------------
    const Object    mInvoker; // Reference to the entity that invoked the command.
    String          mCommand; // Command name extracted from the command string.
    String          mArgument; // Command argument extracted from the command string.
    Listener*       mInstance; // Pointer to the currently executed command listener.
    Object          mObject; // Script object of the currently executed command.

    // --------------------------------------------------------------------------------------------
    Arguments       mArgv; // Extracted command arguments.
    Uint32          mArgc; // Extracted arguments count.

    /* --------------------------------------------------------------------------------------------
     * Default constructor.
    */
    Context(Object & invoker)
        : mBuffer(512)
        , mInvoker(invoker)
        , mCommand()
        , mArgument()
        , mInstance(nullptr)
        , mObject()
        , mArgv()
        , mArgc(0)
    {
        // Reserve enough space upfront
        mCommand.reserve(64);
        mArgument.reserve(512);
    }

    /* --------------------------------------------------------------------------------------------
     * Copy constructor. (disabled)
    */
    Context(const Context & o) = delete;

    /* --------------------------------------------------------------------------------------------
     * Move constructor. (disabled)
    */
    Context(Context && o) = delete;

    /* --------------------------------------------------------------------------------------------
     * Copy assignment operator. (disabled)
    */
    Context & operator = (const Context & o) = delete;

    /* --------------------------------------------------------------------------------------------
     * Move assignment operator. (disabled)
    */
    Context & operator = (Context && o) = delete;
};

/* ------------------------------------------------------------------------------------------------
 * Helper class implementing RAII to release the command context.
*/
struct Guard
{
public:

    // --------------------------------------------------------------------------------------------
    CtrRef mController; // Reference to the guarded controller.
    CtxRef mPrevious; // Previous context when this guard was created.
    CtxRef mCurrent; // The context managed by this guard.

    /* --------------------------------------------------------------------------------------------
     * Default constructor.
    */
    Guard(const CtrRef & ctr, Object & invoker);

    /* --------------------------------------------------------------------------------------------
     * Copy constructor.
    */
    Guard(const Guard & o) = delete;

    /* --------------------------------------------------------------------------------------------
     * Move constructor.
    */
    Guard(Guard && o) = delete;

    /* --------------------------------------------------------------------------------------------
     * Destructor.
    */
    ~Guard();

    /* --------------------------------------------------------------------------------------------
     * Copy assignment operator.
    */
    Guard & operator = (const Guard & o) = delete;

    /* --------------------------------------------------------------------------------------------
     * Move assignment operator.
    */
    Guard & operator = (Guard && o) = delete;
};

/* ------------------------------------------------------------------------------------------------
 * Structure that represents a unique command in the pool.
*/
struct Command
{
    // --------------------------------------------------------------------------------------------
    std::size_t     mHash; // The unique hash that identifies this command.
    String          mName; // The unique name that identifies this command.
    Listener*       mPtr; // The listener that reacts to this command.
    Object          mObj; // A strong reference to the script object.

    /* --------------------------------------------------------------------------------------------
     * Construct a command and the also create a script object from the specified listener.
    */
    Command(std::size_t hash, const String & name, Listener * ptr)
        : mHash(hash), mName(name), mPtr(ptr), mObj(ptr)
    {
        /* ... */
    }

    /* --------------------------------------------------------------------------------------------
     * Construct a command and extract the listener from the specified script object.
    */
    Command(std::size_t hash, const String & name, const Object & obj)
        : mHash(hash), mName(name), mPtr(obj.Cast< Listener * >()), mObj(obj)
    {
        /* ... */
    }

    /* --------------------------------------------------------------------------------------------
     * Construct a command with the given parameters.
    */
    Command(std::size_t hash, const String & name, Listener * ptr, const Object & obj)
        : mHash(hash), mName(name), mPtr(ptr), mObj(obj)
    {
        /* ... */
    }

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

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

    /* --------------------------------------------------------------------------------------------
     * Destructor.
    */
    ~Command();

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

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

/* ------------------------------------------------------------------------------------------------
 * Holds a list of commands to execute as well as authentication or failure resolvers.
*/
struct Controller
{
    // --------------------------------------------------------------------------------------------
    friend class Guard; // Allow the guard to swap the execution context.
    friend class Manager; // Allow the manager to manage the controller.
    friend class Listener; // Allow the listener to attach and detach.

private:

    // --------------------------------------------------------------------------------------------
    Commands        m_Commands; // List of available command instances.
    CtxRef          m_Context; // Context of the currently executed command.

    // --------------------------------------------------------------------------------------------
    Function        m_OnFail; // Callback when something failed while running a command.
    Function        m_OnAuth; // Callback to authenticate execution for a certain invoker.

    // --------------------------------------------------------------------------------------------
    Manager *       m_Manager;

    // --------------------------------------------------------------------------------------------
    static Controllers s_Controllers;

protected:

    /* --------------------------------------------------------------------------------------------
     * Default constructor.
    */
    Controller(Manager * mgr)
        : m_Commands()
        , m_Context()
        , m_OnFail()
        , m_OnAuth()
        , m_Manager(mgr)
    {
        s_Controllers.push_back(this);
    }

    /* --------------------------------------------------------------------------------------------
     * Copy constructor. (disabled)
    */
    Controller(const Controller & o) = delete;

    /* --------------------------------------------------------------------------------------------
     * Move constructor. (disabled)
    */
    Controller(Controller && o) = delete;

    /* --------------------------------------------------------------------------------------------
     * Copy assignment operator. (disabled)
    */
    Controller & operator = (const Controller & o) = delete;

    /* --------------------------------------------------------------------------------------------
     * Move assignment operator. (disabled)
    */
    Controller & operator = (Controller && o) = delete;

protected:

    /* --------------------------------------------------------------------------------------------
     * Forward error message to the error callback.
    */
    template < typename T > void SqError(Int32 type, CSStr msg, T data)
    {
        // Is there a callback that deals with errors?
        if (m_OnFail.IsNull())
        {
            return;
        }
        // Attempt to forward the error to that callback
        try
        {
            m_OnFail.Execute< Int32, CSStr, T >(type, msg, data);
        }
        catch (const Sqrat::Exception & e)
        {
            // We can only log this incident and in the future maybe also include the location
            LogErr("Command error callback failed [%s]", e.what());
        }
    }

    /* --------------------------------------------------------------------------------------------
     * Execute one of the managed commands.
    */
    Int32 Run(const Guard & guard, CSStr command);

    /* --------------------------------------------------------------------------------------------
     * Attempt to execute the specified command.
    */
    Int32 Exec(Context & ctx);

    /* --------------------------------------------------------------------------------------------
     * Attempt to parse the specified argument.
    */
    bool Parse(Context & ctx);

    /* --------------------------------------------------------------------------------------------
     * Attach a command listener to a certain name.
    */
    Object & Attach(Object & obj, Listener * ptr);

    /* --------------------------------------------------------------------------------------------
     * Detach a command listener from a certain name.
    */
    void Detach(const String & name)
    {
        // Obtain the unique identifier of the specified name
        const std::size_t hash = std::hash< String >()(name);
        // Iterator to the found command, if any
        Commands::const_iterator itr = m_Commands.cbegin();
        // Attempt to find the specified command
        for (; itr != m_Commands.cend(); ++itr)
        {
            // Are the hashes identical?
            if (itr->mHash == hash)
            {
                break; // We found our command!
            }
        }
        // Make sure the command exist before attempting to remove it
        if (itr != m_Commands.end())
        {
            m_Commands.erase(itr);
        }
    }

    /* --------------------------------------------------------------------------------------------
     * Detach a command listener from a certain name.
    */
    void Detach(Listener * ptr)
    {
        // Iterator to the found command, if any
        Commands::const_iterator itr = m_Commands.cbegin();
        // Attempt to find the specified command
        for (; itr != m_Commands.cend(); ++itr)
        {
            // Are the instances identical?
            if (itr->mPtr == ptr)
            {
                break; // We found our command!
            }
        }
        // Make sure the command exists before attempting to remove it
        if (itr != m_Commands.end())
        {
            m_Commands.erase(itr);
        }
    }

public:

    /* --------------------------------------------------------------------------------------------
     * Clear the command listeners from all controllers.
    */
    static void ClearAll()
    {
        for (auto & ctr : s_Controllers)
        {
            ctr->Clear();
        }
    }

    /* --------------------------------------------------------------------------------------------
     * Destructor.
    */
    ~Controller()
    {
        s_Controllers.erase(std::remove(s_Controllers.begin(), s_Controllers.end(), this),
                            s_Controllers.end());
    }

    /* --------------------------------------------------------------------------------------------
     * Retrieve the currently active execution context.
    */
    const CtxRef & GetCtx() const
    {
        return m_Context;
    }

    /* --------------------------------------------------------------------------------------------
     * See whether a certain name exist in the command list.
    */
    bool Attached(const String & name) const
    {
        // Obtain the unique identifier of the specified name
        const std::size_t hash = std::hash< String >()(name);
        // Attempt to find the specified command
        for (const auto & cmd : m_Commands)
        {
            // Are the hashes identical?
            if (cmd.mHash == hash)
            {
                return true; // We found our command!
            }
        }
        // No such command exists
        return false;
    }

    /* --------------------------------------------------------------------------------------------
     * See whether a certain instance exist in the command list.
    */
    bool Attached(const Listener * ptr) const
    {
        // Attempt to find the specified command
        for (const auto & cmd : m_Commands)
        {
            // Are the instances identical?
            if (cmd.mPtr == ptr)
            {
                return true; // We found our command!
            }
        }
        // No such command exists
        return false;
    }

    /* --------------------------------------------------------------------------------------------
     * Sort the command list in an ascending order.
    */
    void Sort()
    {
        std::sort(m_Commands.begin(), m_Commands.end(),
            [](Commands::const_reference a, Commands::const_reference b) -> bool {
                return (b.mName < a.mName);
            });
    }

    /* --------------------------------------------------------------------------------------------
     * Detach all the associated command listeners.
    */
    void Clear()
    {
        m_Commands.clear();
    }

    /* --------------------------------------------------------------------------------------------
     * Locate and retrieve a command listener by name.
    */
    const Object & FindByName(const String & name)
    {
        // Obtain the unique identifier of the specified name
        const std::size_t hash = std::hash< String >()(name);
        // Attempt to find the specified command
        for (const auto & cmd : m_Commands)
        {
            // Are the hashes identical?
            if (cmd.mHash == hash)
            {
                return cmd.mObj; // We found our command!
            }
        }
        // No such command exist
        return NullObject();
    }

    /* --------------------------------------------------------------------------------------------
     * Retrieve the error callback.
    */
    Function & GetOnFail()
    {
        return m_OnFail;
    }

    /* --------------------------------------------------------------------------------------------
     * Modify the error callback.
    */
    void SetOnFail(Object & env, Function & func)
    {
        m_OnFail = Function(env.GetVM(), env, func.GetFunc());
    }

    /* --------------------------------------------------------------------------------------------
     * Retrieve the authentication callback.
    */
    Function & GetOnAuth()
    {
        return m_OnAuth;
    }

    /* --------------------------------------------------------------------------------------------
     * Modify the authentication callback.
    */
    void SetOnAuth(Object & env, Function & func)
    {
        m_OnAuth = Function(env.GetVM(), env, func.GetFunc());
    }

    /* --------------------------------------------------------------------------------------------
     * See whether an execution context is currently active.
    */
    bool IsContext() const
    {
        return !!m_Context;
    }

    /* --------------------------------------------------------------------------------------------
     * Retrieve the invoker from the current execution context.
    */
    const Object & GetInvoker() const
    {
        // See if there's an execution context available
        if (!m_Context)
        {
            STHROWF("No active execution context");
        }
        // Return the requested information
        return m_Context->mInvoker;
    }

    /* --------------------------------------------------------------------------------------------
     * Retrieve the listener object from the current execution context.
    */
    const Object & GetListener() const
    {
        // See if there's an execution context available
        if (!m_Context)
        {
            STHROWF("No active execution context");
        }
        // Return the requested information
        return m_Context->mObject;
    }

    /* --------------------------------------------------------------------------------------------
     * Retrieve the command name from the current execution context.
    */
    const String & GetCommand() const
    {
        // See if there's an execution context available
        if (!m_Context)
        {
            STHROWF("No active execution context");
        }
        // Return the requested information
        return m_Context->mCommand;
    }

    /* --------------------------------------------------------------------------------------------
     * Retrieve the command argument from the current execution context.
    */
    const String & GetArgument() const
    {
        // See if there's an execution context available
        if (!m_Context)
        {
            STHROWF("No active execution context");
        }
        // Return the requested information
        return m_Context->mArgument;
    }
};

/* ------------------------------------------------------------------------------------------------
 * Allows interaction with a command controller from script.
*/
class Manager
{
private:

    // --------------------------------------------------------------------------------------------
    CtrRef  m_Controller; // Reference to the managed controller.

    /* --------------------------------------------------------------------------------------------
     * Retrieve the associated controller if valid otherwise throw an exception.
    */
    const CtrRef & GetValid() const
    {
        // Validate the managed controller
        if (!m_Controller)
        {
            STHROWF("No controller associated with this manager");
        }
        // Return the controller reference
        return m_Controller;
    }

public:

    /* --------------------------------------------------------------------------------------------
     * Default constructor.
    */
    Manager()
        : m_Controller(new Controller(this))
    {
        /* ... */
    }

    /* --------------------------------------------------------------------------------------------
     * Copy constructor. (disabled)
    */
    Manager(const Manager & o) = delete;

    /* --------------------------------------------------------------------------------------------
     * Move constructor. (disabled)
    */
    Manager(Manager && o) = delete;

    /* --------------------------------------------------------------------------------------------
     * Copy assignment operator. (disabled)
    */
    Manager & operator = (const Manager & o) = delete;

    /* --------------------------------------------------------------------------------------------
     * Move assignment operator. (disabled)
    */
    Manager & operator = (Manager && o) = delete;

    /* --------------------------------------------------------------------------------------------
     * Used by the script engine to compare two instances of this type.
    */
    Int32 Cmp(const Manager & o) const
    {
        if (m_Controller == o.m_Controller)
        {
            return 0;
        }
        else if (m_Controller.Get() > o.m_Controller.Get())
        {
            return 1;
        }
        else
        {
            return -1;
        }
    }

    /* --------------------------------------------------------------------------------------------
     * Used by the script engine to convert this instance to a string.
    */
    CSStr ToString() const
    {
        return ToStrF("%d", m_Controller.Count());
    }

    /* --------------------------------------------------------------------------------------------
     * Used by the script engine to retrieve the name from instances of this type.
    */
    static SQInteger Typename(HSQUIRRELVM vm);

    /* --------------------------------------------------------------------------------------------
     * Retrieve the associated controller reference.
    */
    const CtrRef & GetCtr() const
    {
        return m_Controller;
    }

    /* --------------------------------------------------------------------------------------------
     * Run a command under a specific invoker.
    */
    Int32 Run(Object & invoker, CSStr command)
    {
        return GetValid()->Run(Guard(m_Controller, invoker), command);
    }

    /* --------------------------------------------------------------------------------------------
     * Sort the command list in an ascending order.
    */
    void Sort()
    {
        GetValid()->Sort();
    }

    /* --------------------------------------------------------------------------------------------
     * Detach all the associated command listeners.
    */
    void Clear()
    {
        GetValid()->Clear();
    }

    /* --------------------------------------------------------------------------------------------
     * Attach a command listener to the managed controller.
    */
    void Attach(Object & obj)
    {
        GetValid()->Attach(obj, nullptr);
    }

    /* --------------------------------------------------------------------------------------------
     * Locate and retrieve a command listener by name.
    */
    const Object & FindByName(const String & name)
    {
        return GetValid()->FindByName(name);
    }

    /* --------------------------------------------------------------------------------------------
     * Retrieve the number of managed command listeners.
    */
    SQInteger GetCount() const
    {
        return ConvTo< SQInteger >::From(GetValid()->m_Commands.size());
    }

    /* --------------------------------------------------------------------------------------------
     * See whether an execution context is currently active.
    */
    bool IsContext() const
    {
        return GetValid()->IsContext();
    }

    /* --------------------------------------------------------------------------------------------
     * Retrieve the error callback.
    */
    Function & GetOnFail()
    {
        return GetValid()->GetOnFail();
    }

    /* --------------------------------------------------------------------------------------------
     * Modify the error callback.
    */
    void SetOnFail(Object & env, Function & func)
    {
        GetValid()->SetOnFail(env, func);
    }

    /* --------------------------------------------------------------------------------------------
     * Retrieve the authentication callback.
    */
    Function & GetOnAuth()
    {
        return GetValid()->GetOnAuth();
    }

    /* --------------------------------------------------------------------------------------------
     * Modify the authentication callback.
    */
    void SetOnAuth(Object & env, Function & func)
    {
        GetValid()->SetOnAuth(env, func);
    }

    /* --------------------------------------------------------------------------------------------
     * Retrieve the invoker from the current execution context.
    */
    const Object & GetInvoker() const
    {
        return GetValid()->GetInvoker();
    }

    /* --------------------------------------------------------------------------------------------
     * Retrieve the listener object from the current execution context.
    */
    const Object & GetListener() const
    {
        return GetValid()->GetListener();
    }

    /* --------------------------------------------------------------------------------------------
     * Retrieve the command name from the current execution context.
    */
    const String & GetCommand() const
    {
        return GetValid()->GetCommand();
    }

    /* --------------------------------------------------------------------------------------------
     * Retrieve the command argument from the current execution context.
    */
    const String & GetArgument() const
    {
        return GetValid()->GetArgument();
    }

    /* --------------------------------------------------------------------------------------------
     * Create command instances and obtain the associated object.
    */
    Object Create(CSStr name)
    {
        return Create(name, _SC(""), NullArray(), 0, SQMOD_MAX_CMD_ARGS-1, -1, false, false);
    }

    /* --------------------------------------------------------------------------------------------
     * Create command instances and obtain the associated object.
    */
    Object Create(CSStr name, CSStr spec)
    {
        return Create(name, spec, NullArray(), 0, SQMOD_MAX_CMD_ARGS-1, -1, false, false);
    }

    /* --------------------------------------------------------------------------------------------
     * Create command instances and obtain the associated object.
    */
    Object Create(CSStr name, CSStr spec, Array & tags)
    {
        return Create(name, spec, tags, 0, SQMOD_MAX_CMD_ARGS-1, -1, false, false);
    }

    /* --------------------------------------------------------------------------------------------
     * Create command instances and obtain the associated object.
    */
    Object Create(CSStr name, CSStr spec, Uint8 min, Uint8 max)
    {
        return Create(name, spec, NullArray(), min, max, -1, false, false);
    }

    /* --------------------------------------------------------------------------------------------
     * Create command instances and obtain the associated object.
    */
    Object Create(CSStr name, CSStr spec, Array & tags, Uint8 min, Uint8 max)
    {
        return Create(name, spec, tags, min, max, -1, false, false);
    }

    /* --------------------------------------------------------------------------------------------
     * Create command instances and obtain the associated object.
    */
    Object Create(CSStr name, CSStr spec, Array & tags, Uint8 min, Uint8 max, SQInteger auth)
    {
        return Create(name, spec, tags, min, max, auth, auth >= 0, false);
    }

    /* --------------------------------------------------------------------------------------------
     * Create command instances and obtain the associated object.
    */
    Object Create(CSStr name, CSStr spec, Array & tags, Uint8 min, Uint8 max, SQInteger auth, bool prot)
    {
        return Create(name, spec, tags, min, max, auth, prot, false);
    }

    /* --------------------------------------------------------------------------------------------
     * Create command instances and obtain the associated object.
    */
    Object Create(CSStr name, CSStr spec, Array & tags, Uint8 min, Uint8 max, SQInteger auth, bool prot, bool assoc);
};

/* ------------------------------------------------------------------------------------------------
 * Attaches to a command name and listens for invocations.
*/
class Listener
{
    // --------------------------------------------------------------------------------------------
    friend class Controller; // Allow the controller to execute this listener.
    friend class Command; // Allow the command to interact with this listener.
    friend class Manager; // Allow the manager to interact with this listener.

public:

    /* --------------------------------------------------------------------------------------------
     * Convenience constructor.
    */
    Listener(CSStr name)
        : Listener(name, _SC(""), NullArray(), 0, SQMOD_MAX_CMD_ARGS-1, -1, false, false)
    {
        /* ... */
    }

    /* --------------------------------------------------------------------------------------------
     * Convenience constructor.
    */
    Listener(CSStr name, CSStr spec)
        : Listener(name, spec, NullArray(), 0, SQMOD_MAX_CMD_ARGS-1, -1, false, false)
    {
        /* ... */
    }

    /* --------------------------------------------------------------------------------------------
     * Convenience constructor.
    */
    Listener(CSStr name, CSStr spec, Array & tags)
        : Listener(name, spec, tags, 0, SQMOD_MAX_CMD_ARGS-1, -1, false, false)
    {
        /* ... */
    }

    /* --------------------------------------------------------------------------------------------
     * Convenience constructor.
    */
    Listener(CSStr name, CSStr spec, Uint8 min, Uint8 max)
        : Listener(name, spec, NullArray(), min, max, -1, false, false)
    {
        /* ... */
    }

    /* --------------------------------------------------------------------------------------------
     * Convenience constructor.
    */
    Listener(CSStr name, CSStr spec, Array & tags, Uint8 min, Uint8 max)
        : Listener(name, spec, tags, min, max, -1, false, false)
    {
        /* ... */
    }

    /* --------------------------------------------------------------------------------------------
     * Convenience constructor.
    */
    Listener(CSStr name, CSStr spec, Array & tags, Uint8 min, Uint8 max, SQInteger auth)
        : Listener(name, spec, tags, min, max, auth, auth >= 0, false)
    {
        /* ... */
    }

    /* --------------------------------------------------------------------------------------------
     * Convenience constructor.
    */
    Listener(CSStr name, CSStr spec, Array & tags, Uint8 min, Uint8 max, SQInteger auth, bool prot)
        : Listener(name, spec, tags, min, max, auth, prot, false)
    {
        /* ... */
    }

    /* --------------------------------------------------------------------------------------------
     * Base constructor.
    */
    Listener(CSStr name, CSStr spec, Array & tags, Uint8 min, Uint8 max, SQInteger auth, bool prot, bool assoc)
        : m_Controller()
        , m_Name(ValidateName(name))
        , m_ArgSpec()
        , m_ArgTags()
        , m_MinArgc(0)
        , m_MaxArgc(SQMOD_MAX_CMD_ARGS-1)
        , m_Spec()
        , m_Help()
        , m_Info()
        , m_OnExec()
        , m_OnAuth()
        , m_OnPost()
        , m_OnFail()
        , m_Authority(ConvTo< Int32 >::From(auth))
        , m_Protected(prot)
        , m_Suspended(false)
        , m_Associate(assoc)
    {
        // Initialize the specifiers to default values
        for (Uint8 n = 0; n < SQMOD_MAX_CMD_ARGS; ++n)
        {
            m_ArgSpec[n] = CMDARG_ANY;
        }
        // Apply the specified argument rules/specifications
        SetSpec(spec);
        // Extract the specified argument tags
        SetArgTags(tags);
        // Set the specified minimum and maximum allowed arguments
        SetMinArgC(min);
        SetMaxArgC(max);
        // Default to no authority check
        m_Authority = -1;
        // Default to unprotected command
        m_Protected = false;
        // Default to unsuspended command
        m_Suspended = false;
        // Default to non-associative arguments
        m_Associate = false;
        // Generate information for the command
        GenerateInfo(false);
    }

    /* --------------------------------------------------------------------------------------------
     * Copy constructor. (disabled)
    */
    Listener(const Listener & o) = delete;

    /* --------------------------------------------------------------------------------------------
     * Move constructor. (disabled)
    */
    Listener(Listener && o) = delete;

    /* --------------------------------------------------------------------------------------------
     * Destructor.
    */
    ~Listener()
    {
        // Detach the command
        if (!m_Controller.Expired())
        {
            m_Controller.Lock()->Detach(this);
        }
        // Release callbacks
        m_OnExec.ReleaseGently();
        m_OnAuth.ReleaseGently();
        m_OnPost.ReleaseGently();
        m_OnFail.ReleaseGently();
    }

    /* --------------------------------------------------------------------------------------------
     * Copy assignment operator. (disabled)
    */
    Listener & operator = (const Listener & o) = delete;

    /* --------------------------------------------------------------------------------------------
     * Move assignment operator. (disabled)
    */
    Listener & operator = (Listener && o) = delete;

    /* --------------------------------------------------------------------------------------------
     * Used by the script engine to compare two instances of this type.
    */
    Int32 Cmp(const Listener & o) const
    {
        if (m_Name == o.m_Name)
        {
            return 0;
        }
        else if (m_Name.size() > o.m_Name.size())
        {
            return 1;
        }
        else
        {
            return -1;
        }
    }

    /* --------------------------------------------------------------------------------------------
     * Used by the script engine to convert this instance to a string.
    */
    const String & ToString() const
    {
        return m_Name;
    }

    /* --------------------------------------------------------------------------------------------
     * Used by the script engine to retrieve the name from instances of this type.
    */
    static SQInteger Typename(HSQUIRRELVM vm);

    /* --------------------------------------------------------------------------------------------
     * Attach the listener instance to the associated command name.
    */
    void Attach(const Manager & mgr)
    {
        // Is the associated name even valid?
        if (m_Name.empty())
        {
            STHROWF("Invalid or empty command name");
        }
        // Detach from the current command controller, if any
        Detach();
        // Is the specified manager valid to even attempt association?
        if (mgr.GetCtr())
        {
            // Attempt to associate with it's controller
            mgr.GetCtr()->Attach(NullObject(), this);
        }
    }

    /* --------------------------------------------------------------------------------------------
     * Detach the listener instance from the associated command name.
    */
    void Detach()
    {
        // Make sure that we are even associated with a controller
        if (!m_Controller.Expired())
        {
            // Detach from the associated controller
            m_Controller.Lock()->Detach(this);
            // Release the controller reference
            m_Controller.Reset();
        }
    }

    /* --------------------------------------------------------------------------------------------
     * See whether the listener instance is attached to the associated command name.
    */
    bool Attached() const
    {
        return (!m_Controller.Expired() && m_Controller.Lock()->Attached(this));
    }

    /* --------------------------------------------------------------------------------------------
     * Retrieve the manager associated with this command listener instance.
    */
    Object GetManager() const
    {
        // Are we even associated with a controller?
        if (!m_Controller)
        {
            return NullObject(); // Default to null
        }
        // Obtain the initial stack size
        const StackGuard sg;
        // Push the instance on the stack
        ClassType< Manager >::PushInstance(DefaultVM::Get(), m_Controller.Lock()->m_Manager);
        // Grab the instance from the stack
        return Var< Object >(DefaultVM::Get(), -1).value;
    }

    /* --------------------------------------------------------------------------------------------
     * Retrieve the name that triggers this command listener instance.
    */
    const String & GetName() const
    {
        return m_Name;
    }

    /* --------------------------------------------------------------------------------------------
     * Retrieve the name that triggers this command listener instance.
    */
    void SetName(CSStr name)
    {
        // Validate the specified name
        ValidateName(name);
        // Is this command already attached to a name?
        if (!m_Controller.Expired() && m_Controller.Lock()->Attached(this))
        {
            const CtrRef ctr = m_Controller.Lock();
            // Detach from the current name if necessary
            ctr->Detach(this);
            // Now it's safe to assign the new name
            m_Name.assign(name);
            // We know the new name is valid
            ctr->Attach(NullObject(), this);
        }
        else
        {
            m_Name.assign(name); // Just assign the name
        }
    }

    /* --------------------------------------------------------------------------------------------
     * Retrieve the argument specification string.
    */
    const String & GetSpec() const
    {
        return m_Spec;
    }

    /* --------------------------------------------------------------------------------------------
     * Modify the argument specification string.
    */
    void SetSpec(CSStr spec)
    {
        // Attempt to process the specified string
        ProcSpec(spec);
        // Assign the specifier, if any
        if (spec)
        {
            m_Spec.clear();
        }
        else
        {
            m_Spec.assign(spec);
        }
    }

    /* --------------------------------------------------------------------------------------------
     * Retrieve the argument tags array.
    */
    Array GetArgTags() const
    {
        // Allocate an array to encapsulate all tags
        Array arr(DefaultVM::Get(), SQMOD_MAX_CMD_ARGS);
        // Put the tags to the allocated array
        for (Uint32 arg = 0; arg < SQMOD_MAX_CMD_ARGS; ++arg)
        {
            arr.SetValue(arg, m_ArgTags[arg]);
        }
        // Return the array with the tags
        return arr;
    }

    /* --------------------------------------------------------------------------------------------
     * Modify the argument tags array.
    */
    void SetArgTags(Array & tags)
    {
        // Preliminary checks before even attempting anything
        if (tags.GetType() != OT_ARRAY || tags.IsNull())
        {
            for (Uint8 n = 0; n < SQMOD_MAX_CMD_ARGS; ++n)
            {
                m_ArgTags[n].clear();
            }
            // We're done here!
            return;
        }
        // Attempt to retrieve the number of specified tags
        const Uint32 max = ConvTo< Uint32 >::From(tags.Length());
        // If no tags were specified then clear current tags
        if (!max)
        {
            for (Uint8 n = 0; n < SQMOD_MAX_CMD_ARGS; ++n)
            {
                m_ArgTags[n].clear();
            }
        }
        // See if we're in range
        else if (max < SQMOD_MAX_CMD_ARGS)
        {
            // Attempt to get all arguments in one go
            tags.GetArray< String >(m_ArgTags, max);
        }
        else
        {
            STHROWF("Argument tag (%u) is out of range (%d)", max, SQMOD_MAX_CMD_ARGS);
        }
    }

    /* --------------------------------------------------------------------------------------------
     * Retrieve the help message associated with this command listener instance.
    */
    const String & GetHelp() const
    {
        return m_Help;
    }

    /* --------------------------------------------------------------------------------------------
     * Modify the help message associated with this command listener instance.
    */
    void SetHelp(CSStr help)
    {
        if (help)
        {
            m_Help.clear();
        }
        else
        {
            m_Help.assign(help);
        }
    }

    /* --------------------------------------------------------------------------------------------
     * Retrieve the informational message associated with this command listener instance.
    */
    const String & GetInfo() const
    {
        return m_Info;
    }

    /* --------------------------------------------------------------------------------------------
     * Modify the informational message associated with this command listener instance.
    */
    void SetInfo(CSStr info)
    {
        if (info)
        {
            m_Info.clear();
        }
        else
        {
            m_Info.assign(info);
        }
    }

    /* --------------------------------------------------------------------------------------------
     * Retrieve the authority level required to execute this command listener instance.
    */
    SQInteger GetAuthority() const
    {
        return m_Authority;
    }

    /* --------------------------------------------------------------------------------------------
     * Modify the authority level required to execute this command listener instance.
    */
    void SetAuthority(SQInteger level)
    {
        m_Authority = ConvTo< Int32 >::From(level);
    }

    /* --------------------------------------------------------------------------------------------
     * See whether this command listener instance requires explicit authority inspection.
    */
    bool GetProtected() const
    {
        return m_Protected;
    }

    /* --------------------------------------------------------------------------------------------
     * Set whether this command listener instance requires explicit authority inspection.
    */
    void SetProtected(bool toggle)
    {
        m_Protected = toggle;
    }

    /* --------------------------------------------------------------------------------------------
     * See whether this command listener instance is currently ignoring calls.
    */
    bool GetSuspended() const
    {
        return m_Suspended;
    }

    /* --------------------------------------------------------------------------------------------
     * Set whether this command listener instance should start ignoring calls.
    */
    void SetSuspended(bool toggle)
    {
        m_Suspended = toggle;
    }

    /* --------------------------------------------------------------------------------------------
     * See whether this command listener instance receives arguments in an associative container.
    */
    bool GetAssociate() const
    {
        return m_Associate;
    }

    /* --------------------------------------------------------------------------------------------
     * Set whether this command listener instance receives arguments in an associative container.
    */
    void SetAssociate(bool toggle)
    {
        m_Associate = toggle;
    }

    /* --------------------------------------------------------------------------------------------
     * Retrieve the maximum arguments supported by this command listener.
    */
    Uint8 GetMinArgC() const
    {
        return m_MinArgc;
    }

    /* --------------------------------------------------------------------------------------------
     * Modify the minimum arguments supported by this command listener.
    */
    void SetMinArgC(SQInteger val)
    {
        // Limit the specified number withing allowed range
        val = ConvTo< Uint8 >::From(val);
        // Perform a range check on the specified argument index
        if (val >= SQMOD_MAX_CMD_ARGS)
        {
            STHROWF("Argument (%d) is out of total range (%d)", val, SQMOD_MAX_CMD_ARGS);
        }
        else if (static_cast< Uint8 >(val) > m_MaxArgc)
        {
            STHROWF("Minimum argument (%d) exceeds maximum (%u)", val, m_MaxArgc);
        }
        // Apply the specified value
        m_MinArgc = static_cast< Uint8 >(val);
    }

    /* --------------------------------------------------------------------------------------------
     * Retrieve the maximum arguments supported by this command listener.
    */
    SQInteger GetMaxArgC() const
    {
        return m_MaxArgc;
    }

    /* --------------------------------------------------------------------------------------------
     * Modify the maximum arguments supported by this command listener.
    */
    void SetMaxArgC(SQInteger val)
    {
        // Limit the specified number withing allowed range
        val = ConvTo< Uint8 >::From(val);
        // Perform a range check on the specified argument index
        if (val >= SQMOD_MAX_CMD_ARGS)
        {
            STHROWF("Argument (%d) is out of total range (%d)", val, SQMOD_MAX_CMD_ARGS);
        }
        else if (static_cast< Uint8 >(val) < m_MinArgc)
        {
            STHROWF("Maximum argument (%d) exceeds minimum (%u)", val, m_MinArgc);
        }
        // Apply the specified value
        m_MaxArgc = static_cast< Uint8 >(val);
    }

    /* --------------------------------------------------------------------------------------------
     * Retrieve the function that must be called when this command listener is executed.
    */
    Function & GetOnExec()
    {
        return m_OnExec;
    }

    /* --------------------------------------------------------------------------------------------
     * Modify the function that must be called when this command listener is executed.
    */
    void SetOnExec(Object & env, Function & func)
    {
        // Make sure that we are allowed to store script resources
        if (!m_Controller)
        {
            STHROWF("Detached commands cannot store script resources");
        }
        // Apply the specified information
        m_OnExec = Function(env.GetVM(), env.GetObject(), func.GetFunc());
    }

    /* --------------------------------------------------------------------------------------------
     * Retrieve the function that must be called when this command listener needs to authenticate.
    */
    Function & GetOnAuth()
    {
        return m_OnAuth;
    }

    /* --------------------------------------------------------------------------------------------
     * Modify the function that must be called when this command listener needs to authenticate.
    */
    void SetOnAuth(Object & env, Function & func)
    {
        // Make sure that we are allowed to store script resources
        if (!m_Controller)
        {
            STHROWF("Detached commands cannot store script resources");
        }
        // Apply the specified information
        m_OnAuth = Function(env.GetVM(), env.GetObject(), func.GetFunc());
    }

    /* --------------------------------------------------------------------------------------------
     * Retrieve the function that must be called when this command listener finished execution.
    */
    Function & GetOnPost()
    {
        return m_OnPost;
    }

    /* --------------------------------------------------------------------------------------------
     * Modify the function that must be called when this command listener finished execution.
    */
    void SetOnPost(Object & env, Function & func)
    {
        // Make sure that we are allowed to store script resources
        if (!m_Controller)
        {
            STHROWF("Detached listeners cannot store script resources");
        }
        // Apply the specified information
        m_OnPost = Function(env.GetVM(), env.GetObject(), func.GetFunc());
    }

    /* --------------------------------------------------------------------------------------------
     * Retrieve the function that must be called when this command listener failed execution.
    */
    Function & GetOnFail()
    {
        return m_OnFail;
    }

    /* --------------------------------------------------------------------------------------------
     * Modify the function that must be called when this command listener failed execution.
    */
    void SetOnFail(Object & env, Function & func)
    {
        // Make sure that we are allowed to store script resources
        if (!m_Controller)
        {
            STHROWF("Detached listeners cannot store script resources");
        }
        // Apply the specified information
        m_OnFail = Function(env.GetVM(), env.GetObject(), func.GetFunc());
    }

    /* --------------------------------------------------------------------------------------------
     * Retrieve the tag of a certain argument.
    */
    const String & GetArgTag(Uint32 arg) const
    {
        // Perform a range check on the specified argument index
        if (arg >= SQMOD_MAX_CMD_ARGS)
        {
            STHROWF("Argument (%u) is out of total range (%u)", arg, SQMOD_MAX_CMD_ARGS);
        }
        // Return the requested information
        return m_ArgTags[arg];
    }

    /* --------------------------------------------------------------------------------------------
     * Modify the tag of a certain argument.
    */
    void SetArgTag(Uint32 arg, CSStr name)
    {
        // Perform a range check on the specified argument index
        if (arg >= SQMOD_MAX_CMD_ARGS)
        {
            STHROWF("Argument (%u) is out of total range (%u)", arg, SQMOD_MAX_CMD_ARGS);
        }
        // The string type doesn't appreciate null values
        else if (name != nullptr)
        {
            m_ArgTags[arg].assign(name);
        }
        // Clear previous name in this case
        else
        {
            m_ArgTags[arg].clear();
        }
    }

    /* --------------------------------------------------------------------------------------------
     * Retrieve the flags of the specified argument.
    */
    Uint8 GetArgFlags(Uint32 idx) const
    {
        // Perform a range check on the specified argument index
        if (idx >= SQMOD_MAX_CMD_ARGS)
        {
            STHROWF("Argument (%u) is out of total range (%u)", idx, SQMOD_MAX_CMD_ARGS);
        }
        // Return the requested information
        return m_ArgSpec[idx];
    }

    /* --------------------------------------------------------------------------------------------
     * See whether whether the specified argument can be used on this command listener instance.
    */
    bool ArgCheck(Uint32 arg, Uint8 flag) const
    {
        // Perform a range check on the specified argument index
        if (arg >= SQMOD_MAX_CMD_ARGS)
        {
            STHROWF("Argument (%u) is out of total range (%u)", arg, SQMOD_MAX_CMD_ARGS);
        }
        // Retrieve the argument flags
        const Uint8 f = m_ArgSpec[arg];
        // Perform the requested check
        return  (f == CMDARG_ANY) ||    // Requires check?
                (f & flag) ||           // Exact match?
                (f & CMDARG_GREEDY && flag & CMDARG_STRING);
    }

    /* --------------------------------------------------------------------------------------------
     * See whether the specified invoker entity has the proper authority to run this command.
    */
    bool AuthCheck(const Object & invoker)
    {
        // Do we need explicit authority verification?
        if (!m_Protected)
        {
            return true; // Anyone can invoke this command
        }
        // Was there a custom authority inspector specified?
        else if (!m_OnAuth.IsNull())
        {
            // Ask the specified authority inspector if this execution should be allowed
            const SharedPtr< bool > ret = m_OnAuth.Evaluate< bool, const Object &, Listener * >(invoker, this);
            // See what the custom authority inspector said or default to disallow
            return !ret ? false : *ret;
        }
        // Was there a global authority inspector specified?
        else if (!m_Controller.Expired() && !m_Controller.Lock()->GetOnAuth().IsNull())
        {
            // Ask the specified authority inspector if this execution should be allowed
            const SharedPtr< bool > ret = m_Controller.Lock()->GetOnAuth().Evaluate< bool, const Object &, Listener * >(invoker, this);
            // See what the custom authority inspector said or default to disallow
            return !ret ? false : *ret;
        }
        // A negative authority level is considered to be the same as unprotected
        else if (m_Authority < 0)
        {
            return true;
        }
        // Default to blocking this execution
        return false;
    }

    /* --------------------------------------------------------------------------------------------
     * Use the command listener argument properties to generate an informational message.
    */
    void GenerateInfo(bool full);

protected:

    // --------------------------------------------------------------------------------------------
    typedef Uint8   ArgSpec[SQMOD_MAX_CMD_ARGS];
    typedef String  ArgTags[SQMOD_MAX_CMD_ARGS];

    /* --------------------------------------------------------------------------------------------
     * Execute the designated callback by passing the arguments in their specified order.
    */
    SQInteger Execute(const Object & invoker, const Array & args)
    {
        // Attempt to evaluate the specified executer knowing the manager did the validations
        SharedPtr< SQInteger > ret = m_OnExec.Evaluate< SQInteger, const Object &, const Array & >(invoker, args);
        // See if the executer succeeded and return the result or default to failed
        return (!ret ? 0 : *ret);
    }

    /* --------------------------------------------------------------------------------------------
     * Execute the designated callback by passing the arguments using an associative container.
    */
    SQInteger Execute(const Object & invoker, const Table & args)
    {
        // Attempt to evaluate the specified executer knowing the manager did the validations
        SharedPtr< SQInteger > ret = m_OnExec.Evaluate< SQInteger, const Object &, const Table & >(invoker, args);
        // See if the executer succeeded and return the result or default to failed
        return (!ret ? 0 : *ret);
    }

    /* --------------------------------------------------------------------------------------------
     * Process the specified string and extract the argument properties in it.
    */
    void ProcSpec(CSStr spec);

private:

    // --------------------------------------------------------------------------------------------
    CtrWRef     m_Controller; // Manager that controls this command listener.

    // --------------------------------------------------------------------------------------------
    String      m_Name; // Name of the command that triggers this listener.

    // --------------------------------------------------------------------------------------------
    ArgSpec     m_ArgSpec; // List of argument type specifications.
    ArgTags     m_ArgTags; // List of argument tags/names.

    // --------------------------------------------------------------------------------------------
    Uint8       m_MinArgc; // Minimum number of arguments supported by this listener.
    Uint8       m_MaxArgc; // Maximum number of arguments supported by this listener.

    // --------------------------------------------------------------------------------------------
    String      m_Spec; // String used to generate the argument type specification list.
    String      m_Help; // String describing what the command is supposed to do.
    String      m_Info; // String with syntax information for the command.

    // --------------------------------------------------------------------------------------------
    Function    m_OnExec; // Function to call when the command is executed.
    Function    m_OnAuth; // Function to call when the invoker must be authenticated.
    Function    m_OnPost; // Function to call after the command was successfully executed.
    Function    m_OnFail; // Function to call after the command execution failed.

    // --------------------------------------------------------------------------------------------
    Int32       m_Authority; // Built-in authority level required to execute this command.

    // --------------------------------------------------------------------------------------------
    bool        m_Protected; // Whether explicit authentication of the invoker is required.
    bool        m_Suspended; // Whether this command should block further invocations.
    bool        m_Associate; // Whether arguments are sent as table instead of array.
};

} // Namespace:: Cmd
} // Namespace:: SqMod

#endif // _COMMAND_HPP_