import java.util.*;
import javax.swing.*;

//-----------------------------------------------------------------------------
//C a r S i m u l a t o r
//Klassen står for udførslen og koordineringen af selve simulationen
class CarSimulator implements Runnable
{
//Private variabler
  private int MAX_CARQUEUES;             //Angiver det makismale antal af køer
  private int nrOfQueues = MAX_CARQUEUES;//Angiver det nuværende antal køer
  private int meanArrivalTime = 0;       //Angiver den gennemsnitlige ankomst tid i milisekunder for biler
  private int diviasionArrivalTime = 0;  //Angiver ankomst afvigelsen.
  private Thread runner = null;          //Producer tråden
  private CarQueue[] carQueues;          //Array af bil køer.!

//Funktioner
  CarSimulator(CarQueuePanel[] carQueuePanels, SimulationResultPanel simulationResultPanel)
  {
    MAX_CARQUEUES = carQueuePanels.length;
    carQueues = new CarQueue[MAX_CARQUEUES];
    for(int i = 0; i < MAX_CARQUEUES; i++)
      carQueues[i] = new CarQueue(carQueuePanels[i]);

    CarQueue.setSimulationResultPanel(simulationResultPanel);
  }
  public boolean newSimulationSession(int meanArrivalTime, //Gennemsnitlige bil ankomst (sek)
                                      int diviasionArrivalTime, //afvigelsen for ankomsten (sek)
                                      int payTime, //betalings tiden (sek)
                                      int nrOfQueues)//Antallet af ønskede simulerede køer
  {
    if(meanArrivalTime <= 0 || diviasionArrivalTime < 0 || meanArrivalTime < diviasionArrivalTime ||
       payTime <= 0 || nrOfQueues <= 0 || nrOfQueues > MAX_CARQUEUES)
      return false;

    //Statiske variabler i CarQueue klargøres!
    CarQueue.setPaymentTime(payTime);
    CarQueue.resetTimeCalculations();

    this.nrOfQueues = nrOfQueues;
    this.meanArrivalTime = meanArrivalTime * 1000;
    this.diviasionArrivalTime = diviasionArrivalTime *  1000;

    for(int i = 0; i < nrOfQueues; i++)
      carQueues[i].openQueue();
    runner = new Thread(this);
    runner.start();
    return true;
  }
  public void stopSimulation()
  { if(runner != null)
    { runner.interrupt();
      runner = null;
      for(int i = 0; i < nrOfQueues; i++)
        carQueues[i].closeQueue();
    }
  }
  public void run()
  { int timeOffset = meanArrivalTime-diviasionArrivalTime;
    try
    { while(!runner.interrupted())
      { carQueues[findSmallestQueue()].putNewCarInQueue(new Car());
        runner.sleep((int)(Math.random()*2*diviasionArrivalTime + timeOffset));
      }
    }catch(InterruptedException e) {}
  }
  private int findSmallestQueue()
  { int nrOfSmallestQueue = 0;
    int minimum = carQueues[nrOfSmallestQueue].nrOfCarsInQueue();
    for(int i = 1; i < nrOfQueues; i++)
    { if(carQueues[i].nrOfCarsInQueue() < minimum)
      { nrOfSmallestQueue = i;
        minimum = carQueues[i].nrOfCarsInQueue();
      }
    }
    return nrOfSmallestQueue;
  }
}
//-----------------------------------------------------------------------------
//C a r Q u e u e
//Klassen holder styr på selve bil køen og sørger for at hive bilerne ud af køen
//når det bliver deres tur til at betale. Herudover holder klassen også styr på
//de statistiske data som skal indsamles under simulationen.
class CarQueue implements Runnable
{
//Statiske variabler. (tider er angivet i milisekunder!)
  static int payTime = 0;
  static long totalWaitingTime = 0;
  static long maximalWaitingTime = 0;
  static int nrOfProcessedCars = 0;
  static SimulationResultPanel simulationResultPanel = null;

//Statiske funktioner, der virker på de statiske variabler
  public static void setPaymentTime(int time) { payTime = time * 1000; }
  public static void setSimulationResultPanel(SimulationResultPanel srp)
  { simulationResultPanel = srp;
  }
  public static void resetTimeCalculations()
  { totalWaitingTime = 0;
    maximalWaitingTime = 0;
    nrOfProcessedCars = 0;
  }
  //Funktionen tilføjer en færdig bils statistiske data til de statiske variabler.
  private static synchronized void finishedCar(long aCarsWaitingTime)
  { nrOfProcessedCars++;
    simulationResultPanel.setProcessedCars(nrOfProcessedCars); //Grafik
    totalWaitingTime += aCarsWaitingTime;
    simulationResultPanel.setMeanWaitingTime((int)((totalWaitingTime/nrOfProcessedCars)/1000)); //Grafik
    if(aCarsWaitingTime > maximalWaitingTime)
    { maximalWaitingTime = aCarsWaitingTime;
      simulationResultPanel.setMaximalWaitingTime((int)(maximalWaitingTime/1000)); //Grafik
    }
  }

//Private variabler
  private long freeTime = 0;     //Angíver det tidspunkt hvor boden igen bliver fri.
  private long openingTime = 0;  //Angiver det tidspunkt i milis hvor boden blev åbnet!
  private int processedCars = 0; //Antallet af behandlede biler for boden

  private LinkedList carQueue = null;    //Selve bil køen
  private Thread runner = null;          //Consumer tråden!
  private CarQueuePanel myOutputPanel = null; //Noget grafik snavs så vi kan få opdateret parametrene

//Funktioner
  CarQueue(CarQueuePanel myOutputPanel) { this.myOutputPanel = myOutputPanel; }

  public void openQueue()
  { freeTime = 0;
    processedCars = 0;
    openingTime = System.currentTimeMillis();
    carQueue = new LinkedList();
    runner = new Thread(this);
    runner.start();
  }
  public void closeQueue()
  { if(runner != null)
    { runner.interrupt();
      runner = null;
      openingTime = System.currentTimeMillis() - openingTime;
    }
  }
  public void run()
  {
    boolean stoppedWaiting = false;
    long myFreeTime = 0;
    try
    { Car myNextCar = null;
      while(!Thread.interrupted())
      {
        myFreeTime = System.currentTimeMillis();
        stoppedWaiting = true;
        while(!Thread.interrupted() && (myNextCar = nextCar()) == null);

        freeTime += System.currentTimeMillis() - myFreeTime;
        myOutputPanel.setFreeTime((int)(freeTime/1000)); //Grafik
        stoppedWaiting = false;
        try{ Thread.sleep(payTime); }catch(InterruptedException e) { return ; }

        finishedCar(myNextCar.calculateCarWaitingTime());
        processedCars++;

        myOutputPanel.setFinishedCars(processedCars); //Grafik
      }
    }
    catch(InterruptedException e)
    {
      if(stoppedWaiting) //Grafik
        myOutputPanel.setFreeTime((int)((freeTime+(System.currentTimeMillis()-myFreeTime))/1000));
    }
  }
  public synchronized int nrOfCarsInQueue() { return carQueue.size(); }
  //Producer adgang til kø.
  public synchronized void putNewCarInQueue(Car aCar)
  { carQueue.addFirst(aCar);
    myOutputPanel.setWaitingCars(carQueue.size()); //Grafik
    notifyAll();
  }
  //Consumer adgang til kø
  private synchronized Car nextCar() throws InterruptedException
  {
    if(carQueue.size() == 0)
    {
      try{ wait(); }catch(InterruptedException e) { throw e; }
    }
    else
    {
      myOutputPanel.setWaitingCars(carQueue.size()-1); //Grafik
      return (Car)carQueue.removeLast();
    }
    return null;
  }
}
//-----------------------------------------------------------------------------
//C a r
//Klassen holder styr på hvornår en bil ankommer til en bil kø ved broen!
class Car
{
  private long arrivalTime = 0; //Angiver tidspunktet for bilens ankomst til køen
  Car() { arrivalTime = System.currentTimeMillis(); }
  public long calculateCarWaitingTime() { return System.currentTimeMillis() - arrivalTime; }
}
//-----------------------------------------------------------------------------