/*
 * Copyright (c) 2005,2006 INRIA
 *
 * SPDX-License-Identifier: GPL-2.0-only
 *
 * Author: Mathieu Lacage <mathieu.lacage@sophia.inria.fr>
 */

#include "default-simulator-impl.h"

#include "assert.h"
#include "log.h"
#include "scheduler.h"
#include "simulator.h"

/**
 * @file
 * @ingroup simulator
 * ns3::DefaultSimulatorImpl implementation.
 */

namespace ns3
{

// Note:  Logging in this file is largely avoided due to the
// number of calls that are made to these functions and the possibility
// of causing recursions leading to stack overflow
NS_LOG_COMPONENT_DEFINE("DefaultSimulatorImpl");

NS_OBJECT_ENSURE_REGISTERED(DefaultSimulatorImpl);

TypeId
DefaultSimulatorImpl::GetTypeId()
{
    static TypeId tid = TypeId("ns3::DefaultSimulatorImpl")
                            .SetParent<SimulatorImpl>()
                            .SetGroupName("Core")
                            .AddConstructor<DefaultSimulatorImpl>();
    return tid;
}

DefaultSimulatorImpl::DefaultSimulatorImpl()
{
    NS_LOG_FUNCTION(this);
    m_stop = false;
    m_uid = EventId::UID::VALID;
    m_currentUid = EventId::UID::INVALID;
    m_currentTs = 0;
    m_currentContext = Simulator::NO_CONTEXT;
    m_unscheduledEvents = 0;
    m_eventCount = 0;
    m_eventsWithContextEmpty = true;
    m_mainThreadId = std::this_thread::get_id();
}

DefaultSimulatorImpl::~DefaultSimulatorImpl()
{
    NS_LOG_FUNCTION(this);
}

void
DefaultSimulatorImpl::DoDispose()
{
    NS_LOG_FUNCTION(this);
    ProcessEventsWithContext();

    while (!m_events->IsEmpty())
    {
        Scheduler::Event next = m_events->RemoveNext();
        next.impl->Unref();
    }
    m_events = nullptr;
    SimulatorImpl::DoDispose();
}

void
DefaultSimulatorImpl::Destroy()
{
    NS_LOG_FUNCTION(this);
    while (!m_destroyEvents.empty())
    {
        Ptr<EventImpl> ev = m_destroyEvents.front().PeekEventImpl();
        m_destroyEvents.pop_front();
        NS_LOG_LOGIC("handle destroy " << ev);
        if (!ev->IsCancelled())
        {
            ev->Invoke();
        }
    }
}

void
DefaultSimulatorImpl::SetScheduler(ObjectFactory schedulerFactory)
{
    NS_LOG_FUNCTION(this << schedulerFactory);
    Ptr<Scheduler> scheduler = schedulerFactory.Create<Scheduler>();

    if (m_events)
    {
        while (!m_events->IsEmpty())
        {
            Scheduler::Event next = m_events->RemoveNext();
            scheduler->Insert(next);
        }
    }
    m_events = scheduler;
}

// System ID for non-distributed simulation is always zero
uint32_t
DefaultSimulatorImpl::GetSystemId() const
{
    return 0;
}

void
DefaultSimulatorImpl::ProcessOneEvent()
{
    Scheduler::Event next = m_events->RemoveNext();

    PreEventHook(EventId(next.impl, next.key.m_ts, next.key.m_context, next.key.m_uid));

    NS_ASSERT(next.key.m_ts >= m_currentTs);
    m_unscheduledEvents--;
    m_eventCount++;

    NS_LOG_LOGIC("handle " << next.key.m_ts);
    m_currentTs = next.key.m_ts;
    m_currentContext = next.key.m_context;
    m_currentUid = next.key.m_uid;
    next.impl->Invoke();
    next.impl->Unref();

    ProcessEventsWithContext();
}

bool
DefaultSimulatorImpl::IsFinished() const
{
    return m_events->IsEmpty() || m_stop;
}

void
DefaultSimulatorImpl::ProcessEventsWithContext()
{
    if (m_eventsWithContextEmpty)
    {
        return;
    }

    // swap queues
    EventsWithContext eventsWithContext;
    {
        std::unique_lock lock{m_eventsWithContextMutex};
        m_eventsWithContext.swap(eventsWithContext);
        m_eventsWithContextEmpty = true;
    }
    while (!eventsWithContext.empty())
    {
        EventWithContext event = eventsWithContext.front();
        eventsWithContext.pop_front();
        Scheduler::Event ev;
        ev.impl = event.event;
        ev.key.m_ts = m_currentTs + event.timestamp;
        ev.key.m_context = event.context;
        ev.key.m_uid = m_uid;
        m_uid++;
        m_unscheduledEvents++;
        m_events->Insert(ev);
    }
}

void
DefaultSimulatorImpl::Run()
{
    NS_LOG_FUNCTION(this);
    // Set the current threadId as the main threadId
    m_mainThreadId = std::this_thread::get_id();
    ProcessEventsWithContext();
    m_stop = false;

    while (!m_events->IsEmpty() && !m_stop)
    {
        ProcessOneEvent();
    }

    // If the simulator stopped naturally by lack of events, make a
    // consistency test to check that we didn't lose any events along the way.
    NS_ASSERT(!m_events->IsEmpty() || m_unscheduledEvents == 0);
}

void
DefaultSimulatorImpl::Stop()
{
    NS_LOG_FUNCTION(this);
    m_stop = true;
}

EventId
DefaultSimulatorImpl::Stop(const Time& delay)
{
    NS_LOG_FUNCTION(this << delay.GetTimeStep());
    return Simulator::Schedule(delay, &Simulator::Stop);
}

//
// Schedule an event for a _relative_ time in the future.
//
EventId
DefaultSimulatorImpl::Schedule(const Time& delay, EventImpl* event)
{
    NS_LOG_FUNCTION(this << delay.GetTimeStep() << event);
    NS_ASSERT_MSG(m_mainThreadId == std::this_thread::get_id(),
                  "Simulator::Schedule Thread-unsafe invocation!");

    NS_ASSERT_MSG(delay.IsPositive(), "DefaultSimulatorImpl::Schedule(): Negative delay");
    Time tAbsolute = delay + TimeStep(m_currentTs);

    Scheduler::Event ev;
    ev.impl = event;
    ev.key.m_ts = (uint64_t)tAbsolute.GetTimeStep();
    ev.key.m_context = GetContext();
    ev.key.m_uid = m_uid;
    m_uid++;
    m_unscheduledEvents++;
    m_events->Insert(ev);
    return EventId(event, ev.key.m_ts, ev.key.m_context, ev.key.m_uid);
}

void
DefaultSimulatorImpl::ScheduleWithContext(uint32_t context, const Time& delay, EventImpl* event)
{
    NS_LOG_FUNCTION(this << context << delay.GetTimeStep() << event);

    if (m_mainThreadId == std::this_thread::get_id())
    {
        Time tAbsolute = delay + TimeStep(m_currentTs);
        Scheduler::Event ev;
        ev.impl = event;
        ev.key.m_ts = (uint64_t)tAbsolute.GetTimeStep();
        ev.key.m_context = context;
        ev.key.m_uid = m_uid;
        m_uid++;
        m_unscheduledEvents++;
        m_events->Insert(ev);
    }
    else
    {
        EventWithContext ev;
        ev.context = context;
        // Current time added in ProcessEventsWithContext()
        ev.timestamp = delay.GetTimeStep();
        ev.event = event;
        {
            std::unique_lock lock{m_eventsWithContextMutex};
            m_eventsWithContext.push_back(ev);
            m_eventsWithContextEmpty = false;
        }
    }
}

EventId
DefaultSimulatorImpl::ScheduleNow(EventImpl* event)
{
    NS_ASSERT_MSG(m_mainThreadId == std::this_thread::get_id(),
                  "Simulator::ScheduleNow Thread-unsafe invocation!");

    return Schedule(Time(0), event);
}

EventId
DefaultSimulatorImpl::ScheduleDestroy(EventImpl* event)
{
    NS_ASSERT_MSG(m_mainThreadId == std::this_thread::get_id(),
                  "Simulator::ScheduleDestroy Thread-unsafe invocation!");

    EventId id(Ptr<EventImpl>(event, false), m_currentTs, 0xffffffff, 2);
    m_destroyEvents.push_back(id);
    m_uid++;
    return id;
}

Time
DefaultSimulatorImpl::Now() const
{
    // Do not add function logging here, to avoid stack overflow
    return TimeStep(m_currentTs);
}

Time
DefaultSimulatorImpl::GetDelayLeft(const EventId& id) const
{
    if (IsExpired(id))
    {
        return TimeStep(0);
    }
    else
    {
        return TimeStep(id.GetTs() - m_currentTs);
    }
}

void
DefaultSimulatorImpl::Remove(const EventId& id)
{
    if (id.GetUid() == EventId::UID::DESTROY)
    {
        // destroy events.
        for (auto i = m_destroyEvents.begin(); i != m_destroyEvents.end(); i++)
        {
            if (*i == id)
            {
                m_destroyEvents.erase(i);
                break;
            }
        }
        return;
    }
    if (IsExpired(id))
    {
        return;
    }
    Scheduler::Event event;
    event.impl = id.PeekEventImpl();
    event.key.m_ts = id.GetTs();
    event.key.m_context = id.GetContext();
    event.key.m_uid = id.GetUid();
    m_events->Remove(event);
    event.impl->Cancel();
    // whenever we remove an event from the event list, we have to unref it.
    event.impl->Unref();

    m_unscheduledEvents--;
}

void
DefaultSimulatorImpl::Cancel(const EventId& id)
{
    if (!IsExpired(id))
    {
        id.PeekEventImpl()->Cancel();
    }
}

bool
DefaultSimulatorImpl::IsExpired(const EventId& id) const
{
    if (id.GetUid() == EventId::UID::DESTROY)
    {
        if (id.PeekEventImpl() == nullptr || id.PeekEventImpl()->IsCancelled())
        {
            return true;
        }
        // destroy events.
        for (auto i = m_destroyEvents.begin(); i != m_destroyEvents.end(); i++)
        {
            if (*i == id)
            {
                return false;
            }
        }
        return true;
    }
    return id.PeekEventImpl() == nullptr || id.GetTs() < m_currentTs ||
           (id.GetTs() == m_currentTs && id.GetUid() <= m_currentUid) ||
           id.PeekEventImpl()->IsCancelled();
}

Time
DefaultSimulatorImpl::GetMaximumSimulationTime() const
{
    return TimeStep(0x7fffffffffffffffLL);
}

uint32_t
DefaultSimulatorImpl::GetContext() const
{
    return m_currentContext;
}

uint64_t
DefaultSimulatorImpl::GetEventCount() const
{
    return m_eventCount;
}

} // namespace ns3
