//=========================================================================
//  CSCHEDULER.CC - part of
//
//                  OMNeT++/OMNEST
//           Discrete System Simulation in C++
//
//  Author: Andras Varga, 2003
//
//=========================================================================

/*--------------------------------------------------------------*
  Copyright (C) 2003-2017 Andras Varga
  Copyright (C) 2006-2017 OpenSim Ltd.
  Monash University, Dept. of Electrical and Computer Systems Eng.
  Melbourne, Australia

  This file is distributed WITHOUT ANY WARRANTY. See the file
  `license' for details on this and other legal matters.
*--------------------------------------------------------------*/

#include "omnetpp/cscheduler.h"
#include "omnetpp/cevent.h"
#include "omnetpp/csimulation.h"
#include "omnetpp/cfutureeventset.h"
#include "omnetpp/globals.h"
#include "omnetpp/cenvir.h"
#include "omnetpp/cconfiguration.h"
#include "omnetpp/cconfigoption.h"
#include "omnetpp/platdep/platmisc.h"  // usleep

namespace omnetpp {

Register_GlobalConfigOption(CFGID_REALTIMESCHEDULER_SCALING, "realtimescheduler-scaling", CFG_DOUBLE, nullptr, "When cRealTimeScheduler is selected as scheduler class: ratio of simulation time to real time. For example, `realtimescheduler-scaling=2` will cause simulation time to progress twice as fast as runtime.");

cScheduler::cScheduler()
{
    sim = nullptr;
}

cScheduler::~cScheduler()
{
}

std::string cScheduler::str() const
{
    return "(no scheduler info provided)";
}

void cScheduler::setSimulation(cSimulation *_sim)
{
    sim = _sim;
}

void cScheduler::lifecycleEvent(SimulationLifecycleEventType eventType, cObject *details)
{
    switch (eventType) {
        case LF_PRE_NETWORK_INITIALIZE: startRun(); break;
        case LF_ON_RUN_END: endRun(); break;
        case LF_ON_SIMULATION_RESUME: executionResumed(); break;
        default: break;
    }
}

//-----

Register_Class(cSequentialScheduler);

std::string cSequentialScheduler::str() const
{
    return "";  // NOTE: Do not change this to "sequential scheduler"! As this scheduler is the "nothing special" scheduler, we don't want to advertise its presence.
}

cEvent *cSequentialScheduler::guessNextEvent()
{
    return sim->getFES()->peekFirst();
}

cEvent *cSequentialScheduler::takeNextEvent()
{
    for (;;)
    {
        cEvent *event = sim->getFES()->removeFirst();
        if (!event)
            throw cTerminationException(E_ENDEDOK);
        if (event->isStale())
            delete event;
        else
            return event;
    }
}

void cSequentialScheduler::putBackEvent(cEvent *event)
{
    sim->getFES()->putBackFirst(event);
}

//-----

Register_Class(cRealTimeScheduler);

cRealTimeScheduler::cRealTimeScheduler() : cScheduler()
{
}

cRealTimeScheduler::~cRealTimeScheduler()
{
}

std::string cRealTimeScheduler::str() const
{
    if (!doScaling)
        return "real-time scheduling";
    else {
        char buf[64];
        sprintf(buf, "scaled real-time scheduling (%gx)", factor);
        return buf;
    }
}

void cRealTimeScheduler::startRun()
{
    factor = getEnvir()->getConfig()->getAsDouble(CFGID_REALTIMESCHEDULER_SCALING);
    if (factor != 0)
        factor = 1 / factor;
    doScaling = (factor != 0);

    baseTime = opp_get_monotonic_clock_usecs();
}

int64_t cRealTimeScheduler::toUsecs(simtime_t t)
{
    return (int64_t) (1000000 * (doScaling ? factor * t.dbl() : t.dbl()));
}

void cRealTimeScheduler::executionResumed()
{
    baseTime = opp_get_monotonic_clock_usecs();
    baseTime = baseTime - toUsecs(sim->getSimTime());
}

bool cRealTimeScheduler::waitUntil(int64_t targetTime)
{
    // if there's more than 200ms to wait, wait in 100ms chunks
    // in order to keep UI responsiveness by invoking getEnvir()->idle()
    int64_t currentTime = opp_get_monotonic_clock_usecs();
    while (targetTime - currentTime >= 200000) {
        usleep(100000);  // 100ms
        if (getEnvir()->idle())
            return false;
        currentTime = opp_get_monotonic_clock_usecs();
    }

    // difference is now at most 100ms, do it at once
    int64_t remaining = targetTime - currentTime;
    if (remaining > 0)
        usleep(remaining);
    return true;
}

cEvent *cRealTimeScheduler::guessNextEvent()
{
    return sim->getFES()->peekFirst();
}

cEvent *cRealTimeScheduler::takeNextEvent()
{
    cEvent *event = sim->getFES()->peekFirst();
    if (!event)
        throw cTerminationException(E_ENDEDOK);

    // calculate target time
    simtime_t eventSimtime = event->getArrivalTime();
    int64_t targetTime = baseTime + toUsecs(eventSimtime);

    // if needed, wait until that time arrives
    int64_t currentTime = opp_get_monotonic_clock_usecs();
    if (targetTime > currentTime) {
        if (!waitUntil(targetTime))
            return nullptr;  // user break
    }
    else {
        // we're behind -- customized versions of this class may alert
        // if we're too much behind, or modify basetime to accept the skew
    }

    // remove event from FES and return it
    cEvent *tmp = sim->getFES()->removeFirst();
    ASSERT(tmp == event);
    return event;
}

void cRealTimeScheduler::putBackEvent(cEvent *event)
{
    sim->getFES()->putBackFirst(event);
}

}  // namespace omnetpp