#pragma once

// ------------------------------------------------------------------------------------------------
#include "Core/Utility.hpp"
#include "Core/Signal.hpp"

// ------------------------------------------------------------------------------------------------
#include "Library/DPP/Other.hpp"
#include "Library/DPP/Constants.hpp"

// ------------------------------------------------------------------------------------------------
#include <chrono>
#include <memory>
#include <functional>

// ------------------------------------------------------------------------------------------------
#include <concurrentqueue.h>
#include <dpp/dpp.h>

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

/* ------------------------------------------------------------------------------------------------
 * Internal event proxy.
*/
struct DpInternalEvent
{
    /* --------------------------------------------------------------------------------------------
     * Event data.
    */
    uint64_t mData{0llu};

    /* --------------------------------------------------------------------------------------------
     * Default constructor.
    */
    DpInternalEvent() noexcept = default;

    /* --------------------------------------------------------------------------------------------
     * Explicit constructor.
    */
    DpInternalEvent(uint64_t type, void * data) noexcept
        : mData((type << 56u) | reinterpret_cast< uint64_t >(data))
    {
    }

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

    /* --------------------------------------------------------------------------------------------
     * Move constructor.
    */
    DpInternalEvent(DpInternalEvent && o) noexcept
        : mData(o.mData)
    {
        o.mData = 0llu; // Take ownership
    }

    /* --------------------------------------------------------------------------------------------
     * Destructor.
    */
    ~DpInternalEvent()
    {
        Release();
    }

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

    /* --------------------------------------------------------------------------------------------
     * Move assignment operator.
    */
    DpInternalEvent & operator = (DpInternalEvent && o) noexcept
    {
        if (mData != o.mData)
        {
            // Release current information
            Release();
            // Replicate members
            mData = o.mData;
            // Take ownership
            o.mData = 0llu;
        }
        return *this;
    }

    /* --------------------------------------------------------------------------------------------
     * Forget about the managed event data.
    */
    void Reset() noexcept
    {
        mData = 0llu;
    }

    /* --------------------------------------------------------------------------------------------
     * Retrieve the event type.
    */
    SQMOD_NODISCARD uint8_t GetType() const noexcept
    {
        return static_cast< uint8_t >((mData >> 56u) & 0xFFllu);
    }

    /* --------------------------------------------------------------------------------------------
     * Retrieve the event data.
    */
    SQMOD_NODISCARD uintptr_t GetData() const noexcept
    {
        return static_cast< uintptr_t >((~(0xFFllu << 56u)) & mData);
    }

    /* --------------------------------------------------------------------------------------------
     * Release associated event data, if any.
    */
    void Release();
};

/* ------------------------------------------------------------------------------------------------
 * The cluster class represents a group of shards and a command queue for sending and receiving
 * commands from discord via HTTP.
*/
struct DpCluster : public SqChainedInstances< DpCluster >
{
    /* --------------------------------------------------------------------------------------------
     * Queue of events generated from other threads.
    */
    using EventQueue = moodycamel::ConcurrentQueue< DpInternalEvent >;

    /* --------------------------------------------------------------------------------------------
     * Managed cluster instance.
    */
    std::unique_ptr< dpp::cluster > mC{nullptr};

    /* --------------------------------------------------------------------------------------------
     * Event queue.
    */
    EventQueue mQueue{4096};

    /* --------------------------------------------------------------------------------------------
     * Explicit constructor.
    */
    explicit DpCluster(StackStrF & token)
        : mC(std::make_unique< dpp::cluster >(token.ToStr()))
        , mQueue(4096)
    {
        Initialize();
    }

    /* --------------------------------------------------------------------------------------------
     * Explicit constructor.
    */
    DpCluster(StackStrF & token, SQInteger intents)
        : mC(std::make_unique< dpp::cluster >(token.ToStr(), static_cast< uint32_t >(intents)))
        , mQueue(4096)
    {
        Initialize();
    }

    /* --------------------------------------------------------------------------------------------
     * Explicit constructor.
    */
    DpCluster(StackStrF & token, SQInteger intents, SQInteger shards)
        : mC(std::make_unique< dpp::cluster >(token.ToStr(), static_cast< uint32_t >(intents), static_cast< uint32_t >(shards)))
        , mQueue(4096)
    {
        Initialize();
    }

    /* --------------------------------------------------------------------------------------------
     * Explicit constructor.
    */
    DpCluster(StackStrF & token, SQInteger intents, SQInteger shards, SQInteger cluster_id)
        : mC(std::make_unique< dpp::cluster >(token.ToStr(), static_cast< uint32_t >(intents), static_cast< uint32_t >(shards), static_cast< uint32_t >(cluster_id)))
        , mQueue(4096)
    {
        Initialize();
    }

    /* --------------------------------------------------------------------------------------------
     * Explicit constructor.
    */
    DpCluster(StackStrF & token, SQInteger intents, SQInteger shards, SQInteger cluster_id, SQInteger max_clusters)
        : mC(std::make_unique< dpp::cluster >(token.ToStr(), static_cast< uint32_t >(intents), static_cast< uint32_t >(shards), static_cast< uint32_t >(cluster_id), static_cast< uint32_t >(max_clusters)))
        , mQueue(4096)
    {
        Initialize();
    }

    /* --------------------------------------------------------------------------------------------
     * Explicit constructor.
    */
    DpCluster(StackStrF & token, SQInteger intents, SQInteger shards, SQInteger cluster_id, SQInteger max_clusters, bool compressed)
        : mC(std::make_unique< dpp::cluster >(token.ToStr(), static_cast< uint32_t >(intents), static_cast< uint32_t >(shards), static_cast< uint32_t >(cluster_id), static_cast< uint32_t >(max_clusters), compressed))
        , mQueue(4096)
    {
        Initialize();
    }

    /* --------------------------------------------------------------------------------------------
     * Explicit constructor.
    */
    DpCluster(StackStrF & token, SQInteger intents, SQInteger shards, SQInteger cluster_id, SQInteger max_clusters, bool compressed, const DpCachePolicy & cp)
        : mC(std::make_unique< dpp::cluster >(token.ToStr(), static_cast< uint32_t >(intents), static_cast< uint32_t >(shards), static_cast< uint32_t >(cluster_id), static_cast< uint32_t >(max_clusters), compressed, cp.ToNative()))
        , mQueue(4096)
    {
        Initialize();
    }

    /* --------------------------------------------------------------------------------------------
     * Destructor.
    */
    ~DpCluster()
    {
        DropEvents();
        // Forget about this instance
        UnchainInstance();
    }

    /* --------------------------------------------------------------------------------------------
     * Start the cluster, connecting all its shards. Returns once all shards are connected.
    */
    DpCluster & Start()
    {
        LogInf("Before start...");
        mC->start(true);
        LogInf("After start...");
        return *this;
    }

    /* --------------------------------------------------------------------------------------------
     * Log a message to whatever log the user is using.
    */
    DpCluster & Log(SQInteger severity, StackStrF & message)
    {
        mC->log(static_cast< dpp::loglevel >(severity), message.ToStr());
        return *this;
    }

    /* --------------------------------------------------------------------------------------------
     * Get the dm channel for a user id.
    */
    SQMOD_NODISCARD dpp::snowflake GetDmChannel(dpp::snowflake user_id) const
    {
        return mC->get_dm_channel(static_cast< dpp::snowflake >(user_id));
    }

    /* --------------------------------------------------------------------------------------------
     * Set the dm channel id for a user id.
    */
    DpCluster & SetDmChannel(dpp::snowflake user_id, dpp::snowflake channel_id)
    {
        mC->set_dm_channel(user_id, channel_id);
        return *this;
    }

    /* --------------------------------------------------------------------------------------------
     * Returns the uptime of the cluster.
    */
    SQMOD_NODISCARD dpp::utility::uptime UpTime() const
    {
        return mC->uptime();
    }

    /* --------------------------------------------------------------------------------------------
     * Returns the uptime of the cluster.
    */
    DpCluster & SetPresence(const DpPresence & p)
    {
        mC->set_presence(p.Valid());
        return *this;
    }

    // --------------------------------------------------------------------------------------------
    LightObj mSqEvents{}; // Table containing the emitted cluster events.

    /* --------------------------------------------------------------------------------------------
     * Retrieve the events table of this cluster.
    */
    SQMOD_NODISCARD LightObj & GetEvents()
    {
        return mSqEvents;
    }

    /* --------------------------------------------------------------------------------------------
     * Cluster signals.
    */
    std::array< SignalPair, static_cast< size_t >(DpEventID::Max) > mEvents{};

    /* --------------------------------------------------------------------------------------------
     * Process the cluster.
    */
    void Process(bool force = false);

    /* --------------------------------------------------------------------------------------------
     * Terminate the cluster.
    */
    void Terminate()
    {
        // Delete the cluster instance
        mC.reset();
        // Release associated script objects
        mSqEvents.Release();
        // Release event signal objects
        DropEvents();
    }

    /* --------------------------------------------------------------------------------------------
     * Enable a certain event for the cluster.
    */
    DpCluster & EnableEvent(SQInteger id);

    /* --------------------------------------------------------------------------------------------
     * Disable a certain event for the cluster.
    */
    DpCluster & DisableEvent(SQInteger id);

private:

    /* --------------------------------------------------------------------------------------------
     * Initialize the cluster.
    */
    void Initialize()
    {
        InitEvents();
        // Remember this instance
        ChainInstance();
    }

    /* --------------------------------------------------------------------------------------------
     * Signal initialization.
    */
    void InitEvents()
    {
        // Ignore the call if already initialized
        if (!mSqEvents.IsNull())
        {
            return;
        }
        // Create a new table on the stack
        sq_newtableex(SqVM(), 64);
        // Grab the table object from the stack
        mSqEvents = LightObj(-1, SqVM());
        // Pop the table object from the stack
        sq_pop(SqVM(), 1);
        // Proceed to initializing the events
        for (size_t i = 0; i < mEvents.size(); ++i)
        {
            InitSignalPair(mEvents[i], mSqEvents, DpEventID::NAME[i]);
        }
    }

    /* --------------------------------------------------------------------------------------------
     * Signal termination.
    */
    void DropEvents()
    {
        for (auto & e : mEvents)
        {
            ResetSignalPair(e);
        }
    }

    /* --------------------------------------------------------------------------------------------
     * Event handlers.
    */
    void OnVoiceStateUpdate(const dpp::voice_state_update_t & ev);
    void OnLog(const dpp::log_t & ev);
    void OnGuildJoinRequestDelete(const dpp::guild_join_request_delete_t & ev);
    void OnInteractionCreate(const dpp::interaction_create_t & ev);
    void OnButtonClick(const dpp::button_click_t & ev);
    void OnSelectClick(const dpp::select_click_t & ev);
    void OnGuildDelete(const dpp::guild_delete_t & ev);
    void OnChannelDelete(const dpp::channel_delete_t & ev);
    void OnChannelUpdate(const dpp::channel_update_t & ev);
    void OnReady(const dpp::ready_t & ev);
    void OnMessageDelete(const dpp::message_delete_t & ev);
    void OnApplicationCommandDelete(const dpp::application_command_delete_t & ev);
    void OnGuildMemberRemove(const dpp::guild_member_remove_t & ev);
    void OnApplicationCommandCreate(const dpp::application_command_create_t & ev);
    void OnResumed(const dpp::resumed_t & ev);
    void OnGuildRoleCreate(const dpp::guild_role_create_t & ev);
    void OnTypingStart(const dpp::typing_start_t & ev);
    void OnMessageReactionAdd(const dpp::message_reaction_add_t & ev);
    void OnGuildMembersChunk(const dpp::guild_members_chunk_t & ev);
    void OnMessageReactionRemove(const dpp::message_reaction_remove_t & ev);
    void OnGuildCreate(const dpp::guild_create_t & ev);
    void OnChannelCreate(const dpp::channel_create_t & ev);
    void OnMessageReactionRemoveEmoji(const dpp::message_reaction_remove_emoji_t & ev);
    void OnMessageDeleteBulk(const dpp::message_delete_bulk_t & ev);
    void OnGuildRoleUpdate(const dpp::guild_role_update_t & ev);
    void OnGuildRoleDelete(const dpp::guild_role_delete_t & ev);
    void OnChannelPinsUpdate(const dpp::channel_pins_update_t & ev);
    void OnMessageReactionRemoveAll(const dpp::message_reaction_remove_all_t & ev);
    void OnVoiceServerUpdate(const dpp::voice_server_update_t & ev);
    void OnGuildEmojisUpdate(const dpp::guild_emojis_update_t & ev);
    void OnGuildStickersUpdate(const dpp::guild_stickers_update_t & ev);
    void OnPresenceUpdate(const dpp::presence_update_t & ev);
    void OnWebhooksUpdate(const dpp::webhooks_update_t & ev);
    void OnGuildMemberAdd(const dpp::guild_member_add_t & ev);
    void OnInviteDelete(const dpp::invite_delete_t & ev);
    void OnGuildUpdate(const dpp::guild_update_t & ev);
    void OnGuildIntegrationsUpdate(const dpp::guild_integrations_update_t & ev);
    void OnGuildMemberUpdate(const dpp::guild_member_update_t & ev);
    void OnApplicationCommandUpdate(const dpp::application_command_update_t & ev);
    void OnInviteCreate(const dpp::invite_create_t & ev);
    void OnMessageUpdate(const dpp::message_update_t & ev);
    void OnUserUpdate(const dpp::user_update_t & ev);
    void OnMessageCreate(const dpp::message_create_t & ev);
    void OnGuildBanAdd(const dpp::guild_ban_add_t & ev);
    void OnGuildBanRemove(const dpp::guild_ban_remove_t & ev);
    void OnIntegrationCreate(const dpp::integration_create_t & ev);
    void OnIntegrationUpdate(const dpp::integration_update_t & ev);
    void OnIntegrationDelete(const dpp::integration_delete_t & ev);
    void OnThreadCreate(const dpp::thread_create_t & ev);
    void OnThreadUpdate(const dpp::thread_update_t & ev);
    void OnThreadDelete(const dpp::thread_delete_t & ev);
    void OnThreadListSync(const dpp::thread_list_sync_t & ev);
    void OnThreadMemberUpdate(const dpp::thread_member_update_t & ev);
    void OnThreadMembersUpdate(const dpp::thread_members_update_t & ev);
    void OnVoiceBufferSend(const dpp::voice_buffer_send_t & ev);
    void OnVoiceUserTalking(const dpp::voice_user_talking_t & ev);
    void OnVoiceReady(const dpp::voice_ready_t & ev);
    void OnVoiceReceive(const dpp::voice_receive_t & ev);
    void OnVoiceTrackMarker(const dpp::voice_track_marker_t & ev);
    void OnStageInstanceCreate(const dpp::stage_instance_create_t & ev);
    void OnStageInstanceDelete(const dpp::stage_instance_delete_t & ev);
};

} // Namespace:: SqMod