/**
 * Filename: project1.cpp
 * Author: Josh Seidel
 * PolyID: 0226424
 * Class:  CS3224
 * Section: Afternoon section
 * Project: 1
 * Description: A simple implementation of a RoundRobin Dispatch Algorithm
 * NOTE: It runs test B and test E with modifications to the times. I cannot think
 * of an exact reason other than the sorting of the Blocked List that is used in the
 * algorithm.
 */

#include <ciso646>
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <iterator>
#include <list>

using namespace std;

//#ifdef DEBUG
//#define _DEBUG true
//#else
#define _DEBUG false
//#endif

#define _DEBUG_PRINTLN(x) if(_DEBUG){ cout << x << endl; }
#define _DEBUG_PRINT(x) if(_DEBUG){ cout << x; }
#define NUM_ELEMENTS(x) (sizeof(x)/sizeof((x)[0]))

#define DEFAULT_FILENAME "proj1.txt"

class Process {
  private: int pID;
  private: double totalCPUTime;
  private: double burstCPUTime;
  private: double blockedTime;
  private: double totalCPUTimeLeft;
  private: double blockedTimeLeft;
  private: double burstCPUTimeLeft;
  private: double timeRun;

  public: Process(){
		Process(-1, 0.0, 0.0, 0.0);
	}
  public: Process(int processID, double total, double burst, double blocked):
	  pID(processID), totalCPUTime(total),
		burstCPUTime(burst), blockedTime(blocked),
		totalCPUTimeLeft(total), blockedTimeLeft(0.0),
		burstCPUTimeLeft(burst),	timeRun(0.0){

  }
  public: ~Process(){
		totalCPUTimeLeft = 0.0;
		blockedTimeLeft = 0.0;
		burstCPUTimeLeft = 0.0;
		timeRun = 0.0;
  }
  public: int getProcessID() const {
		return this->pID;
  }
  public: double getTotalCPUTime() const {
		return this->totalCPUTime;
	}
  public: double getBurstCPUTime() const {
		return this->burstCPUTime;
	}
  public: double getBlockedTime() const {
		return this->blockedTime;
	}
  public: double getTotalCPUTimeLeft() const {
  	return this->totalCPUTimeLeft;
	}
  public: void setTotalCPUTimeLeft(double timeLeft){
		this->totalCPUTimeLeft = timeLeft;
	}
  public: double getBurstCPUTimeLeft() const {
		return this->burstCPUTimeLeft;
	}
  public: void setBurstCPUTimeLeft(double timeLeft){
		this->burstCPUTimeLeft = timeLeft;
  }
  public: double getBlockedTimeLeft() const {
		return this->blockedTimeLeft;
  }
  public: void setBlockedTimeLeft(double timeLeft){
		this->blockedTimeLeft = timeLeft;
  }
  public: int operator<(const Process &process){
		if((this == &process) ||
			 ((this->pID == process.pID) &&
			  (this->totalCPUTime == process.totalCPUTime) &&
				(this->burstCPUTime == process.burstCPUTime) &&
				(this->blockedTime == process.blockedTime) &&
				(this->totalCPUTimeLeft == process.totalCPUTimeLeft) &&
				(this->burstCPUTimeLeft == process.burstCPUTimeLeft) &&
				(this->blockedTimeLeft == process.blockedTimeLeft))
			){
			return 0;
		}
		return ((this->blockedTimeLeft - process.blockedTimeLeft) >= 1e-7) ? 1 : -1;
	}
  public: friend ostream &operator<< (ostream &out, const Process &process){
		out << process.getProcessID() << " "
			  << process.getTotalCPUTime() << " "
			  << process.getBurstCPUTime() << " "
			  << process.getBlockedTime();
		return out;
  }
};

class RoundRobinReadyList {
  private: list<Process> rrlist;
  private: double quantum;
  public: RoundRobinReadyList(double timeQuantum):
    quantum(timeQuantum) {
  }
  public: ~RoundRobinReadyList(){
		quantum = 0.0;
  }
  public: void add(Process process){
		rrlist.push_back(process);
  }
  private: Process nextProcessToRun;
  public: Process &getNextProcessToRun(){
		nextProcessToRun = rrlist.front();
		rrlist.pop_front();
		return nextProcessToRun;
  }
  public: bool isEmpty() const {
		return rrlist.empty();
  }
  public: friend ostream &operator<< (ostream &out, const RoundRobinReadyList &list){
		ostream_iterator<Process> itr_out(out);
		copy(list.rrlist.begin(), list.rrlist.end(), itr_out);
		return out;
  }
};

class RoundRobinBlockedList {
  private: list<Process> blockedList;

  public: RoundRobinBlockedList(){
		
	}
	public: ~RoundRobinBlockedList(){
		
	}
	public: void add(Process &process){
		blockedList.push_back(process);
		blockedList.sort();
	}
	public: bool isEmpty() const {
		blockedList.empty();
	}
	private: Process currentShortestBlockedProcess;
	public: Process &getShortestBlockedProcess(){
		currentShortestBlockedProcess = blockedList.front();
		blockedList.pop_front();
		blockedList.sort();
		return currentShortestBlockedProcess;
	}
	public: bool isZeroBlockedProcess(){
		if(isEmpty()){ return false; }
		blockedList.sort();
		return (blockedList.front().getBlockedTimeLeft() < 1e-7) ? true : false;
	}
	public: void shortenBlockTimes(double time){
		if(isEmpty()){ return; }
		list<Process>::iterator itr = blockedList.begin();
		do {
			itr->setBlockedTimeLeft(itr->getBlockedTimeLeft() - time);
			_DEBUG_PRINTLN("Blocked Time Left: " << itr->getBlockedTimeLeft());
		} while(itr++ != blockedList.end());
		blockedList.sort();
	}
  public: friend ostream &operator<< (ostream &out, const RoundRobinBlockedList &list){
		ostream_iterator<Process> itr_out(out);
		copy(list.blockedList.begin(), list.blockedList.end(), itr_out);
		return out;
  }
};

#define QUANTUM 0.1
class DispatchProcess {
	private: RoundRobinReadyList rrqueue;
	private: RoundRobinBlockedList rrblocked;
	private: double totalRuntime;
	private: double numberOfJobs;
	private: double completionTimes[1024];

	public: DispatchProcess(ifstream &in):
	  rrqueue(QUANTUM), totalRuntime(0.0) {
		char buffer[512] = "";
		in.getline(buffer, NUM_ELEMENTS(buffer));
		int numberOfProcesses = atoi(buffer);
		for(int count = 0; (count < numberOfProcesses) && (!in.eof()); ++count){
			int pID = 0;
			double totalCPUTime = 0.0;
			double burstCPUTime = 0.0;
			double blockTime = 0.0;
			in >> pID;
			_DEBUG_PRINT("Process ID: " << pID << " ");
			in >> totalCPUTime;
			_DEBUG_PRINT("Total CPU Time: " << totalCPUTime << " ");
			in >> burstCPUTime;
			_DEBUG_PRINT("Burst CPU Time: " << burstCPUTime << " ");
			in >> blockTime;
			_DEBUG_PRINTLN("Block Time: " << blockTime);
			Process process = Process(pID, totalCPUTime, burstCPUTime, blockTime);
			_DEBUG_PRINTLN("Created Process: " << process);
			rrqueue.add(process);
		}
	}
  public: void run(){
		totalRuntime = 0.0;
		numberOfJobs = 0.0;
		for(int count = 0; count < NUM_ELEMENTS(completionTimes); ++count){
			completionTimes[count] = 0.0;
		}
		
		while(!rrqueue.isEmpty() || !rrblocked.isEmpty()){
			Process p;
			if(rrqueue.isEmpty()){
				p = rrblocked.getShortestBlockedProcess();
				totalRuntime += p.getBlockedTimeLeft();
				rrblocked.shortenBlockTimes(p.getBlockedTimeLeft());
				p.setBlockedTimeLeft(0.0);
			} else {
				p = rrqueue.getNextProcessToRun();
			}
			double runtime = p.getTotalCPUTimeLeft();
			if(p.getBurstCPUTimeLeft() < runtime){
				runtime = p.getBurstCPUTimeLeft();
				if(QUANTUM < runtime){
					runtime = QUANTUM;
				}
			}
			//cout << "Runtime: " << totalRuntime << endl;
			totalRuntime += runtime;
			p.setBurstCPUTimeLeft(p.getBurstCPUTimeLeft() - runtime);
			p.setTotalCPUTimeLeft(p.getTotalCPUTimeLeft() - runtime);
			//cout << totalRuntime << " RRBlocked test: " << rrblocked << endl; 
			if(!rrblocked.isEmpty()){
				rrblocked.shortenBlockTimes(runtime);
				//cout << totalRuntime << " RRBlocked shortened: " << rrblocked << endl;
				while(rrblocked.isZeroBlockedProcess()){
					Process blockedProcess = rrblocked.getShortestBlockedProcess();
					//cout << "Current blockedProcess: " << blockedProcess << endl;
					if(blockedProcess.getBlockedTimeLeft() < 1e-7){
						blockedProcess.setBlockedTimeLeft(0.0);
						rrqueue.add(blockedProcess);
					} else {
						rrblocked.add(blockedProcess);
						//cout << totalRuntime << " RRQueue done: " << rrqueue << endl;
						//cout << totalRuntime << " RRBlocked done: " << rrblocked << endl;
						break;
					}
				}
			}
			_DEBUG_PRINTLN("QAdd pID: " << p.getProcessID());
			_DEBUG_PRINTLN("QAdd BurstCPUTimeLeft: " << p.getBurstCPUTimeLeft());
			_DEBUG_PRINTLN("QAdd BlockedTimeLeft: " << p.getBlockedTimeLeft());
			_DEBUG_PRINTLN("QAdd TotalTimeLeft: " << p.getTotalCPUTimeLeft());
			if(p.getTotalCPUTimeLeft() < 1E-7){
				++numberOfJobs;
				if((int)numberOfJobs < NUM_ELEMENTS(completionTimes)){
					completionTimes[(int)numberOfJobs] = totalRuntime;
				}
				cout << "Process " << p.getProcessID() << " ended at " << totalRuntime << endl;
			} else if(p.getBurstCPUTimeLeft() < 1E-7){
				p.setBurstCPUTimeLeft(p.getBurstCPUTime());
				p.setBlockedTimeLeft(p.getBlockedTime());
				rrblocked.add(p);
			} else {
				rrqueue.add(p);
			}
		}
		cout << "End of simulation." << endl;
	}
  public: friend ostream &operator<< (ostream &out, const DispatchProcess &process){
		out << "Total Time: " << process.totalRuntime << " seconds." << endl;
		out << "Throughput: " << process.numberOfJobs/process.totalRuntime << " jobs/second" << endl;
		double totalAddedCompletionTime = 0.0;
		for(int count = 0;
			(count < (int)process.numberOfJobs) || (count < NUM_ELEMENTS(process.completionTimes));
			++count){
			totalAddedCompletionTime += process.completionTimes[count];
		}
		out << "Average turnaround time: " << totalAddedCompletionTime/process.numberOfJobs << " seconds." << endl;
		return out;
  }
};

int main(int argc, char **argv){
  ifstream in;
	in.open(DEFAULT_FILENAME);
	if(!in.is_open()){
		cerr << "Couldn't open the file " << DEFAULT_FILENAME << "." << endl;
		return EXIT_FAILURE;
	}
	DispatchProcess dp = DispatchProcess(in);
	_DEBUG_PRINTLN("Finished creating dp... ");
	in.close();
	dp.run();
	_DEBUG_PRINTLN("Finished running dp...");
	cout << dp << endl;
	_DEBUG_PRINTLN("Finished cout of dp...");
	return EXIT_SUCCESS;
}
