import java.awt.*;
import Utilities.*;
import Synchronization.*;
import XtangoAnimation.*;

// message types
class Hungry {} // philosopher sends this to its servant
class NeedL {}  // servants
class NeedR {}  // send
class PassL {}  // these to
class PassR {}  // each other

class Philosopher extends MyObject implements Runnable {

   private int id = 0;
   private int napThink = 0; // both are in
   private int napEat = 0;   // milliseconds
   private Servant myServant = null;
   private Thread me = null;

   public Philosopher(String name, int id, int napThink,
         int napEat, Servant myServant) {
      super(name + " " + id);
      this.id = id;
      this.napThink = napThink;
      this.napEat = napEat;
      this.myServant = myServant;
      System.out.println(getName() + " is alive, napThink="
         + napThink + ", napEat=" + napEat);
      me = new Thread(this);  me.start();
   }

   private void think() {
      int napping;
      napping = 1 + (int) random(napThink);
      System.out.println("age()=" + age() + ", " + getName()
         + " is thinking for " + napping + " ms");
      nap(napping);
   }

   private void eat() {
      int napping;
      napping = 1 + (int) random(napEat);
      System.out.println("age()=" + age() + ", " + getName()
         + " is eating for " + napping + " ms");
      nap(napping);
   }

   public void run() {
      while (true) {
         think();
         System.out.println("age()=" + age() + ", " + getName()
            + " wants to eat");
         myServant.takeForks(id);
         eat();
         myServant.putForks(id);
      }
   }

   public void stop() { me.stop(); }
}

class ServantCondition extends MyObject implements Condition {

   private boolean hungry = false;
   private boolean dirtyL = false, dirtyR = false;

   public ServantCondition(boolean hungry, boolean dirtyL, boolean dirtyR) {
      super("ServantCondition: hungry=" + hungry
         + " dirtyL=" + dirtyL + " dirtyR=" + dirtyR);
      this.hungry = hungry;
      this.dirtyL = dirtyL;
      this.dirtyR = dirtyR;
   }

   public boolean checkCondition(Object m) {
      if (m instanceof Hungry) return true;
      else if (!hungry) return true;
      else if (m instanceof PassL || m instanceof PassR) return true;
      else if (m instanceof NeedL && dirtyL) return true;
      else if (m instanceof NeedR && dirtyR) return true;
      else return false;
   }
}

class Servant extends MyObject implements Runnable {

   // This group of fields is initialized by the constructor.
   private int id = 0;
   private AsyncConditionalMessagePassing myChannel = null;
   private AsyncConditionalMessagePassing leftServantChannel = null;
   private AsyncConditionalMessagePassing rightServantChannel = null;
   private boolean haveL = false, dirtyL = false;
   private boolean haveR = false, dirtyR = false;
   private XtangoAnimator xa = null;
   private String forkL = null, forkR = null;
   private String holderL = null, holderR = null;

   private BinarySemaphore eat = new BinarySemaphore(0);
   private BinarySemaphore releaseForks = new BinarySemaphore(0);

   public Servant(String name, int id,
         String forkL, String forkR, String holderL, String holderR,
         boolean haveL, boolean dirtyL, boolean haveR, boolean dirtyR,
         AsyncConditionalMessagePassing myChannel,
         AsyncConditionalMessagePassing leftServantChannel,
         AsyncConditionalMessagePassing rightServantChannel,
         XtangoAnimator xa) {
      super(name + " " + id);
      this.id = id;
      this.haveL = haveL; this.dirtyL = dirtyL;
      this.haveR = haveR; this.dirtyR = dirtyR;
      this.forkL = forkL; this.forkR = forkR;
      this.holderL = holderL; this.holderR = holderR;
      this.myChannel = myChannel;
      this.leftServantChannel = leftServantChannel;
      this.rightServantChannel = rightServantChannel;
      this.xa = xa;
      System.out.println("Servant " + getName() + " is alive:\n"
         + "forkL=" + forkL + ", forkR=" + forkR
         + ", holderL=" + holderL + ", holderR=" + holderR + "\n"
         + "haveL=" + haveL + ", haveR=" + haveR
         + ", dirtyL=" + dirtyL + ", dirtyR=" + dirtyR);
      new Thread(this).start();
   }

   public void takeForks(int id) {
      xa.color("phil"+id, Color.green);                // animation
      xa.fill("phil"+id, xa.SOLID);                    // animation
      myChannel.send(new Hungry());  // non blocking
      P(eat);                  // wait for empty message
      xa.color("phil"+id, Color.blue);                 // animation
      xa.fill("phil"+id, xa.SOLID);                    // animation
   }

   public void putForks(int id) {

      xa.fill("phil"+id, xa.OUTLINE);                  // animation
      xa.color("phil"+id, Color.black);                // animation
      V(releaseForks);         // send empty message
   }
/*
 * Some animation ideas: when a hungry philosopher receives a fork, change
 * its color to HALF (green or red); when a hungry philosopher has no forks
 * and it gets asked for one...cannot happen...; when a hungry philosopher
 * gets asked for a dirty fork it has, change its color to SOLID red for
 * frustration since it has to give it up.
 */

   public void run() {
// Move a left or right fork initially given to this philosopher to be next
// to the philosopher.
      if (haveR) xa.jumpTo(forkR, holderR);          // animation...
      if (haveL) xa.jumpTo(forkL, holderL);
      if (dirtyR) {
         xa.color(forkR, Color.orange);
      }
      if (dirtyL) {
         xa.color(forkL, Color.orange);
      }                                              // ...animation
      Object message = null;
      ServantCondition sc = null;
      boolean hungry = false;
      while (true) {
         sc = new ServantCondition(hungry, dirtyL, dirtyR);
         message = myChannel.receive(sc);
         if (message instanceof Hungry) {
            hungry = true;
            if (!haveR) rightServantChannel.send(new NeedL());
            else System.out.println
               (age() + " hungry philosopher " + id + " has right fork");
            if (!haveL) leftServantChannel.send(new NeedR());
            else System.out.println
               (age() + " hungry philosopher " + id + " has left fork");
            while (!(haveR && haveL)) {  // while hungry, wait for forks
               sc = new ServantCondition(hungry, dirtyL, dirtyR);
               message = myChannel.receive(sc);
               if (message instanceof PassL) {  // left servant sends fork
// Move the fork from where it was to be next to this philosopher and then
// change its symbol to be light gray, i.e., not dirty.
                  xa.moveTo(forkL, holderL);
                  xa.color(forkL, Color.lightGray);
                  xa.fill("phil"+id, xa.HALF);
                  haveL = true; dirtyL = false;
                  System.out.println
                     (age() + " hungry philosopher " + id + " got left fork");
               } else if (message instanceof PassR) {
                  // right servant sends fork
// Ditto.
                  xa.moveTo(forkR, holderR);
                  xa.color(forkR, Color.lightGray);
                  xa.fill("phil"+id, xa.HALF);
                  haveR = true; dirtyR = false;
                  System.out.println
                     (age() + " hungry philosopher " + id + " got right fork");
               } else if (message instanceof NeedL) {  // dirtyL is true
                  // hungry philosopher must relinquish dirty fork
                  // to avoid starvation
                  haveL = false; dirtyL = false;
                  leftServantChannel.send(new PassR());
                  leftServantChannel.send(new NeedR());
                  xa.color("phil"+id, Color.red);
                  System.out.println(age() + " hungry philosopher "
                     + id + " sends dirty left fork");
               } else if (message instanceof NeedR) {  // dirtyR is true
                  // hungry philosopher must relinquish dirty fork
                  // to avoid starvation
                  haveR = false; dirtyR = false;
                  rightServantChannel.send(new PassL());
                  rightServantChannel.send(new NeedL());
                  xa.color("phil"+id, Color.red);
                  System.out.println(age() + " hungry philosopher "
                     + id + " sends dirty right fork");
               } else System.err.println
                  ("Servant" + id + " received bogus message!");
            }
            System.out.println
               (age() + " philosopher " + id + " has both forks");
            V(eat); dirtyR = true; dirtyL = true;
// Now that the philosopher is eating, its forks are getting dirty so
// change their symbols to be yellow.
            xa.color(forkL, Color.yellow);
            xa.color(forkR, Color.yellow);
            P(releaseForks);
            hungry = false;
// Now that the philosopher has finished eating, its forks are dirty so
// change their symbols to be orange.
            xa.color(forkL, Color.orange);
            xa.color(forkR, Color.orange);
            System.out.println
               (age() + " philosopher " + id + " is finished eating");
         } else if (message instanceof NeedR) {
            // not hungry and have right fork
            if (!haveR) System.err.println
               ("Servant " + id + " asked for right fork it does not have!");
            haveR = false; dirtyR = false; rightServantChannel.send(new PassL());
            System.out.println
               (age() + " unhungry philosopher " + id + " sends right fork");
         } else if (message instanceof NeedL) {
            // not hungry and have left fork
            if (!haveL) System.err.println
               ("Servant " + id + " asked for left fork it does not have!");
            haveL = false; dirtyL = false; leftServantChannel.send(new PassR());
            System.out.println
               (age() + " unhungry philosopher " + id + " sends left fork");
         } else
            System.err.println("Servant" + id + " received bogus message!");
      }
   }
}

class AnimatedDP extends MyObject {

   private static final double TWO_PI = 2.0 * Math.PI;

   public static void main(String[] args) {

      // parse command line options, if any, to override defaults
      GetOpt go = new GetOpt(args, "Up:R:");
      go.optErr = true;
      String usage = "Usage: -p numPhilosophers"
               + " -R runTime napThink[i] napEat[i] i=0,1,...";
      int ch = -1;
      int numPhilosophers = 5;
      int runTime = 60;      // seconds
      while ((ch = go.getopt()) != go.optEOF) {
         if      ((char)ch == 'U') {
            System.out.println(usage);  System.exit(0);
         }
         else if ((char)ch == 'p') numPhilosophers =
            go.processArg(go.optArgGet(), numPhilosophers);
         else if ((char)ch == 'R')
            runTime = go.processArg(go.optArgGet(), runTime);
         else {
            System.err.println(usage);  System.exit(1);
         }
      }
      System.out.println("AnimatedDP: numPhilosophers="
         + numPhilosophers + ", runTime=" + runTime);

      // process non-option command line arguments
      int[] napThink = new int[numPhilosophers];
      int[] napEat = new int[numPhilosophers];
      int argNum = go.optIndexGet();
      for (int i = 0; i < numPhilosophers; i++) {
         napThink[i] = 8; napEat[i] = 2;  // defaults
         napThink[i] = go.tryArg(argNum++, napThink[i]);
         napEat[i] = go.tryArg(argNum++, napEat[i]);
      }

      XtangoAnimator xa = new XtangoAnimator();          // animation...
      xa.begin();
// Change coordinates so 0,0 is the center, then create a big black outline
// circle to be the table.
      xa.coords(-1.5f, -1.5f, 1.5f, 1.5f);
      xa.circle("C0", 0.0f, 0.0f, 1.0f, Color.black, xa.OUTLINE);
      // bowl of spaghetti
      xa.circle("Cs", 0.0f, 0.0f, 0.5f, Color.orange, xa.HALF);
// Put some annotated symbols on the screen.
      xa.circle("cf", -1.4f, -0.6f, 0.02f, Color.lightGray, xa.SOLID);
      xa.text("cft", -1.3f, -0.625f, false, Color.black, "clean fork");
      xa.circle("uf", -1.4f, -0.7f, 0.02f, Color.yellow, xa.SOLID);
      xa.text("uft", -1.3f, -0.725f, false, Color.black, "in-use fork");
      xa.circle("df", -1.4f, -0.8f, 0.02f, Color.orange, xa.SOLID);
      xa.text("dft", -1.3f, -0.825f, false, Color.black, "dirty fork");
      xa.circle("C1", -1.4f, -0.9f, 0.05f, Color.black, xa.OUTLINE);
      xa.text("T1", -1.3f, -0.925f, false, Color.black, "THINKING");
      xa.circle("C2", -1.4f, -1.0f, 0.05f, Color.green, xa.SOLID);
      xa.text("T2", -1.3f, -1.025f, false, Color.black, "HUNGRY");
      xa.circle("C3", -1.4f, -1.1f, 0.05f, Color.blue, xa.SOLID);
      xa.text("T3", -1.3f, -1.125f, false, Color.black, "EATING");
      xa.circle("C4", -1.4f, -1.2f, 0.05f, Color.red, xa.SOLID);
      xa.text("T4", -1.3f, -1.225f, false, Color.black,
         "HUNGRY and had to give up a dirty fork");
      xa.circle("C5", -1.4f, -1.3f, 0.05f, Color.red, xa.HALF);
      xa.circle("C6", -1.3f, -1.3f, 0.05f, Color.green, xa.HALF);
      xa.text("T6", -1.2f, -1.325f, false, Color.black,
         "HUNGRY and received a fork");
// Seat the philosophers around the table.  Philosopher 1 is to the left of
// philosopher 0 and fork 0 is to the left of philosopher 0.  Put a clean
// fork between each philosopher.
      float gap = (float) TWO_PI/numPhilosophers;
      for (int i = 0; i < numPhilosophers; i++) {
         double radianp = i*gap;
         float sinp = (float) Math.sin(radianp);
         float cosp = (float) Math.cos(radianp);
         xa.circle("phil"+i, sinp, cosp, 0.2f*gap, Color.black, xa.OUTLINE);
         xa.bigText("TP"+i, sinp*0.55f, cosp*0.55f, true, Color.black, ""+i);
         double radianf = radianp + 0.5f*gap;
         float sinf = (float) Math.sin(radianf);
         float cosf = (float) Math.cos(radianf);
         xa.pointLine("fork"+i, sinf, cosf, 0.4f*sinf, 0.4f*cosf,
            Color.lightGray, xa.THICK);
// Put nearly invisible circles (points) on the left and right side of each
// philosopher to be places the forks can be moved to when the philosopher
// gets possession of a fork.
         double radianLf = radianp + 0.2f*gap;
         double radianRf = radianp - 0.2f*gap;
         float sinLf = (float) Math.sin(radianLf);
         float cosLf = (float) Math.cos(radianLf);
         float sinRf = (float) Math.sin(radianRf);
         float cosRf = (float) Math.cos(radianRf);
         xa.circle("forkL"+i, sinLf, cosLf, 0.001f, Color.black, xa.OUTLINE);
         xa.circle("forkR"+i, sinRf, cosRf, 0.001f, Color.black, xa.OUTLINE);
      }
      xa.delay(10);                             // ...animation

      // create the communication channels
      AsyncConditionalMessagePassing[] channel
         = new AsyncConditionalMessagePassing[numPhilosophers];
      for (int i = 0; i < numPhilosophers; i++) {
         channel[i] = new AsyncConditionalMessagePassing(false);
      }

      // set up the initial fork locations; this is not symmetric
      // to avoid deadlock; all philosophers have their left fork
      // clean except the last one who has no forks; the first
      // philosopher has both forks dirty
      boolean[] haveL = new boolean[numPhilosophers];
      boolean[] haveR = new boolean[numPhilosophers];
      boolean[] dirtyL = new boolean[numPhilosophers];
      boolean[] dirtyR = new boolean[numPhilosophers];
      String[] forkL = new String[numPhilosophers];
      String[] forkR = new String[numPhilosophers];
      String[] holderL = new String[numPhilosophers];
      String[] holderR = new String[numPhilosophers];
      for (int i = 0; i < numPhilosophers; i++) {
         forkL[i] = "fork" + i;
         forkR[i] = "fork" + ((i-1+numPhilosophers)%numPhilosophers);
         holderL[i] = "forkL" + i;  holderR[i] = "forkR" + i;
         haveL[i] = true;
         haveR[i] = dirtyL[i] = dirtyR[i] = false;
      }
      haveL[0] = haveR[0] = dirtyL[0] = dirtyR[0] = true;
      haveL[numPhilosophers-1] = false;

      // create the Servants with self-starting threads
      Servant[] servant = new Servant[numPhilosophers];
      for (int i = 0; i < numPhilosophers; i++) {
         servant[i] = new Servant("Servant", i,
            forkL[i], forkR[i], holderL[i], holderR[i],
            haveL[i], dirtyL[i], haveR[i], dirtyR[i],
            channel[i],                           // this Servant's channel
            channel[(i+1)%numPhilosophers],       // left neighbor's channel
            channel[(i-1+numPhilosophers)%numPhilosophers],
                                               // right neighbor's channel
            xa);
      }
      xa.delay(10);

      // create the Philosophers with self-starting threads
      Philosopher[] phil = new Philosopher[numPhilosophers];
      for (int i = 0; i < numPhilosophers; i++)
         phil[i] = new Philosopher("Philosopher", i,
            napThink[i]*1000, napEat[i]*1000, servant[i]);
      System.out.println("All Philosopher and Servant threads started");

      // let the Philosophers and Servants run for a while
      nap(runTime*1000);
      System.out.println("age()=" + age()
         + ", time to stop the Philosophers and Servants and exit");
      for (int i = 0; i < numPhilosophers; i++) phil[i].stop();
      xa.end();
      System.exit(0);
   }
}

/* ............... Example compile and run(s)

D:\Animations>javac addp.java

D:\Animations>java AnimatedDP -R10 4 2 4 2 4 2 4 2 4 2
                                            ... end of example run(s)  */
