import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;

/**
 * A simple graphical applet to demonstrate a multi-thread solution
 * to the N dining philosophers problem.
 */

public class DiningApplet extends Applet 
    implements Observer, ActionListener, ItemListener {
    // GUI member variables
    DiningCanvas picture;
    TextArea  logarea;
    Label     legend;
    Label     caption;
    Label     status;
    Checkbox  fastBox;
    TextField counterField;
    Button    startButton;
    Polygon   chopstickPics[];
    Point     philPics[];
    double    rad, philrad, stickrad;
    int       runningthreads;
    boolean   eatfast;

    // Constants - change these to adjust the simulation
    public static final int BASIC_DELAY = 2000;
    public static final int BASIC_APPETITE = 4;
    public static final int DEFAULT_PHILOSOPHERS = 5;

    // functional members, don't mess with these
    int count;
    Philosopher diners[];
    Utensil chopsticks[];

    public void init() {
	count = DEFAULT_PHILOSOPHERS;
	diners = null;
	chopsticks = null;

	// GUI stuff
	setLayout(new GridLayout(2,1));
	picture = new DiningCanvas();
	add(picture);

	Panel bottompanel = new Panel();
	bottompanel.setLayout(new BorderLayout());

	logarea = new TextArea();
	bottompanel.add("Center", logarea);

	Panel controlpanel = new Panel();
	legend = new Label("Legend: Blue=thinking, Yellow=hungry, Green=eating, Gray=done", Label.CENTER);
	legend.setBackground(Color.pink);
	caption = new Label("Philosophers:", Label.RIGHT);
	counterField = new TextField("" + count, 3);
	startButton = new Button("START");
	fastBox = new Checkbox("Slow  ", false);
	status = new Label("                          Initializing", Label.RIGHT);
	startButton.addActionListener(this);
	fastBox.addItemListener(this);
	controlpanel.add(caption);
	controlpanel.add(counterField);
	controlpanel.add(startButton);
	controlpanel.add(fastBox);
	controlpanel.add(status);
	bottompanel.add("North",legend);
	bottompanel.add("South", controlpanel);
	add(bottompanel);
	runningthreads = 0;
    }

    public void start() {
	status.setText("Click on START to start");
    }

    public void beginSimulation() {
	int i;

	if (runningthreads > 0) {
	    logarea.append("Aborting simulation before completion.\n");
	    for(i = 0; i < diners.length; i++) {
		diners[i].stop();
	    }
	    runningthreads = 0;
	}
	picture.clear();
	String cntString = counterField.getText();
	try {
	    count = Integer.parseInt(cntString); 
	}
	catch (NumberFormatException nfe) {
	    count = DEFAULT_PHILOSOPHERS;
	    counterField.setText("" + count);
	}
	logarea.append("\nBeginning simulation with " + count + " philosophers\n");

	synchronized (picture) {
	    diners = new Philosopher[count];
	    chopsticks = new Utensil[count];
	}

	Dimension psize = picture.getSize();
	Point center = new Point(psize.width / 2, psize.height / 2);
	rad = ((psize.width < psize.height)?
	       ((psize.width * 4)/5):((psize.height * 4) / 5)) / 2.0;
	philrad = (rad/count) * 2.1;
	stickrad = rad - philrad;
	philPics = new Point[count];
	int csx[] = new int[4];
	int csy[] = new int[4];
	chopstickPics = new Polygon[count];
	double phi;
	double div = (2 * Math.PI)/(count * 2);
	double swdiv = Math.PI / 90;
	int c;
	for(c = 0, phi = 0.0; c < count; c++, phi += div) {
	    philPics[c] = new Point((int)(rad * Math.sin(phi)) + center.x,
				    (int)(rad * Math.cos(phi)) + center.y);
	    phi += div;
	    csx[0] = (int)(rad * Math.sin(phi)) + center.x;
	    csy[0] = (int)(rad * Math.cos(phi)) + center.y;
	    csx[1] = (int)(stickrad * Math.sin(phi)) + center.x;
	    csy[1] = (int)(stickrad * Math.cos(phi)) + center.y;
	    csx[2] = (int)(stickrad * Math.sin(phi + swdiv)) + center.x;
	    csy[2] = (int)(stickrad * Math.cos(phi + swdiv)) + center.y;
	    csx[3] = (int)(rad * Math.sin(phi + swdiv)) + center.x;
	    csy[3] = (int)(rad * Math.cos(phi + swdiv)) + center.y;
	    chopstickPics[(c + 1) % count] = new Polygon(csx,csy,4);
	}

	for(i = 0; i < count; i++) {
	    chopsticks[i] = new Utensil("Chopstick " + i, chopstickPics[i]);
	    chopsticks[i].addObserver(picture);
	}

	for(i = 0; i < count; i++) {
	    diners[i] = new Philosopher("Diner " + i, 
					chopsticks[i],
					chopsticks[(i + 1) % count],
					philPics[i]);
	    diners[i].setEatFast(eatfast);
	    diners[i].addObserver(this);
	    diners[i].addObserver(picture);
	}
	for(runningthreads = 0; runningthreads < count; runningthreads++) {
	    diners[runningthreads].start();
	}
	picture.repaint();
	status.setText("Simulated philosophers: " + count);
    }

    public void actionPerformed(ActionEvent evt) {
	beginSimulation();
    }
    public void itemStateChanged(ItemEvent evt) {
	eatfast = (!(fastBox.getState()));
	if (diners != null) synchronized (this) {
	    for(int i = 0; i < count; i++) {
		diners[i].setEatFast(eatfast);
	    }
	}
	return;
    }

    public void stop() {
	if (runningthreads > 0) {
	    for(int i = 0; i < diners.length; i++) {
		diners[i].stop();
	    }
	    runningthreads = 0;
	}
	return;
    }

    public void update(Observable obj, Object r) {
	if (r instanceof String) synchronized(this) {
	    logarea.append("\n");
	    logarea.append(r.toString());
	    if (obj instanceof Philosopher) {
		Philosopher p = (Philosopher)obj;
		if (p.status == Philosopher.STATUS_DONE) {
		    runningthreads--;
		}
		if (runningthreads <= 0) {
		    status.setText("Simulation done.");
		}
	    }
	}
	return;
    }

    class DiningCanvas extends Canvas implements Observer {
	Color bg;
	Color statColors[];
	Color stickColor;
	Color textColor;

	public DiningCanvas() {
	    bg = Color.pink;
	    setBackground(bg);
	    stickColor = new Color(0x33, 0x66, 0x33);
	    textColor = Color.black;
	    statColors = new Color[8];
	    statColors[Philosopher.STATUS_DONE] = Color.darkGray;
	    statColors[Philosopher.STATUS_THINKING] = Color.blue;
	    statColors[Philosopher.STATUS_GRABBING] = Color.yellow;
	    statColors[Philosopher.STATUS_EATING] = Color.green;

	}

	public void clear() {
	    Graphics g = getGraphics();
	    g.setColor(bg);
	    Dimension d = getSize();
	    g.fillRect(0, 0, d.width, d.height);
	}

	public void paintItem(Object o, Graphics g) {
	    if (o instanceof Utensil) {
		Utensil u = (Utensil)o;
		g.setColor((u.isAvailable())?(stickColor):(bg));
		g.fillPolygon((Polygon)(u.observerRef));
	    }
	    else if (o instanceof Philosopher) {
		Philosopher p = (Philosopher)o;
		g.setColor(statColors[p.status]);
		Point pp = (Point)(p.observerRef);
		int pr = (int)philrad;
		g.fillOval(pp.x - ((int)(pr/2)),
			   pp.y - ((int)(pr/2)),
			   pr, pr);
		g.setColor(textColor);
		g.drawOval(pp.x - ((int)(pr/2)),
			   pp.y - ((int)(pr/2)),
			   pr, pr);
		// paintItem(p.sticks[0], g);
		// paintItem(p.sticks[1], g);
	    }
	    return;
	}

	public void paint(Graphics g) {
	    int i;
	    Utensil u;
	    Philosopher p;
	    if (diners != null && diners.length > 1) synchronized (this) {
		for(i = 0; i < count; i++) {
		    u = chopsticks[i];
		    paintItem(u,g);
		}
		for(i = 0; i < count; i++) {
		    p = diners[i];
		    paintItem(p,g);
		}
	    }
	    return;
	}

	public void update(Observable obj, Object r) {
	    if (!(r instanceof String))
		paintItem(obj, getGraphics());
	}
		
    }

}


