/*
    Project 1
    CES 500, Spring 2006
    Sonoma State University
    David Bozarth
    Current version: 05-23-06
*/

import java.io.*;
import java.util.*;
import java.math.*;
import java.lang.RuntimeException;
   
/**
* Simulation of ATM switch to study buffering and queueing disciplines at its output ports.
* During each time slot, the following events occur in the indicated order:
*  0. For each output queue:
*     a. If queue length is nonzero, then queue length is decremented by one & number in service set to one.
*        Otherwise, number in service is set to zero.
*  1. A received cell appears at each of zero or more inputs with probability P.
*  2. For each such cell: 
*     a. One destination port is assigned as a uniformly distributed random variable.
*     b. The queue length at the destination port is incremented by one.
*     c. The list of occurrences of interarrival intervals for the destination queue is updated.
*     d. The most recent arrival time for the destination queue is updated.
*  3. For each output queue:
*     a. The list of occurrences of queue lengths for that queue is updated.
*     b. The total 'number in system' for that queue is updated.
* After all time slots have been processed, three disk files are written in comma-separated text format:
*  1. Probability mass function of queue length for each queue.
*  2. Probability mass function of interarrival interval for each queue.
*  3. Aggregate queue parameters (Lq, L, Ta [mean interarrival interval]) for each queue.
* Settings:
*  N: (16, 32, or 64): Number of input ports on the switch. (output N = input N)
*  P: (0.6, 0.7, 0.8, 0.9, or 1.0): Probability of receiving a cell at one input during one time slot.
*  DURATION: Number of time slots to be processed.
*  START: The processed time slot number after which data starts being recorded.
*/
public class SimSwitch {
   public static final int N                 = 16;
   public static final double P              = 0.999;
   public static final int DURATION          = 100000;
   public static final int START             = 5000;
   public static final int NO_CELL           = Integer.MAX_VALUE;
   public static final String IN_FILE        = "infile.txt";
   public static final String ARRIVAL_FILE   = "arrival_file.txt";
   public static final String QLENGTH_FILE   = "qlength_file.txt";
   public static final String PARAMTR_FILE   = "paramtr_file.txt";      
   public static final boolean fileFlg       = false;
   public static final boolean echoFlg       = false;  
 
   /**
    * Main routine
    *
    */
    public static void main(String args[]) throws IOException {
      int clock                  = 0;
      int[] length               = new int[N];        // Updated length of the corresponding queue.
      int[] arrival              = new int[N];        // Holds the time slot index of most recent queue arrival.
      int[] service              = new int[N];        // Holds the current value of 'number in service' (1 or 0).
      int[] system               = new int[N];        // Accumulator for 'number in system'.
      int[] lengthSum            = new int[N];        // Accumulator for 'number in queue'.      
      double[] Lq                = new double[N];     // Receives calculated value of mean queue length Lq.
      double[] L                 = new double[N];     // Receives calculated value of mean number in system L.
      double[] Ta                = new double[N];     // Receives calculated value of mean interarrival interval Ta.      
      ArrayList[] interArrival   = new ArrayList[N];  // Histograph of occurrences of interrarival intervals per queue.
      ArrayList[] queueLength    = new ArrayList[N];  // Histograph of occurrences of queue lengths per queue.

      System.out.println();
                        
      initialize(N, length, arrival, service, system, lengthSum, Lq, L, Ta, interArrival, queueLength);
      simulate(N, P, START, DURATION, length, arrival, service, system, lengthSum, interArrival, queueLength);      
      report(N, START, DURATION, system, lengthSum, Lq, L, Ta, interArrival, queueLength);
      
      System.out.println("See output files in current directory:");
      System.out.println("\t" + ARRIVAL_FILE);
      System.out.println("\t" + QLENGTH_FILE);
      System.out.println("\t" + PARAMTR_FILE);
     
   }
   
         
   public static void initialize(int n, 
            int[] length, int[] arrival, int[] service, int[] system, int[] lengthSum,
            double[] Lq, double[] L, double[] Ta,
            ArrayList[] interArrival, ArrayList[] queueLength
            ) {
      for (int i = 0; i < n; ++i) {
         length[i]       = 0;
         arrival[i]      = 0;
         service[i]      = 0;
         system[i]       = 0;
         lengthSum[i]    = 0;         
         Lq[i]           = 0.0;
         L[i]            = 0.0;
         Ta[i]           = 0.0;
         interArrival[i] = new ArrayList(); // List of Integer objects.
         queueLength[i]  = new ArrayList(); // List of Integer objects.                         
      }
                                                         
   }                                      
 

   public static void simulate(int n, double p, int start, int duration, 
                              int[] length, int[] arrival, int[] service, int[] system, int[] lengthSum,
                              ArrayList[] interArrival, ArrayList[] queueLength
                             ) {
      for (int clock = 0; clock < duration; ++clock) {
         // Service one cell from each nonempty queue.
         for (int i = 0; i < n; ++i) {
            if (length[i] > 0) {           
               length[i] -= 1;
               service[i] = 1;               
               if (echoFlg) {
                  System.out.println(clock + ": Queue " + i + " serviced 1 cell. New length " + length[i]);
               }
            }
            else {
               service[i] = 0;
            }              
         }
         
         // Process incoming cells.
         for (int i = 0; i < n; ++i) {
            // Roll a Bernoulli for this input port.            
            double d = Math.random();          
            if (d > p) continue; // no cell

            // Update destination queue.
            int dest = chooseOneRandom(n);
            length[dest] += 1;
            if (echoFlg) {            
               System.out.println(clock + ": From input " + i + ", queue " + dest + " updated to length " + length[dest]);
            }
            
            // Update list of interarrival intervals.
            if (clock >= start) {            
               int interval = clock - arrival[dest];
               ArrayList arl = interArrival[dest];

               if (arl.size() <= interval) {
                  // Fill in the gap with zeros.
                  for (int k = arl.size(); k < interval; ++k) {
                     arl.add( new Integer(0) );   
                  }
                  // Now add a new interval of size 1.
                  arl.add( new Integer(1) );
                  if (echoFlg) {                               
                     System.out.println(clock + ": New interval " + interval + " added for queue " + dest);
                  }
               }
               else {
                  Integer integer = (Integer) arl.get(interval);
                  int x = integer.intValue() + 1;
                  arl.set( interval, new Integer(x) );
                  if (echoFlg) {                  
                     System.out.println(clock + ": Interval " + interval + " increased to " + x + " for queue " + dest);
                  }
               }
            }
            
            // Update the most recent arrival time for this queue.
            arrival[dest] = clock;  
         }
         
         // Update the list of queue length occurrences for each queue.
         if (clock >= start) {
            for (int i = 0; i < n; ++i) {
               ArrayList arl = queueLength[i];
               int qln = length[i];
               lengthSum[i] += qln;               
               system[i]    += (qln + service[i]);
               
               if (arl.size() <= qln) {
                  // Fill in the gap with zeros.
                  for (int k = arl.size(); k < qln; ++k) {
                     arl.add( new Integer(0) );   
                  }
                  // Now add a new interval of size 1.                  
                  arl.add( new Integer(1) );
                  if (echoFlg) {                              
                     System.out.println(clock + ": New queue length " + qln + " added for queue " + i);
                  }                
               }
               else {             
                  Integer integer = (Integer) arl.get(qln);
                  int x = integer.intValue() + 1;
                  arl.set( qln, new Integer(x) );
                  if (echoFlg) {                  
                     System.out.println(clock + ": Queue length " + qln + " increased to " + x + " for queue " + i);
                  }                                 
               }
            }
         }
       }         
                                                         
   }                                  
   
 
   public static void report(int n, int start, int duration, 
                              int system[], int lengthSum[], double Lq[], double L[], double Ta[], 
                              ArrayList[] interArrival, ArrayList[] queueLength) {  
      PrintWriter pwa = null;
      PrintWriter pwq = null;
      PrintWriter pwp = null;
      
      try {
         pwa = new PrintWriter( new FileOutputStream(ARRIVAL_FILE), true );
         pwq = new PrintWriter( new FileOutputStream(QLENGTH_FILE), true );
         pwp = new PrintWriter( new FileOutputStream(PARAMTR_FILE), true );         
         int[] interArrivalHit = new int[n]; // counter
         int[] interArrivalSum = new int[n]; // accumulator         
         int   queueLengthHits  = duration - start; 
         boolean[] aDone = new boolean[n];
         boolean[] qDone = new boolean[n];
         boolean aDoneFlg = false;
         boolean qDoneFlg = false;        
         
         // Initialize the accessory arrays.
         for (int i = 0; i < n; ++i) {
            aDone[i] = false;
            qDone[i] = false;
            ArrayList arl = interArrival[i];
            interArrivalHit[i] = 0;
            interArrivalSum[i] = 0;

            // Store the mean queue length and system load.
            Lq[i] = new Integer(lengthSum[i]).doubleValue() / queueLengthHits;
            L[i]  = new Integer(   system[i]).doubleValue() / queueLengthHits;
                       
            // Tally the sum and number of occurrences of interarrival interval for this queue.
            for (int j = 0 ; j < arl.size(); ++j) {
               Integer integer = (Integer) arl.get(j);
               interArrivalHit[i] += integer.intValue();
               interArrivalSum[i] += ( j * integer.intValue() );               
            }
            
            // Store the mean interarrival interval.
            Ta[i] = new Integer(interArrivalSum[i]).doubleValue() / interArrivalHit[i];         
         }
         
         // Translate the contents of the interArrival & queueLength lists to probability values in CSV format.
         for (int i = 0; !aDoneFlg || !qDoneFlg; ++i) {
            StringBuffer sba = new StringBuffer();
            StringBuffer sbq = new StringBuffer();                       
            sba.append(i);
            sbq.append(i);
            
            for (int j = 0; j < n; ++j) {
               ArrayList a = interArrival[j];
               ArrayList q = queueLength[j];

               String astring = new String();            
               if (a.size() <= i) {
                  aDone[j] = true;
                  astring = new Double(0.0).toString();
               }
               else {
                  double d = ((Integer) a.get(i)).doubleValue() / interArrivalSum[j];
                  astring = new Double(d).toString();
               }
               sba.append("," + astring);

               String qstring = new String();            
               if (q.size() <= i) {
                  qDone[j] = true;
                  qstring = new Double(0).toString();
               }
               else {
                  double d = ((Integer) q.get(i)).doubleValue() / queueLengthHits;
                  qstring = new Double(d).toString();
               }
               sbq.append("," + qstring);
               
            }
            
            pwa.print( sba.toString()); pwa.println();
            pwq.print( sbq.toString()); pwq.println();
            if (echoFlg) {
               System.out.println( sba.toString() );            
               System.out.println( sbq.toString() );
            }
            
            int aCount = 0; int qCount = 0;
            for (int j = 0; j < n; ++j) {
               if (aDone[j] == true) aCount++;
               if (qDone[j] == true) qCount++;
            }
            
            if (aCount == n) aDoneFlg = true;
            if (qCount == n) qDoneFlg = true;                    
         }

         if (echoFlg) {
            for (int i = 0; i < n; ++i) System.out.print(Lq[i] + " ");
            System.out.println();          
            for (int i = 0; i < n; ++i) System.out.print(L[i] + " ");
            System.out.println();          
            for (int i = 0; i < n; ++i) System.out.print(Ta[i] + " ");
            System.out.println();          
         }
                
         // Build the Parameter output file.
         StringBuffer sbp = new StringBuffer();
         writeDoubleCSV(sbp, pwp, "Lq", Lq, n);
                  
         sbp = sbp.delete( 0, sbp.length() );
         writeDoubleCSV(sbp, pwp, "L", L, n);
 
         sbp = sbp.delete( 0, sbp.length() );
         writeDoubleCSV(sbp, pwp, "Ta", Ta, n);
      }
      catch (IOException ex) {
         System.out.println( ex.getMessage() );    
      }
      finally {
         if (pwa != null) {
            pwa.close();          
         }
         if (pwq != null) {
            pwq.close();          
         }
         if (pwp != null) {
            pwp.close();          
         }                                               
      }           
 
   }   

   
   // Precondition: sb is empty.
   public static void writeDoubleCSV(StringBuffer sb, PrintWriter pw, String tag, double[] data, int size) {
      sb.append(tag);
      for (int i = 0; i < size; ++i) {
         String string = new String();
         string = new Double(data[i]).toString();
         sb.append("," + string);            
      }
      pw.print( sb.toString()); pw.println();
   
   }

   
   public static int chooseOneRandom(int size) {
      double d = Math.random() * size;
      //d = Math.rint(d);
      Double dd = new Double(d);
      return dd.intValue();
    
   }
 
   
   public static void getFileInput(String file, ArrayList arrayList, int cols) 
         throws IOException {
      String inLine = null;
      BufferedReader inFile = null;
      
      try {     
           inFile = new BufferedReader( new FileReader(file) );
           while (( inLine = inFile.readLine() ) != null) {              
               double[] row = new double[cols];
               StringTokenizer st = new StringTokenizer(inLine, ",");
               int id = Integer.parseInt(st.nextToken());
               
               for (int i = 0; i < cols; ++i) {
                  String next = st.nextToken();
                  row[i] = Double.parseDouble(next);                  
               }
               
               //Point point = new Point(id, 0, cols, row);
               //arrayList.add(point);  
           }
      }
      catch (IOException ex) {
         System.out.println( ex.getMessage() );
      }
      finally {
         try {
            if (inFile != null) {
               inFile.close();
            }
         }
         catch (IOException ex) {
            System.out.println( ex.getMessage() );            
         }
      }
      
   }
   
   
   public static void putFileOutput(
         PrintWriter pw,
         boolean echo
         ) throws IOException {
      String outLine = null;
           
         //pw.println(string);
         pw.println(" ");
         pw.println();
         
         if (echo == true) {
            //System.out.println(string);
            System.out.println();
         }              
      pw.println();
      System.out.println();
   }
 
     
   public static int max(int one, int two) {
      if (one < two) return two;
      return one;
      
   }
      
      
   public static int min(int one, int two) {
      if (one < two) return one;
      return two;   
      
   }
   
   
   public static int min(int one, int two, int three) {
      int rtn = one;
      if (two < rtn) rtn = two;
      if (three < rtn) rtn = three;
      return rtn;
      
   }
   
   
   public static void showList(ArrayList arrayList) {
      int size = arrayList.size();
      for (int i = 0; i < size; ++i) {
         System.out.print( ((Integer) arrayList.get(i)).intValue() + " " );
      }
      System.out.println();
      
   }   

        
}   // end of class SimSwitch
   
