package round_robin;

import java.util.LinkedList;

import round_robin.graphics.SimulationGui;

/**
 * This class implements functionality associated with
 * the CPU unit of the simulated system.
 */
public class Cpu {
	
	LinkedList<Process> cpuQueue;
	long maxCpuTime;
	Statistics statistics;
	Process activeProcess = null;
	EventQueue eventQueue;
	
    /**
     * Creates a new CPU with the given parameters.
     * @param cpuQueue		The CPU queue to be used.
     * @param maxCpuTime	The Round Robin time quant to be used.
     * @param statistics	A reference to the statistics collector.
     */
    public Cpu(LinkedList<Process> cpuQueue, long maxCpuTime, Statistics statistics, EventQueue eventQueue) {
        this.cpuQueue = cpuQueue;
        this.maxCpuTime = maxCpuTime;
        this.statistics = statistics;
        this.eventQueue = eventQueue;
    }

    /**
     * Adds a process to the CPU queue, and activates (switches in) the first process
     * in the CPU queue if the CPU is idle.
     * @param p		The process to be added to the CPU queue.
     * @param clock	The global time.
     * @return		The event causing the process that was activated to leave the CPU,
     *				or null	if no process was activated.
     */
    public void insertProcess(Process p, long clock) {
        cpuQueue.add(p);
        p.enterCpuQueue(clock);
    }

    /**
     * Activates (switches in) the first process in the CPU queue, if the queue is non-empty.
     * The process that was using the CPU, if any, is switched out and added to the back of
     * the CPU queue, in accordance with the Round Robin algorithm.
     * @param clock	The global time.
     * @return		The event causing the process that was activated to leave the CPU,
     *				or null	if no process was activated.
     */
    public Process switchProcess(long clock) {
    	Process old = this.activeProcess;
    	if (old != null){
    		old.leftCpu(clock);
    	}
    	
        if(cpuQueue.isEmpty()) {
            this.activeProcess = null;
        } else {
            this.activeProcess = cpuQueue.remove();
        }

        
        if (this.activeProcess != null){
        	this.activeProcess.enterCpu(clock);
        	
            if (activeProcess.getRemainingCpuTime() <= activeProcess.getTimeToNextIoOperation() && activeProcess.getRemainingCpuTime() <= this.maxCpuTime) {
            	Event processEnd = new Event(Event.END_PROCESS, clock + activeProcess.getRemainingCpuTime());
                eventQueue.insertEvent(processEnd);
                
            } else if (activeProcess.getTimeToNextIoOperation() <= maxCpuTime) {
            	Event processToIo = new Event(Event.IO_REQUEST, clock + activeProcess.getTimeToNextIoOperation());
                eventQueue.insertEvent(processToIo);

            } else {
                Event processSwitch = new Event(Event.SWITCH_PROCESS, clock + maxCpuTime);
                eventQueue.insertEvent(processSwitch);
            }

        }
        return old;
    }

    /**
     * Called when the active process left the CPU (for example to perform I/O),
     * and a new process needs to be switched in.
     * @return 
     * @return	The event generated by the process switch, or null if no new
     *			process was switched in.
     */
    public Process activeProcessLeft(long clock) {
        return this.switchProcess(clock);
    }

    /**
     * Returns the process currently using the CPU.
     * @return	The process currently using the CPU.
     */
    public Process getActiveProcess() {
        return this.activeProcess;
    }

    /**
     * This method is called when a discrete amount of time has passed.
     * @param timePassed	The amount of time that has passed since the last call to this method.
     */
    public void timePassed(long timePassed) {
		statistics.cpuQueueLengthTime += cpuQueue.size()*timePassed;
		if (cpuQueue.size() > statistics.cpuQueueLargestLength) {
			statistics.cpuQueueLargestLength = cpuQueue.size();
		}
    }
    
    public boolean isIdle(){
    	return this.activeProcess == null;
    }

}
