/****************************************************************************
** COPYRIGHT (C):    1997 Cay S. Horstmann. All Rights Reserved.
** PROJECT:          David Bellin's CRC Card Book
** FILE:             Intersection.java
** PURPOSE:          Simulation of traffic lights at intersection
** VERSION           1.0
** PROGRAMMERS:      Cay Horstmann (CSH)
** RELEASE DATE:     3-15-97 (CSH)
** UPDATE HISTORY:
****************************************************************************/

import java.awt.*;
import java.applet.*;
import java.util.*;

public class Intersection extends Applet implements Timed
{  public void init()
   {  ts = new TrafficStream[7];
         // wasting 0 entry to stay with terminology on paper
      ts[1] = new TrafficStream(XCENTER - WIDTH / 2, 
         YCENTER - WIDTH / 2 - YSTREAM, XSTREAM, YSTREAM, SignalFace.LEFT);
      ts[2] = new TrafficStream(XCENTER + WIDTH / 2 - XSTREAM, 
         YCENTER + WIDTH / 2, XSTREAM, YSTREAM, SignalFace.RIGHT);
      ts[3] = new TrafficStream(XCENTER - WIDTH / 2 - XSTREAM, 
         YCENTER + WIDTH / 2 - YSTREAM, XSTREAM, YSTREAM, SignalFace.RIGHT);
      ts[4] = new TrafficStream(XCENTER + WIDTH / 2, 
         YCENTER - WIDTH / 2, XSTREAM, YSTREAM, SignalFace.LEFT);
      ts[5] = new TrafficStream(XCENTER - WIDTH / 2 - XSTREAM, 
         YCENTER + WIDTH / 2 - 2 * YSTREAM, XSTREAM, YSTREAM, SignalFace.RIGHT);
      ts[6] = new TrafficStream(XCENTER + WIDTH / 2, 
         YCENTER - WIDTH / 2 + YSTREAM, XSTREAM, YSTREAM, SignalFace.LEFT);
      
      phases = new Phase[5];
         // wasting 0 entry to stay with terminology on paper
      phases[1] = new Phase(30, 10); 
      phases[1].addStream(ts[1]).addStream(ts[2]);
      phases[2] = new Phase(40, 10);
      phases[2].addStream(ts[3]).addStream(ts[4]);
      phases[3] = new Phase(40, 10);
      phases[3].addStream(ts[3]).addStream(ts[5]);
      phases[4] = new Phase(40, 10);
      phases[4].addStream(ts[4]).addStream(ts[6]);
      
      setColor(2, SignalFace.GREEN);

      appletThreadGroup = Thread.currentThread().getThreadGroup();
   }
   
   public void paint(Graphics g)
   {  g.drawLine(0, YCENTER - WIDTH / 2, XCENTER - WIDTH / 2, YCENTER - WIDTH / 2);
      g.drawLine(XCENTER - WIDTH / 2, YCENTER - WIDTH / 2, XCENTER - WIDTH / 2, 0);   
      
      g.drawLine(0, YCENTER + WIDTH / 2, XCENTER - WIDTH / 2, YCENTER + WIDTH / 2);
      g.drawLine(XCENTER - WIDTH / 2, YCENTER + WIDTH / 2, XCENTER - WIDTH / 2, 2 * YCENTER);   
      
      g.drawLine(XCENTER + WIDTH / 2, 0, XCENTER + WIDTH / 2, YCENTER - WIDTH / 2);   
      g.drawLine(XCENTER + WIDTH / 2, YCENTER - WIDTH / 2, 2 * XCENTER, YCENTER - WIDTH / 2);
      
      g.drawLine(XCENTER + WIDTH / 2, YCENTER + WIDTH / 2, 2 * XCENTER, YCENTER + WIDTH / 2);
      g.drawLine(XCENTER + WIDTH / 2, YCENTER + WIDTH / 2, XCENTER + WIDTH / 2, 2 * YCENTER);   
      
      for (int i = 1; i < ts.length; i++)
         ts[i].paint(g);
   }
   
   public boolean mouseDown(Event evt, int x, int y) 
   {  for (int i = 1; i < phases.length; i++)
         if (phases[i].mouseDown(x, y))
         {  if (currentState == GREEN)
               setColor(currentPhase, SignalFace.YELLOW);
            else
               repaint();
            return true;   
         }
      return false;
   }
   
   public void elapsed(Timer t)
   {  if (currentState == GREEN_MIN) // end of minimum green phase
      {  boolean found = false;
         int j = currentPhase + 1;
         while (!found && j != currentPhase)
         {  j = j % phases.length;
            if (j == 0) j = 1;
            if (phases[j].needsServicing()) found = true;
            else j++;
         }
         if (found) 
            setColor(currentPhase, SignalFace.YELLOW);
         else 
            currentState = GREEN;
      }
      else if (currentState == YELLOW) // end of yellow phase
      {  setColor(currentPhase, SignalFace.RED);
         boolean found = false;
         int j = currentPhase + 1;
         while (!found)
         {  j = j % phases.length;
            if (j == 0) j = 1;
            if (phases[j].needsServicing()) found = true;
            else j++;
         }
         setColor(j, SignalFace.GREEN);
      }
   }
   
   private void setColor(int i, int color)
   {  int time = phases[i].setColor(color);
      if (color == SignalFace.GREEN) 
      {  currentPhase = i;
         currentState = GREEN_MIN;
      }
      else if (color == SignalFace.YELLOW)
         currentState = YELLOW;
      repaint();
      if (time > 0)
         new Timer(this, time * 1000 / 10).start();
   }
   
   private Phase[] phases;
   private TrafficStream[] ts;
   private int currentPhase;
   private int currentState;
   
   private static int XCENTER = 160;
   private static int YCENTER = 160;
   private static int WIDTH = 120; // width of intersection
   private static int XSTREAM = 4 * SignalFace.RADIUS;
   private static int YSTREAM = 4 * SignalFace.RADIUS; 
   private static int GREEN_MIN = 1;
   private static int GREEN = 2;
   private static int YELLOW = 3;
   
   static ThreadGroup appletThreadGroup;
}

class Phase
{  public Phase(int theGreenTime, int theYellowTime)
   {  greenTime = theGreenTime;
      yellowTime = theYellowTime;
      tstreams = new Vector();
      needsSvc = false;
   }
   
   public Phase addStream(TrafficStream ts)
   {  tstreams.addElement(ts);
      return this;
   }
   
   public int setColor(int color) // returns the minimum time in that color
   {  for (int i = 0; i < tstreams.size(); i++)
         ((TrafficStream)tstreams.elementAt(i)).setColor(color);
      if (color == SignalFace.GREEN) 
      {  needsSvc = false;  
         return greenTime;
      }
      else if (color == SignalFace.YELLOW) return yellowTime;      
      else return 0;
   }
   
   public boolean needsServicing() { return needsSvc; }
   
   public boolean mouseDown(int x, int y)
   {  for (int i = 0; i < tstreams.size(); i++)
         if (((TrafficStream)tstreams.elementAt(i)).mouseDown(x, y))
         {  needsSvc = true;
            return true;
         }
      return false;
   }
   
   private int greenTime;
   private int yellowTime;
   private boolean needsSvc;
   private Vector tstreams;
}

class TrafficStream
{  public TrafficStream(int x, int y, int width, int height, int sensorOrientation)
   {  if (sensorOrientation == SignalFace.LEFT)
      {  detector = new Detector(new Point(x + width / 2, y + height / 2 - SignalFace.RADIUS / 2),
            SignalFace.RADIUS, SignalFace.RADIUS);
         signalFace = new SignalFace(new Point(x, y), SignalFace.RADIUS);
      }
      else
      {  detector = new Detector(new Point(x, y + height / 2 - SignalFace.RADIUS / 2),
            SignalFace.RADIUS, SignalFace.RADIUS);
         signalFace = new SignalFace(new Point(x + width / 2, y), SignalFace.RADIUS);
      }
   }
   
   public void setColor(int color)
   {  signalFace.setColor(color);
      if (color == SignalFace.GREEN) detector.reset();      
   }
   
   public boolean mouseDown(int x, int y)  
   {  if (signalFace.getColor() != SignalFace.GREEN && detector.mouseDown(x, y))
      {  return true;
      }
      return false;
   }
   
   public void paint(Graphics g)
   {  detector.paint(g);
      signalFace.paint(g);
   }
   
   private Detector detector;
   private SignalFace signalFace;
}

class Detector
{  public Detector(Point s, int w, int h)
   {  start = s;
      width = w;
      height = h;
      activated = false;
   }

   public boolean mouseDown(int x, int y)
   {  if (activated) return false;
      if (start.x <= x && x <= start.x + width && start.y <= y && y <= start.y + height)
      {  activated = true;
         return true;
      }
      return false;
   }
   
   public void reset()
   {  activated = false;
   }
   
   public void paint(Graphics g)
   {  g.drawRect(start.x, start.y, height, width);
      if (activated) g.fillRect(start.x, start.y, height, width);
   }

   private Point start;
   int width;   
   int height;
   boolean activated;
}

class SignalFace
{  public SignalFace(Point s, int w)
   {  start = s;
      width = w;
      color = RED;
   }
   
   public void paint(Graphics g)
   {  Color c = Color.red;
      int y = 0;
      if (color == YELLOW) 
      {  c = Color.yellow;
         y = width;
      }
      else if (color == GREEN) 
      {  c = Color.green;
         y = 2 * width;
      }
      g.setColor(c);
      g.fillOval(start.x, start.y + y, width, width);
      g.setColor(Color.black);
      for (int i = 0; i < 3; i++)
      g.drawOval(start.x, start.y + i * width, width, width);
   }
   
   public int getColor() { return color; }
   
   public void setColor(int c) { color = c; }
   
   public static int RED = 0;
   public static int YELLOW = 1;   
   public static int GREEN = 2;
   public static int RADIUS = 8;
   public static int RIGHT = 1;
   public static int LEFT = -1;
   
   private int color;
   private Point start;
   private int width;
}

interface Timed
{  public void elapsed(Timer t);
}

class Timer extends Thread
{  public Timer(Timed t, int i)
   {  super(Intersection.appletThreadGroup, "Timer");
      target = t; interval = i;
      setDaemon(true);
   }
   
   public void run()
   {  try { sleep(interval); }
      catch(InterruptedException e) {}
      target.elapsed(this);
   }
   
   private Timed target;
   private int interval;
}


