/**
 * COMP2240 Assignment 1
 * Date: 28/08/21
 * Author: Zach S-B
 * Student Number: c3262201
 */
import java.util.LinkedList;

public class Algorithm {

    private int timeStep = 0;
    private int disp;
    private int timeLastChecked = -1;
    private String type;

    private static LinkedList<Process> processListFromFile;   //Original List of processes from the the file
    private static LinkedList<Integer> randomFromFile;   //Original List of processes from the the file
    private LinkedList<Process> waitingQueue = new LinkedList<>();  //Processes officially added to the waiting queue. Also functions as the first q1 for fbv
    private LinkedList<Process> newProcesses = new LinkedList<>();  //Processes that have arrived since the last check


    //Queues for FBV
    private LinkedList<Process> q2 = new LinkedList<>();
    private LinkedList<Process> q3 = new LinkedList<>();
    private LinkedList<Process> q4 = new LinkedList<>();
    
    //Constructor
    public Algorithm(int dispInput, LinkedList<Process> processListInput, LinkedList<Integer> randomListInput, String typeOfAlgorithm)
    {
        disp = dispInput;
        processListFromFile = processListInput;
        type = typeOfAlgorithm;
        randomFromFile = randomListInput;
    }

    //Check for new processes that have arrived
    private void checkForArrivals()
    {
        //Check each processe from the file, and if it has arrived between timeStep and whenever we last checked, add it.
        for(int i = 0; i< processListFromFile.size(); i++)
        {
            if((processListFromFile.get(i).getArrive()<=timeStep) && (processListFromFile.get(i).getArrive() > timeLastChecked))
            {
                newProcesses.add(processListFromFile.get(i));
            }
        }

        timeLastChecked = timeStep;

        //do any tie breaking by sorting the new processes based on name
        boolean swap = false;
        do
        {
            swap = false;
            for(int i = 0; i< newProcesses.size()-1; i++)
            {          
                if(Integer.parseInt(newProcesses.get(i).getId().substring(1))>Integer.parseInt(newProcesses.get(i+1).getId().substring(1)))
                {
                    Process temp = newProcesses.get(i);
                    newProcesses.remove(i);
                    newProcesses.add(i+1, temp);
                    swap = true;
                }
            }
        }while(swap);

        //Move the newly arrived (and organised) processes to the queue.
        for(int i =0; i < newProcesses.size(); i++)
        {
            waitingQueue.add(newProcesses.get(i));
        }

        //Clear the processes list so it is ready to go for the next time
        newProcesses.clear();
    }

    //Run the actual dispatch and running of programs
    public void runDispatch()
    {       
        System.out.println(type+":"); //Currently using FCFS methodology
       
        int processesFinished = 0;  //Used to track if we've done all the processes from the file
        
        Process currentProcess;
        Process lastRunProcess = new Process("temp", 0, 0, 0);  //temp process for comparing the first run
        
        do
         {
            checkForArrivals(); //Check for any new processes since timestep            
            //As long as there is something in the queue
            if(waitingQueue.size()>0)
            {
                //Which process runs next depends on the algorithm used.
                switch (type) {
                    case "FCFS":
                        currentProcess = waitingQueue.getFirst();
                        break;
                    case "SRT":
                        currentProcess = findShortestProcess();
                        break;
                    case "LTR":
                        currentProcess = getWinner();
                        break;                  
                    default:
                        currentProcess = waitingQueue.getFirst();
                        break;
                }

                //If the next process to run is different than the last one run, we need to run the actual dispatcher, which takes time, to change process
                if(!lastRunProcess.getId().equals(currentProcess.getId()))
                {
                    timeStep += disp;

                    System.out.println("T"+timeStep+": "+currentProcess.getId());

                    //Add the time waited to each of the currently waiting processes records
                    for(int i = 0; i<waitingQueue.size(); i++)
                    {
                        waitingQueue.get(i).wait(disp);
                    }
                }
                
                //Move the simulation one step forward
                progressOneTick(currentProcess);
                
                //If a process finishes, save details and remove it from the queue
                if(currentProcess.isDone())
                {
                    currentProcess.setEndTime(timeStep);
                    currentProcess.setTurnTime();
                    waitingQueue.remove(currentProcess);
                    processesFinished++;
                }

                lastRunProcess = currentProcess;
            }
            
         }while(processesFinished!=processListFromFile.size());      
    }

    public void runDispatchFBV()
    {       
        System.out.println(type+":"); //Currently using FCFS methodology
       
        int processesFinished = 0;  //Used to track if we've done all the processes from the file
        
        Process currentProcess;
        Process lastRunProcess = new Process("temp", 0, 0, 0);  //temp process for comparing the first run
        
        do
         {
            checkForArrivals(); //Check for any new processes since timestep            
            //As long as there is something in the queue
            if(waitingQueue.size()>0)
            {   
                currentProcess = waitingQueue.getFirst();

                //If the next process to run is different than the last one run, we need to run the actual dispatcher, which takes time, to change process
                if(!lastRunProcess.getId().equals(currentProcess.getId()))
                {
                    timeStep += disp;

                    System.out.println("T"+timeStep+": "+currentProcess.getId());

                    //Add the time waited to each of the currently waiting processes records
                    for(int i = 0; i<waitingQueue.size(); i++)
                    {
                        waitingQueue.get(i).wait(disp);
                        waitingQueue.get(i).addQueueTime(disp);
                    }
                    for(int i = 0; i<q2.size(); i++)
                    {
                        q2.get(i).wait(disp);
                        q2.get(i).addQueueTime(disp);
                    }
                    for(int i = 0; i<q3.size(); i++)
                    {
                        q3.get(i).wait(disp);
                        q3.get(i).addQueueTime(disp);
                    }
                    for(int i = 0; i<q4.size(); i++)
                    {
                        q4.get(i).wait(disp);
                        q4.get(i).addQueueTime(disp);
                    }                                                            
                }
                
                //Move the simulation one step forward
                progressOneTick(currentProcess);
                
                //If a process finishes, save details and remove it from the queue
                if(currentProcess.isDone())
                {
                    currentProcess.setEndTime(timeStep);
                    currentProcess.setTurnTime();
                    waitingQueue.remove(currentProcess);
                    processesFinished++;
                }


                moveQueues();
                lastRunProcess = currentProcess;
            }
            
         }while(processesFinished!=processListFromFile.size());      
    }    
    
    /**
     * Moves everything along in the queues. Frankly, this is quite wrong and I misunderstood the point of the time quantums.
     * Instead of running for however many ticks, I let it stay in queue for a certain ammount of time (see q4, it has 4 instead of 32)
     * Furthermore, I forgot to reset the queue wait time. If I fix this, it hangs. Clearly this function is lacking, but I do not have the time to correct it.
     */
    private void moveQueues()
    {
        for(int i = 0; i<waitingQueue.size(); i++)
        {
            if(waitingQueue.get(i).getQueueTime()>=1)
            {
                Process temp = waitingQueue.remove(i);
                q2.add(temp);
            }
        }
        for(int i = 0; i<q2.size(); i++)
        {
            if(q2.get(i).getQueueTime()>=1)
            {
                Process temp = q2.remove(i);
                q3.add(temp);
            }
        }   
        
        for(int i = 0; i<q3.size(); i++)
        {
            if(q3.get(i).getQueueTime()>=4)
            {
                Process temp = q3.remove(i);
                q4.add(temp);
            }
        }      
        
        for(int i = 0; i<q4.size(); i++)
        {
            if(q4.get(i).getQueueTime()>=4)
            {
                Process temp = q4.remove(i);
                waitingQueue.add(0, temp);
            }
        }        
    }
    /**
     * Finds and returns the process with the shortest remaining time till completion 
     * @return the process with the shortest remaining time till completion 
     */
    private Process findShortestProcess()
    {
        int listPos = -1;
        int remaining = waitingQueue.getFirst().getRemainingTime();

        for(int i = 0; i<waitingQueue.size(); i++)
        {
            if(waitingQueue.get(i).getRemainingTime()<remaining)
            {
                listPos = i;
                remaining = waitingQueue.get(i).getRemainingTime();
            }
        }

        if(listPos==-1)
        {
            return waitingQueue.getFirst();
        }
        else
        {
            return waitingQueue.get(listPos);
        }
    }

    /**
     * Moves the simulation along 1 tick
     * @param currentlyRunningProcess Is the process that is currently running. It gets 1 progress tick, everything else waiting gets 1 waiting tick.
     */
    private void progressOneTick(Process currentlyRunningProcess)
    {   
        timeStep++;
        for(int i = 0; i< waitingQueue.size(); i++)
        {
            if(!currentlyRunningProcess.getId().equals(waitingQueue.get(i).getId()))
            {
                waitingQueue.get(i).runForOneTick(false);
            }
            else
            {
                waitingQueue.get(i).runForOneTick(true);
            }
        }

        for(int i = 0; i< q2.size(); i++)
        {
            if(!currentlyRunningProcess.getId().equals(q2.get(i).getId()))
            {
                q2.get(i).runForOneTick(false);
            }
            else
            {
                q2.get(i).runForOneTick(true);
            }
        }
        
        for(int i = 0; i< q3.size(); i++)
        {
            if(!currentlyRunningProcess.getId().equals(q3.get(i).getId()))
            {
                q3.get(i).runForOneTick(false);
            }
            else
            {
                q3.get(i).runForOneTick(true);
            }
        }
        
        for(int i = 0; i< q4.size(); i++)
        {
            if(!currentlyRunningProcess.getId().equals(q4.get(i).getId()))
            {
                q4.get(i).runForOneTick(false);
            }
            else
            {
                q4.get(i).runForOneTick(true);
            }
        }        
    }  

    int randIndex = 0;
    private int getRandom()
    {
        int returnvalue;
        returnvalue = randomFromFile.get(randIndex);
        randIndex++;

        if(randIndex > randomFromFile.size())
        {
           randIndex = 0;
        }

        return returnvalue;
    }

    private Process getWinner()
    {
        int counter = 0;

        int winner = getRandom();

        while(true)
        {
            for(int i = 0; i < waitingQueue.size(); i++)
            {
                counter+=waitingQueue.get(i).getTickets();
                if(counter>winner)
                {
                    return waitingQueue.get(i);
                }
            }
        }
    }

    public double averageTurnTime()
    {
        double turnTime = 0;
        for(int i = 0; i<processListFromFile.size(); i++)
        {
            turnTime+= processListFromFile.get(i).getTurnTime();
        }

        return turnTime/processListFromFile.size();
    }

    public double averageWaitTime()
    {
        double waitTime = 0;
        for(int i = 0; i<processListFromFile.size(); i++)
        {
            waitTime+= processListFromFile.get(i).getWaitTime();
        }

        return waitTime/processListFromFile.size();
    }    
}