/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package qsim.simulators;

import umontreal.iro.lecuyer.simevents.*;
import umontreal.iro.lecuyer.rng.*;
import umontreal.iro.lecuyer.randvar.RandomVariateGen;
import umontreal.iro.lecuyer.stat.*;
import java.util.LinkedList;
import qsim.distributions.PseudoHyperExpDist;
import qsim.distributions.PseudoHypoExpDist;
import qsim.rng.HyperExpRandomVariateGen;
import qsim.rng.HypoExpRandomVariateGen;
import qsim.stat.BatchMeansMethod;
import umontreal.iro.lecuyer.probdist.ContinuousDistribution;

public class QueueEv extends QueueMotor{

    private LinkedList<Customer> waitList = new LinkedList<Customer>();
    private LinkedList<Customer> servList = new LinkedList<Customer>();
    
    
    TallyStore utilization = new TallyStore("Kihasználtság");
    TallyStore queueSize = new TallyStore("Sorhossz");
    TallyStore custWaits = new TallyStore("Várakozási idők");
    TallyStore custServs = new TallyStore("Kiszolgálási idők");
    TallyStore sojourn = new TallyStore("Válaszidő");
    Tally custCount = new Tally("Igényszámláló");
    private double timeScale = 0.1;

    private class Customer {

        double arrivTime;
        double servTime;
        private boolean waited = false;
        
        public void addServTimeToStatistic(Tally t){
            t.add(servTime);
        }
        
        public void setWaited(){
            waited = true;
        }
        
        public boolean isWaited(){
            return waited;
        }
    }

    public QueueEv(ContinuousDistribution arrDist, ContinuousDistribution servDist, int n, int k, String policy, double timeHorizon) {
        this.arrDist = arrDist;
        this.servDist = servDist;
        this.n = n;
        this.k = k;
        
        //beérkező időközök eloszlása
        if (arrDist instanceof PseudoHyperExpDist){
            genArr = new HyperExpRandomVariateGen(new MRG32k3a(), arrDist);
        }
        else if (arrDist instanceof PseudoHypoExpDist){
            genArr = new HypoExpRandomVariateGen(new MRG32k3a(), arrDist);
        }
        else {
            long[] genArrSeed = {Math.round(Math.random() * 100000), Math.round(Math.random() * 100000), Math.round(Math.random() * 100000),
                Math.round(Math.random() * 100000), Math.round(Math.random() * 100000), Math.round(Math.random() * 100000)
            };
            MRG32k3a arrRng = new MRG32k3a();
            arrRng.setSeed(genArrSeed);
            genArr = new RandomVariateGen(arrRng, arrDist);
        }
        
        //kiszolgálási idők eloszlása
        if (servDist instanceof PseudoHyperExpDist){
            genServ = new HyperExpRandomVariateGen(new MRG32k3a(), servDist);
        }
        else if (servDist instanceof PseudoHypoExpDist){
            genServ = new HypoExpRandomVariateGen(new MRG32k3a(), servDist);
        }
        else {
            long[] genServSeed = {Math.round(Math.random() * 100000), Math.round(Math.random() * 100000), Math.round(Math.random() * 100000),
                Math.round(Math.random() * 100000), Math.round(Math.random() * 100000), Math.round(Math.random() * 100000)
            };
            MRG32k3a servRng = new MRG32k3a();
            servRng.setSeed(genServSeed);
            genServ = new RandomVariateGen(servRng, servDist);
        }
          
        
        //kiszolgálási elv
        this.policy = new String(policy);

        //szimulációs ablak
        this.timeHorizon = timeHorizon;
    }
    
    public boolean isFIFO(){
        if (this.policy.equals("FIFO")){
            return true;
        }
        return false;
    }
    
    private boolean isInfiniteQueue(){
        return k==-1 ? true : false;
    }

    public void simulateOneRun() {
        Simulator.getDefaultSimulator().init();
        new EndOfSim().schedule(timeHorizon);
        new NewObservation().schedule(timeScale);
        new Arrival().schedule(genArr.nextDouble());
        Simulator.getDefaultSimulator().start();
    }

    private class Arrival extends Event {

        @Override
        public void actions() {
            new Arrival().schedule(genArr.nextDouble()); // Next arrival.
            
            Customer cust = new Customer();  // Cust just arrived.
            cust.arrivTime = Simulator.getDefaultSimulator().time();
            //itt nem állítunk be kiszolgálási időt, mert lehet hogy elvész az igény
            custCount.add(1);

            
            
            if (servList.size() > (n-1)) { // Must join the queue.
                if (!isInfiniteQueue() && waitList.size() >= (k-n)){ //Loss
                    return;
                }
                cust.servTime = genServ.nextDouble(); //kiszolgálási idő meghatározása
                
                queueSize.add(waitList.size());
                waitList.addLast(cust);
            } else { // Starts service.
                cust.servTime = genServ.nextDouble(); //kiszolgálási idő meghatározása
                custWaits.add(0.0);
                queueSize.add(0.0);
                sojourn.add(cust.servTime);
                servList.addLast(cust);
                new Departure().schedule(cust.servTime);
            }
        }
    }

    private class Departure extends Event {

        @Override
        public void actions() {
            servList.removeFirst().addServTimeToStatistic(custServs);
            if (waitList.size() > 0) {
                // Starts service for next one in queue.
                Customer cust = isFIFO() ? waitList.removeFirst() : waitList.removeLast();
                double waitTime = Simulator.getDefaultSimulator().time() - cust.arrivTime;
                custWaits.add(waitTime);                
                sojourn.add(waitTime + cust.servTime);
                servList.addLast(cust);
                new Departure().schedule(cust.servTime);
            }
        }
    }
    
    private class NewObservation extends Event{

        @Override
        public void actions() {
            utilization.add(servList.size());
            new NewObservation().schedule(timeScale);
        }
        
    }

    private class EndOfSim extends Event {

        @Override
        public void actions() {
            Simulator.getDefaultSimulator().stop();
        }
    }

    @Override
    public String getSimResult() {
        this.simulateOneRun();
        this.utilization.getDoubleArrayList().trimToSize();
        this.queueSize.getDoubleArrayList().trimToSize();
        this.custWaits.getDoubleArrayList().trimToSize();
        this.custServs.getDoubleArrayList().trimToSize();
        this.sojourn.getDoubleArrayList().trimToSize();
        
        String result = this.utilization.shortReport() + "\n" +
                this.queueSize.shortReport() + "\n" +
                this.custWaits.shortReport() + "\n" +
                this.custServs.shortReport() + "\n" +
                this.sojourn.shortReport() + "\n" +
                this.custCount.sum();
        //System.out.println(result);
        return result;
    }
    
    @Override
    public void generateChart(String chartType, int batchSize, double confidenceLevel, String chartTitle) {
        if (chartType.equals("utilization")) {
            new BatchMeansMethod(this.utilization.getArray(), batchSize, confidenceLevel, chartTitle).drawBachmeansChart();
        } 
        else if (chartType.equals("queueSize")) {
            new BatchMeansMethod(this.queueSize.getArray(), batchSize, confidenceLevel, chartTitle).drawBachmeansChart();
        }
        else if (chartType.equals("waitTime")) {
            new BatchMeansMethod(this.custWaits.getArray(), batchSize, confidenceLevel, chartTitle).drawBachmeansChart();
        }
        else if (chartType.equals("servTime")) {
            new BatchMeansMethod(this.custServs.getArray(), batchSize, confidenceLevel, chartTitle).drawBachmeansChart();
        }
        else if (chartType.equals("sojourn")) {
            new BatchMeansMethod(this.sojourn.getArray(), batchSize, confidenceLevel, chartTitle).drawBachmeansChart();
        }
    }
}