#ifndef _ROUTINE_HPP_
#define _ROUTINE_HPP_

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

// ------------------------------------------------------------------------------------------------
#include <array>
#include <utility>
#include <unordered_map>

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

/* ------------------------------------------------------------------------------------------------
 * Execute callbacks after specific intervals of time.
*/
class Routine
{
public:

    /* --------------------------------------------------------------------------------------------
     * Simplify future changes to a single point of change.
    */
    typedef Int64                                       Time;
    typedef SQInteger                                   Interval;
    typedef Uint32                                      Iterator;
    typedef std::pair< Interval, Routine * >            Element;
    typedef std::array< Element, SQMOD_MAX_ROUTINES >   Routines;
    typedef std::unordered_map< Routine *, Object >     Objects;

    /* --------------------------------------------------------------------------------------------
     * Process all active routines and update elapsed time.
    */
    static void Process();

    /* --------------------------------------------------------------------------------------------
     * Initialize all resources and prepare for startup.
    */
    static void Initialize();

    /* --------------------------------------------------------------------------------------------
     * Release all resources and prepare for shutdown.
    */
    static void Deinitialize();

protected:

    // --------------------------------------------------------------------------------------------
    static Time         s_Last; // Last time point.
    static Time         s_Prev; // Previous time point.
    static Routines     s_Routines; // Buckets of routines grouped by similar intervals.
    static Objects      s_Objects; // List of existing routines and their associated object.

protected:

    /* --------------------------------------------------------------------------------------------
     * Create or locate the object for the specified routine and keep a strong reference to it.
    */
    static Object Associate(Routine * routine);

    /* --------------------------------------------------------------------------------------------
     * Release the strong reference associated with the specified routine so it can be destroyed.
    */
    static void Dissociate(Routine * routine);

    /* --------------------------------------------------------------------------------------------
     * See whether the specified routine exists in the pool and references itself.
    */
    static bool Associated(Routine * routine);

    /* --------------------------------------------------------------------------------------------
     * Remove the specified routine from the pool and any associated reference, if any.
    */
    static void Forget(Routine * routine);

    /* --------------------------------------------------------------------------------------------
     * Insert a routine instance into the pool to be processed.
    */
    static void Insert(Routine * routine, bool associate = true);

    /* --------------------------------------------------------------------------------------------
     * Insert a routine instance from the pool to not be processed.
    */
    static void Remove(Routine * routine);

    /* --------------------------------------------------------------------------------------------
     * Release routine resources.
    */
    void Release();

    /* --------------------------------------------------------------------------------------------
     * Execute the binded callback.
    */
    void Execute();

    /* --------------------------------------------------------------------------------------------
     * See whether this routine is valid otherwise throw an exception.
    */
    void Validate() const
    {
        if (m_Terminated)
        {
            STHROWF("Routine was terminated [%s]", m_Tag.c_str());
        }
    }

private:

    /* --------------------------------------------------------------------------------------------
     * Constructor with just an interval.
    */
    Routine(Object & env, Function & func, Interval interval);

    /* --------------------------------------------------------------------------------------------
     * Constructor with just an interval and explicit iterations.
    */
    Routine(Object & env, Function & func, Interval interval, Iterator iterations);

    /* --------------------------------------------------------------------------------------------
     * Constructor with just an interval, explicit iterations and arguments.
    */
    Routine(Object & env, Function & func, Interval interval, Iterator iterations
            , Object & a1);

    /* --------------------------------------------------------------------------------------------
     * Constructor with just an interval, explicit iterations and arguments.
    */
    Routine(Object & env, Function & func, Interval interval, Iterator iterations
            , Object & a1, Object & a2);

    /* --------------------------------------------------------------------------------------------
     * Constructor with just an interval, explicit iterations and arguments.
    */
    Routine(Object & env, Function & func, Interval interval, Iterator iterations
            , Object & a1, Object & a2, Object & a3);

    /* --------------------------------------------------------------------------------------------
     * Constructor with just an interval, explicit iterations and arguments.
    */
    Routine(Object & env, Function & func, Interval interval, Iterator iterations
            , Object & a1, Object & a2, Object & a3, Object & a4);

    /* --------------------------------------------------------------------------------------------
     * Constructor with just an interval, explicit iterations and arguments.
    */
    Routine(Object & env, Function & func, Interval interval, Iterator iterations
            , Object & a1, Object & a2, Object & a3, Object & a4, Object & a5);

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

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

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

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

private:

    /* --------------------------------------------------------------------------------------------
     * Number of iterations before self destruct.
    */
    Iterator     m_Iterations;

    /* --------------------------------------------------------------------------------------------
     * Interval between calls.
    */
    Interval    m_Interval;

    /* --------------------------------------------------------------------------------------------
     * The index of the slot in the pool.
    */
    Uint16       m_Slot;

    /* --------------------------------------------------------------------------------------------
     * Number of arguments to forward.
    */
    Uint16       m_Arguments;

    /* --------------------------------------------------------------------------------------------
     * Whether calls should be ignored.
    */
    bool        m_Suspended;

    /* --------------------------------------------------------------------------------------------
     * Whether the routine was terminated.
    */
    bool        m_Terminated;

    /* --------------------------------------------------------------------------------------------
     * The callback to be executed when triggered.
    */
    Function    m_Callback;

    /* --------------------------------------------------------------------------------------------
     * User tag associated with this instance.
    */
    String      m_Tag;

    /* --------------------------------------------------------------------------------------------
     * User data associated with this instance.
    */
    Object      m_Data;

    /* --------------------------------------------------------------------------------------------
     * Arguments to be forwarded to the callback.
    */
    Object      m_Arg1, m_Arg2, m_Arg3, m_Arg4, m_Arg5, m_Arg6, m_Arg7,
                m_Arg8, m_Arg9, m_Arg10, m_Arg11, m_Arg12, m_Arg13, m_Arg14;

public:

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

    /* --------------------------------------------------------------------------------------------
     * Used by the script engine to compare two instances of this type.
    */
    Int32 Cmp(const Routine & o) const;

    /* --------------------------------------------------------------------------------------------
     * Used by the script engine to convert an instance of this type to a string.
    */
    CSStr ToString() const;

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

    /* --------------------------------------------------------------------------------------------
     * Retrieve the associated user tag.
    */
    const String & GetTag() const;

    /* --------------------------------------------------------------------------------------------
     * Modify the associated user tag.
    */
    void SetTag(CSStr tag);

    /* --------------------------------------------------------------------------------------------
     * Retrieve the associated user data.
    */
    Object & GetData();

    /* --------------------------------------------------------------------------------------------
     * Modify the associated user data.
    */
    void SetData(Object & data);

    /* --------------------------------------------------------------------------------------------
     * Modify the associated user tag and allow chaining of operations.
    */
    Routine & ApplyTag(CSStr tag);

    /* --------------------------------------------------------------------------------------------
     * Modify the associated user data and allow chaining of operations.
    */
    Routine & ApplyData(Object & data);

    /* --------------------------------------------------------------------------------------------
     * Activate the routine instance.
    */
    Routine & Activate();

    /* --------------------------------------------------------------------------------------------
     * Deactivate the routine instance.
    */
    Routine & Deactivate();

    /* --------------------------------------------------------------------------------------------
     * Terminate this routine by deactivating and releasing all resources.
    */
    Routine & Terminate();

    /* --------------------------------------------------------------------------------------------
     * Unmark this routine as terminated and allow to be activated.
    */
    Routine & Reanimate();

    /* --------------------------------------------------------------------------------------------
     * Modify an explicit value to be passed as the specified argument.
    */
    Routine & SetArg(Uint16 num, Object & val);

    /* --------------------------------------------------------------------------------------------
     * Retrieve the value that is passed as the specified argument.
    */
    Object & GetArg(Uint16 num);

    /* --------------------------------------------------------------------------------------------
     * Retrieve the amount of time required to wait between calls to the routine.
    */
    Interval GetInterval() const;

    /* --------------------------------------------------------------------------------------------
     * Modify the amount of time required to wait between calls to the routine.
    */
    void SetInterval(Interval interval);

    /* --------------------------------------------------------------------------------------------
     * Retrieve the number of times that the routine can be called before terminating itself.
    */
    Iterator GetIterations() const;

    /* --------------------------------------------------------------------------------------------
     * Modify the number of times that the routine can be called before terminating itself.
    */
    void SetIterations(Iterator iterations);

    /* --------------------------------------------------------------------------------------------
     * Retrieve the number of arguments that are forwarded when executing the callback.
    */
    Uint16 GetArguments() const;

    /* --------------------------------------------------------------------------------------------
     * Modify the number of arguments that are forwarded when executing the callback.
    */
    void SetArguments(Uint16 num);

    /* --------------------------------------------------------------------------------------------
     * See whether the routine is suspended from further calls.
    */
    bool GetSuspended() const;

    /* --------------------------------------------------------------------------------------------
     * Set whether the routine is suspended from further calls.
    */
    void SetSuspended(bool toggle);

    /* --------------------------------------------------------------------------------------------
     * See whether the routine was terminated or not.
    */
    bool GetTerminated() const;

    /* --------------------------------------------------------------------------------------------
     * Retrieve the currently binded callback.
    */
    Function & GetCallback();

    /* --------------------------------------------------------------------------------------------
     * Bind a certain function to be executed when this routine is triggered.
    */
    void SetCallback(Object & env, Function & func);

public:

    /* --------------------------------------------------------------------------------------------
     * Create a routine with just an interval.
    */
    static Object Create(Object & env, Function & func, Interval interval);

    /* --------------------------------------------------------------------------------------------
     * Create a routine with just an interval and explicit iterations.
    */
    static Object Create(Object & env, Function & func, Interval interval, Iterator iterations);

    /* --------------------------------------------------------------------------------------------
     * Create a routine with just an interval, explicit iterations and arguments.
    */
    static Object Create(Object & env, Function & func, Interval interval, Iterator iterations
                            , Object & a1);

    /* --------------------------------------------------------------------------------------------
     * Create a routine with just an interval, explicit iterations and arguments.
    */
    static Object Create(Object & env, Function & func, Interval interval, Iterator iterations
                            , Object & a1, Object & a2);

    /* --------------------------------------------------------------------------------------------
     * Create a routine with just an interval, explicit iterations and arguments.
    */
    static Object Create(Object & env, Function & func, Interval interval, Iterator iterations
                            , Object & a1, Object & a2, Object & a3);

    /* --------------------------------------------------------------------------------------------
     * Create a routine with just an interval, explicit iterations and arguments.
    */
    static Object Create(Object & env, Function & func, Interval interval, Iterator iterations
                            , Object & a1, Object & a2, Object & a3, Object & a4);

    /* --------------------------------------------------------------------------------------------
     * Create a routine with just an interval, explicit iterations and arguments.
    */
    static Object Create(Object & env, Function & func, Interval interval, Iterator iterations
                            , Object & a1, Object & a2, Object & a3, Object & a4, Object & a5);

    /* --------------------------------------------------------------------------------------------
     * Attempt to find a certain routine by its associated tag.
    */
    static Object FindByTag(CSStr tag);
};

} // Namespace:: SqMod

#endif // _ROUTINE_HPP_