package net.catpad.infobus.test.dinner;

import java.util.ArrayList;
import java.util.List;

import net.catpad.infobus.ServiceId;
import net.catpad.infobus.ServiceLauncher;
import net.catpad.infobus.ServiceThreadAlreadyRunningException;

/**
 * This is the implementation of the Dijkstra's Dining Philosophers problem.
 * (see, for example, http://en.wikipedia.org/wiki/Dining_philosophers).
 
 * This solutions prevents deadlock by requiring a philosopher to start eating
 * only when he can take both forks from the table. If at least one of the forks
 * is not available, the philosopher will not take another one as well.
 
 * This solution also achieves some fairness by making each philosopher to notify
 * his neighbours that he has finished his meal, i.e. that at least one of
 * their forks is available.
 
 * There is an arbitrary number of philosophers in this program and the same
 * number of service threads: one thread serves one philosopher. In the beginning
 * the main application wakes up all the philosophers by sending them
 * WakeUpCommQueueItem communication queue item which encapsulates the logic of
 * the philosopher's behaviour. The same item is used when the philosophers
 * notify each other about their forks availability.
 
 * At the end each philosopher reports about how many times he ate during the dinner.
 * All tests has shown that a very good fairness is indeed achieved.
 
 @author Michael Gertelman
 *
 */
public class InfoBusTestDinner {

  // The table with 5 forks  
  public static final Table table = new Table();
  
  // We have a very large number of philosophers
  public static final int PHILOSOPHERS_NUMBER = 1000;
  
  private final static ArrayList<Philosopher> philosophers = 
                                         new ArrayList<Philosopher>(PHILOSOPHERS_NUMBER);
  private static List<ServiceId> idList; 
  
  public static void main(String[] args) {    
  
    ServiceLauncher launcher = new ServiceLauncher();
    
    try {
      // Start all service threads that will serve all our philosophers
      idList = launcher.startServices(0, PHILOSOPHERS_NUMBER-1"Service for Philosopher ");      
      
    catch (ServiceThreadAlreadyRunningException e) {
      System.err.println(e.getMessage());
    }

    int counter = 0;
    for (ServiceId id : idList) {
      
      // Create a new philosopher which will be served by the current service thread
      Philosopher philosopher = new Philosopher(counter,id);
      philosophers.add(counter, philosopher);
      
      // Wake up the philosopher's service thread and let it compete for the forks
      launcher.getInfoBus().postItem(id, new WakeUpCommQueueItem(launcher, philosopher));
      
      counter++;
    }
        
    // The dinner is going to be 1 minute long    
    try {
      Thread.sleep(60000);
    catch (InterruptedException e) {
      e.printStackTrace();
    }
        
    // Enough thinking and eating!    
    launcher.shutdownGracefully();
    
    // Wait until all service threads finish their tasks
    try {
      launcher.join();
    catch (InterruptedException e) {
      System.err.println("Join interrupted!");
    }

    System.out.println("The dinner is over");    

    // Let's see how fair it was...
    for (Philosopher philosopher : philosophers) {
      philosopher.printStatistics();
    }
        
  }
  
  public static Philosopher getPhilosopher(int number) {
    return philosophers.get(number);
  }

}