// CPUScheduler.h     Dave Reed      2/5/05
///////////////////////////////////////////////////////////////////////////////

#include <iostream>
#include <iomanip>
#include <queue>
#include "CPUScheduler.h"
#include "Job.h"
using namespace std;

CPUScheduler::CPUScheduler(int delay, int slice, int min, int max) : randomizer(max-min+1)
// Constructor: initializes counters for time slice, load time, and I/O range
{
    loadDelay = delay;
    timeSlice = slice;
    IOmin = min;
    IOmax = max;

    sliceTimeRemaining = timeSlice;
    loadTimeRemaining = loadDelay;

    currentTime = 0;
}

int CPUScheduler::getTime() const
// Returns: current time in the simulation
{
    return currentTime;
}

bool CPUScheduler::jobsRemaining() const
// Returns: true if there is at least one job in either of the ready or wait queues
{
    return (!readyQueue.empty() || !waitQueue.empty());
}

void CPUScheduler::addNewJob(Job newJob)
// Assumes: time > 0
// Results: adds newJob to the back of the ready queue (at specified time)
{
    readyQueue.push(newJob);
    cout << setw(4) << currentTime << ": JOB " << newJob.getID() << " ARRIVES" << endl;
}

void CPUScheduler::execute()
// Assumes: time > 0
// Results: executes one CPU cycle (at specified time) -- this may involve moving jobs
//          from waiting to ready, loading a new job, performing one instruction 
//          execution, and displaying theresults of the execution
{
    currentTime++;

    while (!waitQueue.empty() && waitQueue.top().getPriority() <= currentTime) { 
        Job newJob = (Job)waitQueue.top();                  // MOVE JOBS FROM WAIT QUEUE
        waitQueue.pop();                                    // TO READY QUEUE AS NEEDED
        readyQueue.push(newJob);
    }

    if (!readyQueue.empty()) {                              // IF READY JOB IS AVAILABLE
        Job & currentJob = readyQueue.front();              //   GET THE NEXT JOB

        if (loadTimeRemaining > 0) {                        //   IF STILL LOADING, IDLE
            if (loadTimeRemaining == loadDelay) {
                cout << setw(4) << currentTime << ":   LOAD JOB " << currentJob.getID() 
                     << " (delay = " << loadDelay << ")" << endl;
            }
            loadTimeRemaining--;
        }
        else {                                              //   OTHERWISE, PROCESS
            if (sliceTimeRemaining == timeSlice) {         //     DISPLAY IF NEW JOB
                cout << setw(4) << currentTime << ":     START JOB " << currentJob.getID() 
                     << " (burst = " << currentJob.getRemainingBurst() << ")" << endl;
            }

            JobStatus status = currentJob.execute();        //   EXECUTE THE JOB AND
            sliceTimeRemaining--;                           //   REDUCE ITS TIME SLICE

            if (status == DONE) {                           //   IF JOB FINISHED
                cout << setw(4) << currentTime+1 << ":       FINISH JOB " 
                     << currentJob.getID() << endl;
                readyQueue.pop();                           //     REMOVE FROM QUEUE AND
                sliceTimeRemaining = timeSlice;             //     RESET COUNTS
                loadTimeRemaining = loadDelay;
            }
            else if (status == IO) {                        //   IF REQUESTED I/O
                int wait = getIOLength();
                cout << setw(4) << currentTime+1 << ":       I/O JOB " << currentJob.getID()
                     << " (wait = " << wait << ")" << endl;
                PriorityJob waitJob(currentJob, currentTime+1+wait);
                readyQueue.pop();                           //     MOVE TO WAIT QUEUE
                waitQueue.push(waitJob);                    //     USING RANDOM DELAY, 
                sliceTimeRemaining = timeSlice;            //     AND RESET COUNTS
                loadTimeRemaining = loadDelay;
            }
            else if (sliceTimeRemaining == 0) {             //   IF TIME SLICE IS UP
                cout << setw(4) << currentTime+1 << ":       TIMEOUT JOB " 
                     << currentJob.getID() << endl;
                Job readyJob = currentJob;
                readyQueue.pop();                           //     MOVE TO BACK OF QUEUE
                readyQueue.push(readyJob);
                sliceTimeRemaining = timeSlice;            //       RESET TIME SLICE
                if (readyQueue.size() > 1) {               //       IF OTHER JOBS READY,
                    loadTimeRemaining = loadDelay;         //         RESET LOAD COUNT
                }
            }
        }
    }
} 

int CPUScheduler::getIOLength()
// Returns: a random integer between IOmin and IOmax, inclusive
{
    return randomizer.roll()+IOmin-1;
}