#ifndef SMS_H
#define SMS_H

#include <stdlib.h>
#include <time.h>
#include "sysc/utils/sc_report.h"
#include "IScheduler.h"
#include "../core/ControllerCore.h"
#include "../core/configuration/Configuration.h"
#include "../../common/dramExtension.h"
#include "../../common/DebugManager.h"

#define LOW_SYSTEM_LOAD 16
#define LOW_MPKC 1
#define MEDIUM_MPKC 10
#define MEDIUM_THRESHOLD_AGE 50
#define HIGH_THRESHOLD_AGE 200
#define MPKC_RESET_CYCLE 10000

using namespace std;
typedef std::deque<gp *>::iterator gp_deque_iterator;

/**
 * SMS - Staged Memory Scheduler involves 3 steps:
 * 1. Arrage request in to each buffer of each thread
 * 2. Forming ready batches for each thread, i.e all requests access the same row. The batch is deemed
 *  ready when the next request accesses different row OR When the buffer is full OR When this batch
 *  is too old
 * 3. Send batches to bank buffers. The rules to send batches are Shortest-Job-First OR Round-Robin
 *  How we select the rule depends on the probability we setup earlier.
 */
class SMS: public sc_module, public IScheduler
{
public:
    SMS(sc_module_name /*_name*/, ControllerCore &controllerCore,
        unsigned int SJFprobability) : IScheduler(controllerCore),
        SJFprobability(SJFprobability), debugManager(DebugManager::getInstance())
    {
        // initialize selected thread iterator
        lastSelectedThread = readyBatchInclusiveEndLocs.end();
        SC_THREAD(batchScheduler);
    }
    SC_HAS_PROCESS(SMS);

    virtual ~SMS()
    {
    }

    virtual void storeRequest(gp *payload) override;
    virtual std::pair<Command, gp *> getNextRequest(Bank bank) override;
    virtual gp *getPendingRequest(Bank bank) override;

    void batchScheduler();

private:
    std::map<Thread, std::deque<gp *>> requestBuffers;
    std::map<Bank, std::deque<gp *>> bankBuffers;
    std::map<Thread, std::deque<size_t>> readyBatchInclusiveEndLocs;

    std::map<Thread, unsigned int> inFlightMemRequestCounter;
    std::map<Thread, unsigned int> cacheMisses;
    std::map<Thread, float> MPKCs;
    unsigned int SJFprobability;

    std::map<Thread, std::deque<size_t>>::iterator lastSelectedThread;
    sc_event newRequest;

    DebugManager &debugManager;

    bool batchFormation(sc_time const &memClk, Thread const &thread,
                        const std::deque<gp *> &requestBuffer, const size_t &inclusiveBeginLoc);
    void multiBatchFormation(const sc_time &memClk);
    bool pickSJF();
    bool pickRR();
    void drainOnePayloadFromReadybatch(const sc_time &memClk);

    bool existLowIntensityThread() const;
    bool isSystemLightlyLoaded() const;
    bool isThresholdAgeExceeded(const Thread &thread, const sc_time &memClk,
                                const size_t &inclusiveBeginLoc, const size_t &exclusiveEndLoc);
    bool isExceededReqBufferSize(size_t const &exclusiveEndLoc);
    void updateMPKCs(const sc_time &memClk);

    bool isRequestBuffersEmpty() const;
    bool existReadyBatches() const;
};

#endif // SMS_H
