import umontreal.iro.lecuyer.simevents.*;
import umontreal.iro.lecuyer.simprocs.*;
import umontreal.iro.lecuyer.rng.*;
import umontreal.iro.lecuyer.randvar.ExponentialGen;
import umontreal.iro.lecuyer.stat.Tally;
import java.io.*;
import java.util.*;

public class Jobshop {
   int nbMachTypes;       // Number of machine types M.
   int nbTaskTypes;       // Number of task types N.
   double warmupTime;     // Warmup time T_0.
   double horizonTime;    // Horizon length T.
   boolean warmupDone;    // Becomes true when warmup time is over.
   Resource[] machType;   // The machines groups as resources.
   TaskType[] taskType;   // The task types.
   RandomStream streamArr = new MRG32k3a(); // Stream for arrivals.
   BufferedReader input;

   public Jobshop() throws IOException {
      // SimProcess.init();
      readData();
   }

   // Reads data file, and creates machine types and task types.
   void readData() throws IOException {
      input = new BufferedReader (new FileReader ("Jobshop.dat"));
      StringTokenizer line = new StringTokenizer (input.readLine());
      warmupTime = Double.parseDouble (line.nextToken());
      line = new StringTokenizer (input.readLine());
      horizonTime = Double.parseDouble (line.nextToken());
      line = new StringTokenizer (input.readLine());
      nbMachTypes = Integer.parseInt (line.nextToken());
      nbTaskTypes = Integer.parseInt (line.nextToken());
      machType = new Resource[nbMachTypes];
      for (int m=0; m < nbMachTypes; m++) {
         line = new StringTokenizer (input.readLine());
         String name = line.nextToken();
         int nb = Integer.parseInt (line.nextToken());
         machType[m] = new Resource (nb, name);
      }
      taskType = new TaskType[nbTaskTypes];
      for (int n=0; n < nbTaskTypes; n++)
         taskType[n] = new TaskType();
      input.close();
   }


   class TaskType {
      public String     name;        // Task name.
      public double     arrivalRate; // Arrival rate.
      public int        nbOper;      // Number of operations.
      public Resource[] machOper;    // Machines where operations occur.
      public double[]   lengthOper;  // Durations of operations.
      public Tally      statSojourn; // Stats on sojourn times.

      // Reads data for new task type and creates data structures.
      TaskType() throws IOException {
         StringTokenizer line = new StringTokenizer (input.readLine());
         statSojourn = new Tally (name = line.nextToken()); 
         arrivalRate = Double.parseDouble (line.nextToken());
         nbOper = Integer.parseInt (line.nextToken());
         machOper = new Resource[nbOper];
         lengthOper = new double[nbOper];
         for (int i = 0; i < nbOper; i++) {
            int p = Integer.parseInt (line.nextToken());
            machOper[i] = machType[p-1];
            lengthOper[i] = Double.parseDouble (line.nextToken());
         }
      }

      // Performs the operations of this task (to be called by a process).
      public void performTask (SimProcess p) {
         double arrivalTime = Sim.time();
         for (int i=0; i < nbOper; i++) {
            machOper[i].request (1); p.delay (lengthOper[i]);
            machOper[i].release (1);
         }
         if (warmupDone) statSojourn.add (Sim.time() - arrivalTime);
      }
   }

   public class Task extends SimProcess {
      TaskType type;

      Task (TaskType type) { this.type = type; }

      public void actions() { 
	  // First schedules next task of this type, then executes task.
         new Task (type).schedule (ExponentialGen.nextDouble
               (streamArr, type.arrivalRate));
         type.performTask (this);
      }
   }

   Event endWarmup = new Event() {
      public void actions() {
         for (int m=0; m < nbMachTypes; m++)
            machType[m].setStatCollecting (true);
         warmupDone = true;
      }
   };

   Event endOfSim = new Event() {
      public void actions() { Sim.stop(); }
   };

   public void simulateOneRun() {
      SimProcess.init();
      endOfSim.schedule (horizonTime);
      endWarmup.schedule (warmupTime);
      warmupDone = false;
      for (int n = 0; n < nbTaskTypes; n++) {
         new Task (taskType[n]).schedule (ExponentialGen.nextDouble 
            (streamArr, taskType[n].arrivalRate));
      }
      Sim.start();
   }

   public void printReportOneRun() {
      for (int m=0; m < nbMachTypes; m++) 
         System.out.println (machType[m].report());
      for (int n=0; n < nbTaskTypes; n++) 
         System.out.println (taskType[n].statSojourn.report());
   }

   static public void main (String[] args) throws IOException { 
      Jobshop shop = new Jobshop();
      shop.simulateOneRun();
      shop.printReportOneRun();
   }
}
