import java.lang.*;
import java.awt.*;      
import java.util.*;
public class MeterCanvas extends Canvas implements Observer {
 Movie movie;
 int minimum, maximum;  // Minimum and maximum values displayed
 int curval;            // Currently displayed value
 String title;          // Title string for the meter
 int xpoints[];         // Coordinates for the arrows endpoints
 int ypoints[];         // Coordinates for the arrows endpoints
 int yoffset;           // Gap at the bottom
 // Zero-parameter constructor calls the three-parameter constructor:
 public MeterCanvas() {this("Title to be Supplied", 0, 30);}
 // Three-parameter constructor takes three arguments specifying the
 // title and the minimum and maximum values that the meter displays:
 public MeterCanvas(String titleString, int minVal, int maxVal) {
  title   = titleString;
  minimum = minVal;  maximum = maxVal;
  // Set the current value to be average:
  curval = (minimum+maximum)/ 2;
  // Create the arrays for holding the arrow end points:
  xpoints = new int[3]; ypoints = new int[3];
 }
 // Title setter:
 public void setTitle(String s) {title = s; repaint();}
 // Value setter:
 public void setValue(int value) {
  // If the value is less than the minimum, set to minimum:
  if (value < minimum) {
   curval = minimum; 
  }
  // If the value is greater than the maximum, set to maximum:
  else if (value > maximum) {
   curval = maximum; 
  }
  else {
   curval = value;
  }
  repaint();
 }
 // Value getter:
 public int getValue() {
        return curval;
 }
 // Establish model-viewer connection:
 public void attachObservable (Movie m) {  
  if (m != movie) {          
   if (movie != null) {movie.deleteObserver(this);}  
   movie = m;             
   movie.addObserver(this);
   movie.changed();       
  }
 }
 // Honor requirement of observer interface:
 public void update (Observable o, Object x) {         
  setTitle(movie.getTitle());                          
  setValue(movie.rating());                            
 }                                                     
 // Draw the arrow, which consists of a line and a triangle:
 public void drawValue(Graphics g, Dimension d, int r) {
  int xoff   = d.width/2;
  int yoff   = d.height/2+10;
  // Compute the angle dictated by the value:
  double angle  = ((double)(curval - minimum)
                   / (double)(maximum - minimum))
                  * Math.PI;
  // Increase the angle from left to right:
  angle = Math.PI - angle;
  double cang = Math.cos(angle);
  double sang = Math.sin(angle);
  int x2 = (int)(r * cang);
  int y2 = (int)(r * sang);
  // Draw the line (note that yoff-y2 is computed because the
  // window coordinates have y increasing downward):
  g.drawLine(xoff, yoff, x2+xoff, yoff-y2);
  int lArrow = 20;              // Length of the arrow
  int wArrow = 4;               // Half width of the arrow
  // Compute the points on the triangle border:
  xpoints[0] = x2 + xoff;
  ypoints[0] = yoff - y2;
  xpoints[1] = x2 - (int)(lArrow * cang) - (int)(wArrow * sang) + xoff;
  ypoints[1] = yoff - (y2 - (int )(lArrow * sang) + (int)(wArrow * cang));
  xpoints[2] = x2 - (int)(lArrow * cang) + (int)(wArrow * sang) + xoff;
  ypoints[2] = yoff - (y2 - (int)(lArrow * sang) - (int)(wArrow * cang));
  // Draw a filled polygon for the triangle:
  g.fillPolygon(xpoints, ypoints, 3);
 }
 // Draws the tick marks associated with the meter:
 public int drawTics(Graphics g, Dimension d, FontMetrics f) {
  double angInc = Math.PI / 50.0;
  double angle = 0.0;
  int xoff = d.width/2;
  int yoff = d.height/2+10;
  int radius = Math.min(d.height/2-10, d.width/2-4);
  int iradius = radius - (radius / 10);
  int sradius   = radius - (radius / 20);
  // Draw an arc on which to draw the tics:
  g.drawArc(xoff - radius, yoff - radius,
            radius * 2, radius * 2, 0, 180);
  g.fillOval(xoff - 4, yoff - 4, 8, 8);
  // Try to draw about 50 tick marks with about 10 major ticks
  for (int i=0; i<=50; i++, angle += angInc) {
   double cang = Math.cos(angle);
   double sang = Math.sin(angle);
   int x1 = (int)(radius * cang) + xoff;
   int y1 = yoff - (int)(radius * sang);
   if (i%5 == 0) {
    int x2 = (int)(iradius * cang) + xoff;
    int y2 = yoff - (int)(iradius * sang);
    g.drawLine(x1, y1, x2, y2);
   }
   else {
    int x2 = (int)(sradius * cang) + xoff;
    int y2 = yoff - (int)(sradius * sang);
    g.drawLine(x1, y1, x2, y2);         
   }
  }
  // Draw the min and max labels:
  int fontHeight = f.getHeight();
  int fontDescent = f.getDescent();
  int stringWidth;
  String sval = String.valueOf(minimum);
  stringWidth = f.stringWidth(sval);
  // Draw the minimum-value string:
  g.drawString(sval, xoff - sradius + 10, yoff - fontDescent);
  sval = String.valueOf(maximum);
  stringWidth = f.stringWidth(sval);
  // Draw the maximum-value string:
  g.drawString(sval,
               xoff + sradius - stringWidth - 10, 
               yoff - fontDescent);     
  return sradius;
 }
 // Write the title associated with the meter, centered at bottom:
 public void drawTitle(Graphics g, Dimension d, FontMetrics f) {
  int stringWidth = f.stringWidth(title);
  int fontHeight = f.getHeight();
  int fontDescent = f.getDescent();
  g.drawString(title, 
               (d.width-stringWidth) / 2, 
               d.height / 2 + 10 + fontHeight + fontDescent);
 }
 // Paint, by calling other methods:
 public void paint(Graphics g) {
  Dimension d = getSize();
  FontMetrics fm = g.getFontMetrics();
  yoffset = (fm.getHeight() * 2);
  int ir = drawTics(g, d, fm);
  drawValue(g, d, ir);
  drawTitle(g, d, fm);
 }
 // Assist in sizing:
 public Dimension getMinimumSize() {return new Dimension(150, 150);}
 public Dimension getPreferredSize() {return new Dimension(150, 150);}
}
