// ------------------------------------------------------------------------------------------------
#include "Misc/Signal.hpp"

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

// ------------------------------------------------------------------------------------------------
Signal::SignalContainer Signal::s_Signals;
Signal::FreeSignals     Signal::s_FreeSignals;

// ------------------------------------------------------------------------------------------------
SQInteger Signal::Typename(HSQUIRRELVM vm)
{
    static const SQChar name[] = _SC("SqSignal");
    sq_pushstring(vm, name, sizeof(name));
    return 1;
}

// ------------------------------------------------------------------------------------------------
void Signal::Terminate()
{
    // Terminate named signals
    for (const auto & s : s_Signals)
    {
        // Clear slots
        s.second.mPtr->Clear();
        // Release the name
        s.second.mPtr->m_Name.clear();
    }
    // Finally clear the container itself
    s_Signals.clear();
    // Create a copy so we don't invalidate iterators when destructor removes the instances
    FreeSignals fsig(s_FreeSignals);
    // Terminate anonymous signals
    for (const auto & s : fsig)
    {
        // Clear slots
        s->Clear();
    }
    // Finally clear the container itself
    s_FreeSignals.clear();
}

// ------------------------------------------------------------------------------------------------
Object Signal::Create()
{
    // Remember the current stack size
    const StackGuard sg;
    // Create the signal instance
    DeleteGuard< Signal > dg(new Signal());
    // Attempt to create the signal instance
    ClassType< Signal >::PushInstance(DefaultVM::Get(), dg.Get());
    // This is now managed by the script
    dg.Release();
    // Return the created signal
    return Var< Object >(DefaultVM::Get(), -1).value;
}

// ------------------------------------------------------------------------------------------------
Object Signal::Create(String name)
{
    // Validate the signal name
    if (name.empty())
    {
        STHROWF("Empty signal names are not allowed");
    }
    // Compute the hash of the specified name
    const std::size_t hash = std::hash< String >{}(name);
    // See if the signal already exists
    for (const auto & e : s_Signals)
    {
        if (e.first == hash)
        {
            return e.second.mObj; // Found a match so let's return it
        }
    }
    // Remember the current stack size
    const StackGuard sg;
    // Create the signal instance
    DeleteGuard< Signal > dg(new Signal(std::move(name)));
    // Grab the signal instance pointer
    Signal * ptr = dg.Get();
    // Attempt to create the signal instance
    ClassType< Signal >::PushInstance(DefaultVM::Get(), ptr);
    // This is now managed by the script
    dg.Release();
    // Grab a reference to the instance created on the stack
    s_Signals.emplace_back(hash, SignalReference(ptr, Var< Object >(DefaultVM::Get(), -1).value));
    // Return the created signal
    return s_Signals.back().second.mObj;
}

// ------------------------------------------------------------------------------------------------
void Signal::Remove(String name)
{
    // Validate the signal name
    if (name.empty())
    {
        STHROWF("Empty signal names are not allowed");
    }
    // Compute the hash of the specified name
    const std::size_t hash = std::hash< String >{}(name);
    // Iterator to the existing signal, if any
    SignalContainer::const_iterator itr = s_Signals.cbegin();
    // Search for a signal with this name
    for (; itr != s_Signals.cend(); ++itr)
    {
        if (itr->first == hash)
        {
            break;
        }
    }
    // Did we find anything?
    if (itr != s_Signals.cend())
    {
        // Clear the name
        itr->second.mPtr->m_Name.clear();
        // Put it on the free list
        s_FreeSignals.push_back(itr->second.mPtr);
        // Finally, remove it from the named list
        s_Signals.erase(itr);
    }
}

// ------------------------------------------------------------------------------------------------
Object Signal::Fetch(String name)
{
    // Validate the signal name
    if (name.empty())
    {
        STHROWF("Empty signal names are not allowed");
    }
    // Compute the hash of the specified name
    const std::size_t hash = std::hash< String >{}(name);
    // Search for a signal with this name
    for (const auto & e : s_Signals)
    {
        if (e.first == hash)
        {
            return e.second.mObj; // Found a match so let's return it
        }
    }
    // No such signal exists
    STHROWF("Unknown signal named (%s)", name.c_str());
    // SHOULD NOT REACH THIS POINT!
    return NullObject();
}

// ------------------------------------------------------------------------------------------------
static SQInteger SqCreateSignal(HSQUIRRELVM vm)
{
    const Int32 top = sq_gettop(vm);
    // Was the signal name specified?
    if (top <= 1)
    {
        sq_pushobject(DefaultVM::Get(), Signal::Create());
    }
    else
    {
        // Attempt to generate the string value
        StackStrF val(vm, 2);
        // Have we failed to retrieve the string?
        if (SQ_FAILED(val.mRes))
        {
            return val.mRes; // Propagate the error!
        }
        // Create the signal instance and push it on the stack
        try
        {
            sq_pushobject(DefaultVM::Get(), Signal::Create(String(val.mPtr, val.mLen)));
        }
        catch (const Sqrat::Exception & e)
        {
            return sq_throwerror(vm, e.what());
        }
    }
    // We have an argument on the stack
    return 1;
}

// ------------------------------------------------------------------------------------------------
static SQInteger SqRemoveSignal(HSQUIRRELVM vm)
{
    const Int32 top = sq_gettop(vm);
    // Was the signal name specified?
    if (top <= 1)
    {
        return sq_throwerror(vm, "Missing signal name");
    }
    // Attempt to generate the string value
    StackStrF val(vm, 2);
    // Have we failed to retrieve the string?
    if (SQ_FAILED(val.mRes))
    {
        return val.mRes; // Propagate the error!
    }
    // Create the signal instance and push it on the stack
    try
    {
        Signal::Remove(String(val.mPtr, val.mLen));
    }
    catch (const Sqrat::Exception & e)
    {
        return sq_throwerror(vm, e.what());
    }
    // We have an argument on the stack
    return 1;
}

// ------------------------------------------------------------------------------------------------
static SQInteger SqFetchSignal(HSQUIRRELVM vm)
{
    const Int32 top = sq_gettop(vm);
    // Was the signal name specified?
    if (top <= 1)
    {
        return sq_throwerror(vm, "Missing signal name");
    }
    // Attempt to generate the string value
    StackStrF val(vm, 2);
    // Have we failed to retrieve the string?
    if (SQ_FAILED(val.mRes))
    {
        return val.mRes; // Propagate the error!
    }
    // Create the signal instance and push it on the stack
    try
    {
        sq_pushobject(DefaultVM::Get(), Signal::Fetch(String(val.mPtr, val.mLen)));
    }
    catch (const Sqrat::Exception & e)
    {
        return sq_throwerror(vm, e.what());
    }
    // We have an argument on the stack
    return 1;
}

// ================================================================================================
void Register_Signal(HSQUIRRELVM vm)
{
    RootTable(vm).Bind(_SC("SqSignalWrapper"),
        Class< Signal, NoConstructor< Signal > >(vm, _SC("SqSignalWrapper"))
        // Meta-methods
        .Func(_SC("_cmp"), &Signal::Cmp)
        .SquirrelFunc(_SC("_typename"), &Signal::Typename)
        .Func(_SC("_tostring"), &Signal::ToString)
        // Core Properties
        .Prop(_SC("Slots"), &Signal::Count)
        // Core Methods
        .Func(_SC("Clear"), &Signal::Clear)
        .Func(_SC("Connect"), &Signal::Connect)
        .Func(_SC("Connected"), &Signal::Connected)
        .Func(_SC("Disconnect"), &Signal::Disconnect)
        .Func(_SC("EliminateThis"), &Signal::DisconnectThis)
        .Func(_SC("EliminateFunc"), &Signal::DisconnectFunc)
        .Func(_SC("CountThis"), &Signal::CountThis)
        .Func(_SC("CountFunc"), &Signal::CountFunc)
        .Func(_SC("Head"), &Signal::Head)
        .Func(_SC("Tail"), &Signal::Tail)
        // Squirrel Functions
        .SquirrelFunc(_SC("Emit"), &Signal::SqEmit)
        .SquirrelFunc(_SC("Broadcast"), &Signal::SqEmit)
        .SquirrelFunc(_SC("Query"), &Signal::SqQuery)
        .SquirrelFunc(_SC("Consume"), &Signal::SqConsume)
    );

    RootTable(vm)
        .SquirrelFunc(_SC("SqSignal"), &SqFetchSignal)
        .SquirrelFunc(_SC("SqCreateSignal"), &SqCreateSignal)
        .SquirrelFunc(_SC("SqRemoveSignal"), &SqRemoveSignal);
}

/* ------------------------------------------------------------------------------------------------
 * Forward the call to terminate the signals.
*/
void TerminateSignals()
{
    Signal::Terminate();
}

} // Namespace:: SqMod