// Hand class, September/October 2001
//
// Designed for Brian's Superbly Incredible and Well-Commented Non-Functional
// Clock.
//
// This class is my baby. I've spent quite a few hours striving to make this
// the ideal class for an analog clock hand. There are still a few things I
// could tweak, but I've tweaked enough lately, so I think I'll leave it just
// the way it is. At least for now.

import aLibrary.*;
import java.awt.*;

public class Hand {

  /*
   * CLASS VARIABLES that can be changed (via methods) by a user of this class
   */

  private int h, k;
  // The fixed end of the Hand is located at position (h, k) in the coordinate
  // system of its container.

  private int len;
  // The length of this Hand (in pixels).

  private boolean sweep;
  // Each Hand can optionally "sweep," moving continuously around the clock, as
  // opposed to "ticking" from one position to another.

  private Hand controlled;
  // Every Hand can optionally control another Hand. This is desirable if you
  // want a second hand to control the corresponding minute hand, for instance.

  private double controlRatio;
  // Only applicable if this Hand is controlling another one. This ratio
  // determines how many rotations this Hand must complete to effect one
  // rotation of the controlled Hand. As an example, suppose a second hand
  // controls a minute hand. controlRatio would be set to 60.

  private double initialAngle;
  // This is the initial angle, either given explicitly in a constructor or
  // set in setLogicalAngle(). More details in setAngle().

  private double logicalAngle;
  // This is the logical angle, either given explicitly in a constructor or set
  // in setLogicalAngle(). More details in setAngle().

  private double unitsInCircle;
  // This is the number of units contained in one complete rotation of the
  // Hand. For example, a minute hand might set unitsInCircle to 60, since
  // there are 60 units (minutes) in one rotation of a minute hand. This value
  // is used to determine the meaning of \u03b8.

  private Container container;
  // Every Hand must have a Container to be visible. Use the place(Container)
  // method to place this Hand into a Container.

  /*
   * CLASS VARIABLES that exist solely for this class
   */

  private ALine line;
  // The line which is the graphical representation of this Hand.

  /*
   * CONSTRUCTORS
   */

  public Hand(int x, int y, int l) {
  // Constructs a new Hand object centered at (x, y) of length l.
  //
  // Preconditions:
  //   0 < x < containerWidth
  //   0 < y < containerHeight
  //   0 < l
  //
  // Postconditions:
  //   (h, k) == (x, y)
  //   len == l
  //   sweep == true
  //   controlled == null
  //   controlRatio == 1
  //   initialAngle == 0
  //   logicalAngle == 0
  //   unitsInCircle == 60
  //   container == null

    this(x, y, l, true, null, 1d, 0d, 0d, 60d, null);

  }

  public Hand(int x, int y, int l, boolean s) {
  // Constructs a new Hand object centered at (x, y) of length l, with the
  // sweep property determined by s.
  //
  // Preconditions:
  //   0 < x < containerWidth
  //   0 < y < containerHeight
  //   0 < l
  //
  // Postconditions:
  //   (h, k) == (x, y)
  //   len == l
  //   sweep == s
  //   controlled == null
  //   controlRatio == 1
  //   initialAngle == 0
  //   logicalAngle == 0
  //   unitsInCircle == 60
  //   container == null

    this(x, y, l, s, null, 1d, 0d, 0d, 60d, null);

  }

  public Hand(int x, int y, int l, Hand ch, double cr) {
  // Constructs a new Hand object centered at (x, y) of length l, controlling
  // the Hand ch according to the ratio cr.
  //
  // Preconditions:
  //   0 < x < containerWidth
  //   0 < y < containerHeight
  //   0 < l
  //   ch != null
  //   cr != 0
  //
  // Postconditions:
  //   (h, k) == (x, y)
  //   len == l
  //   sweep == true
  //   controlled == ch
  //   controlRatio == cr
  //   initialAngle == 0
  //   logicalAngle == 0
  //   unitsInCircle == 60
  //   container == null

    this(x, y, l, true, ch, cr, 0d, 0d, 60d, null);

  }

  public Hand(int x, int y, int l, Hand ch, double cr, Container c) {
  // Constructs a new Hand object centered at (x, y) of length l, controlling
  // the Hand ch according to the ratio cr, and placed in c.
  //
  // Preconditions:
  //   0 < x < containerWidth
  //   0 < y < containerHeight
  //   0 < l
  //   ch != null
  //   cr != 0
  //   c != null
  //
  // Postconditions:
  //   (h, k) == (x, y)
  //   len == l
  //   sweep == true
  //   controlled == ch
  //   controlRatio == cr
  //   initialAngle == 0
  //   logicalAngle == 0
  //   unitsInCircle == 60
  //   container == c

    this(x, y, l, true, ch, cr, 0d, 0d, 60d, c);

  }

  public Hand(int x, int y, int l, double uic) {
  // Constructs a new Hand object centered at (x, y) of length l, with uic
  // units in one rotation.
  //
  // Preconditions:
  //   0 < x < containerWidth
  //   0 < y < containerHeight
  //   0 < l
  //   uic > 0
  //
  // Postconditions:
  //   (h, k) == (x, y)
  //   len == l
  //   sweep == true
  //   controlled == null
  //   controlRatio == 1
  //   initialAngle == 0
  //   logicalAngle == 0
  //   unitsInCircle == uic
  //   container == null

    this(x, y, l, true, null, 1d, 0d, 0d, uic, null);

  }

  public Hand(int x, int y, int l, Hand ch, double cr, double uic,
              Container c) {
  // Constructs a new Hand object centered at (x, y) of length l, controlling
  // the Hand ch according to the ratio cr, with uic units in one rotation,
  // placed in c.
  //
  // Preconditions:
  //   0 < x < containerWidth
  //   0 < y < containerHeight
  //   0 < l
  //   ch != null
  //   cr != 0
  //   uic > 0
  //   c != null
  //
  // Postconditions:
  //   (h, k) == (x, y)
  //   len == l
  //   sweep == true
  //   controlled == ch
  //   controlRatio == cr
  //   initialAngle == 0
  //   logicalAngle == 0
  //   unitsInCircle == uic
  //   container == c

    this(x, y, l, true, ch, cr, 0d, 0d, uic, c);

  }

  public Hand(int x, int y, int l, boolean s, Hand ch, double cr, double uic,
              Container c) {
  // Constructs a new Hand object centered at (x, y) of length l, with the
  // sweep property determined by s, controlling the Hand ch according to the
  // ratio cr, with uic units in one rotation, placed in c.
  //
  // Preconditions:
  //   0 < x < containerWidth
  //   0 < y < containerHeight
  //   0 < l
  //   ch != null
  //   cr != 0
  //   uic > 0
  //   c != null
  //
  // Postconditions:
  //   (h, k) == (x, y)
  //   len == l
  //   sweep == s
  //   controlled == ch
  //   controlRatio == cr
  //   initialAngle == 0
  //   logicalAngle == 0
  //   unitsInCircle == uic
  //   container == c

    this(x, y, l, s, ch, cr, 0d, 0d, uic, c);

  }

  public Hand(int x, int y, int l, boolean s, double uic) {
  // Constructs a new Hand object centered at (x, y) of length l, with the
  // sweep property determined by s, with uic units in one rotation.
  //
  // Preconditions:
  //   0 < x < containerWidth
  //   0 < y < containerHeight
  //   0 < l
  //   uic > 0
  //
  // Postconditions:
  //   (h, k) == (x, y)
  //   len == l
  //   sweep == s
  //   controlled == null
  //   controlRatio == 1
  //   initialAngle == 0
  //   logicalAngle == 0
  //   unitsInCircle == uic
  //   container == null

    this(x, y, l, s, null, 1d, 0d, 0d, uic, null);

  }

  public Hand(int x, int y, int l, boolean s, double uic, Container c) {
  // Constructs a new Hand object centered at (x, y) of length l, with the
  // sweep property determined by s, with uic units in one rotation, placed in
  // c.
  //
  // Preconditions:
  //   0 < x < containerWidth
  //   0 < y < containerHeight
  //   0 < l
  //   uic > 0
  //   c != null
  //
  // Postconditions:
  //   (h, k) == (x, y)
  //   len == l
  //   sweep == s
  //   controlled == null
  //   controlRatio == 1
  //   initialAngle == 0
  //   logicalAngle == 0
  //   unitsInCircle == uic
  //   container == c

    this(x, y, l, s, null, 1d, 0d, 0d, uic, c);

  }

  public Hand(int x, int y, int l, double uic, Container c) {
  // Constructs a new Hand object centered at (x, y) of length l, with uic
  // units in one rotation, placed in c.
  //
  // Preconditions:
  //   0 < x < containerWidth
  //   0 < y < containerHeight
  //   0 < l
  //   uic > 0
  //   c != null
  //
  // Postconditions:
  //   (h, k) == (x, y)
  //   len == l
  //   sweep == true
  //   controlled == null
  //   controlRatio == 1
  //   initialAngle == 0
  //   logicalAngle == 0
  //   unitsInCircle == uic
  //   container == c

    this(x, y, l, true, null, 1d, 0d, 0d, uic, c);

  }

  public Hand(int x, int y, int l, Container c) {
  // Constructs a new Hand object centered at (x, y) of length l, placed in c.
  //
  // Preconditions:
  //   0 < x < containerWidth
  //   0 < y < containerHeight
  //   0 < l
  //   c != null
  //
  // Postconditions:
  //   (h, k) == (x, y)
  //   len == l
  //   sweep == true
  //   controlled == null
  //   controlRatio == 1
  //   initialAngle == 0
  //   logicalAngle == 0
  //   unitsInCircle == 60
  //   container == c

    this(x, y, l, true, null, 1d, 0d, 0d, 60d, c);

  }

  public Hand(int x, int y,        // fixed point of Hand
              int l,               // length of Hand
              boolean s,           // whether or not this hand "sweeps"
              Hand ch, double cr,  // used to control another Hand
              double init\u03b8,   // initial angle [see setLogicalAngle()]
              double log\u03b8,    // logical angle [see setLogicalAngle()]
              double uic,          // unitsInCircle; e.g., 60 for second hand
              Container c) {       // where this Hand is contained
  // Constructs a new Hand object centered at (x, y) of length l, with the
  // sweep property determined by s, controlling the Hand ch according to the
  // ratio cr, with initial angle init\u03b8 and logical angle log\u03b8,
  // with uic units in one rotation, placed in c.
  //
  // Preconditions:
  //   0 < x < containerWidth
  //   0 < y < containerHeight
  //   l > 0
  //   ch != null
  //   cr > 0
  //   uic > 0
  //   c != null
  //
  // Postconditions:
  //   (h, k) == (x, y)
  //   len == l
  //   sweep == s
  //   controlled == ch
  //   controlRatio == cr
  //   initialAngle == init\u03b8
  //   logicalAngle == log\u03b8
  //   unitsInCircle == uic
  //   container == c

    setCenter(x, y);
    setLength(l);
    setSweep(s);
    control(ch, cr);
    setInitialAngle(init\u03b8);
    setLogicalAngle(log\u03b8);
    setUnitsInCircle(uic);
    repaint();  // we need to create the line before we place it
    place(c);
    // Call various methods to set our class variables.

  }

  /*
   * PUBLIC METHODS to set class variables
   */

  public void control(Hand ch, double cr) {
  // Specifies a Hand that this Hand will control. The control ratio (cr) is
  // the number of times that the controlling Hand must rotate to make the
  // controlled Hand complete one rotation.
  //
  // Preconditions:
  //   ch != null
  //   cr > 0
  //
  // Postconditions:
  //   controlled = ch
  //   controlRatio = cr

    if ((ch != null) && (cr != 0)) {
      controlled = ch;
      controlRatio = cr;
    }

  }

  public void setCenter(int x, int y) {
  // Sets the center of this Hand.
  //
  // Preconditions:
  //   0 < x < containerWidth
  //   0 < y < containerWidth
  //
  // Postconditions:
  //   h == x
  //   k == y

    h = x;
    k = y;

  }

  public void setLength(int l) {
  // Sets the length of this Hand.
  //
  // Precondition:
  //   l > 0
  //
  // Postcondition:
  //   len == l

    len = l;

  }

  public void setLogicalAngle(double angle) {
  // Sets the logical angle of this Hand. More details below.
  //
  // Preconditions:
  //   None.
  //
  // Postcondition:
  //   logicalAngle == angle
  //
  // Every Hand has three different angles associated with it--the initial
  // angle, the logical angle, and the physical angle. The physical angle is
  // the angle exemplified by the line shown on the screen. This angle is
  // arrived at by adding together the other two angles; that is, the initial
  // angle plus the logical angle equals the physical angle. By using three
  // angles to describe the position of a Hand, we can vastly increase its
  // capabilities.
  //   Consider the following scenario:
  //   You have a clock laying on its right side; that is, the clock face is
  // rotated 90 degrees clockwise. The 12 on this clock will be at the same
  // position as a 3 on an upright clock. Thus, for the hands on this clock,
  // the initial angle (their "12 o'clock" angle) is 90 degrees clockwise from
  // the top of the clock face. However, the hands continue to move as usual,
  // and so the logical angle increases from zero. To draw this on the screen,
  // we need to compute the physical angle, which can be found by adding the
  // logical angle to the initial angle. The physical angle is computed on the
  // fly in repaint().
  //   If you don't understand my reasoning, don't worry. Just set the initial
  // angle to zero and work with the logical angle and you'll get something
  // that works like a normal clock.

    logicalAngle = angle;

  }

  public void setPhysicalAngle(double angle) {
  // Sets the physical angle of this Hand. More details in setLogicalAngle().
  //
  // Preconditions:
  //   None.
  //
  // Postcondition:
  //   logicalAngle == angle

    logicalAngle = angle - initialAngle;

  }

  public void setInitialAngle(double angle) {
  // Sets the initial angle of this Hand. More details in setLogicalAngle().
  //
  // Preconditions:
  //   None.
  //
  // Postcondition:
  //   initialAngle == angle

    initialAngle = angle;

  }

  public void setSweep(boolean s) {
  // Sets the sweep value of this Hand. If true, the Hand sweeps around the
  // clock. If false, the Hand ticks from one position to another.
  //
  // Preconditions:
  //   None.
  //
  // Postcondition:
  //   sweep == s

    sweep = s;

  }

  public void setUnitsInCircle(double u) {
  // Sets the number of units in one rotation. For example, there are 12 units
  // in one rotation of an hour hand, but there are 60 units in one rotation of
  // a minute hand. This number can be anything, but it makes setAngle() easier
  // if this number makes sense (like 12 or 60).
  //
  // Precondition:
  //   u > 0
  //
  // Postcondition:
  //   unitsInCircle == u
  //
  // However, unitsInCircle must always be valid. Thus, if 0 is passed in,
  // unitsInCircle will be set to its default value of 60. If u is negative,
  // unitsInCircle will use the absolute value of u.

    if (u == 0) {
      // Zero is an absolutely unacceptable value for unitsInCircle, as
      // setAngle() divides by unitsInCircle. We will simply set unitsInCircle
      // to its default value of 60.
      unitsInCircle = 60d;
    } else {
      // If it's not zero, we'll take the absolute value of u (to protect
      // against negative numbers) and assign that to unitsInCircle.
      unitsInCircle = Math.abs(u);
    }

  }

  public void place(Container c) {
  // Places this Hand in a container.
  //
  // Precondition:
  //   c != null
  //
  // Postconditions:
  //   This Hand is placed in c.
  //   container == c

    if (c != null) {
      container = c;
      container.add(line, 0);
    }

  }

  /*
   * PUBLIC METHODS to perform actions
   */

  public void moveTo(double log\u03b8) {
  // Moves this Hand to the specified logical angle, and moves the controlled
  // Hand an appropriate amount (if a controlled Hand exists).
  //
  // Preconditions:
  //   none.
  //
  // Postconditions:
  //   This Hand is moved to the specified logical angle.
  //   The controlled Hand (if it exists) is moved an appropriate amount, and
  //     this hand moves its controlled Hand, and so on until the end of the
  //     control chain.

    setLogicalAngle(log\u03b8 % unitsInCircle);
    // Set the logical angle

    if (controlled != null)
      controlled.moveTo(log\u03b8 / controlRatio);
    // Move the controlled Hand if it exists

  }

  public void repaint() {
  // Recalculates the moving endpoint of this Hand, based on current values for
  // the initial and logical angles, and redraws the Hand.
  //
  // Preconditions:
  //   none
  //
  // Postcondition:
  //   A new line is drawn which corresponds to the physical angle of this
  //     Hand. It is then placed in its container (if this Hand has already
  //     been placed).

    double physicalAngle;
    // This is the physical angle, which is computed by adding the logical
    // angle to the initial angle. physicalAngle is in units. More details in
    // setLogicalAngle().

    double physicalAngleRadians;
    // This is the physical angle expressed in radians, so that we can use the
    // sine and cosine functions to compute the moving endpoint of the Hand.

    int endX, endY;
    // These are the coordinates for the moving endpoint of this Hand.

    physicalAngle = initialAngle + logicalAngle;
    // See setLogicalAngle() for an explanation.

    if (!sweep)
      physicalAngle = (int)physicalAngle;
    // To create the "ticking" effect, we'll chop off the fractional part of
    // physicalAngle if this Hand is not set to sweep.

    physicalAngleRadians = (Math.PI / 2) -
                           (2 * Math.PI * (physicalAngle / unitsInCircle));
    // Compute the physical angle in radians

    endX = (int)(h + (len * Math.cos(physicalAngleRadians)) + 0.5);
    endY = (int)(k - (len * Math.sin(physicalAngleRadians)) + 0.5);
    // Calculate the moving endpoint of this line

    if ((line != null) && (line.parentContainer() != null))
      line.remove();
    // Remove the line if it's already placed somewhere

    line = new ALine(h, k, endX, endY);
    // Draw a new line

    if (container != null)
      container.add(line, 0);
    // Place this line in the container

  }

  /*
   * PUBLIC METHODS to get class variables
   */

  public Container parentContainer() {
  // Returns the container which holds this Hand.
  //
  // Preconditions:
  //   None.
  //
  // Postcondition:
  //   The container which holds this Hand is returned.

    return container;

  }

}