// A class that represents line drawings.

// History:
//   Feb. 2002 -- Created by Doug Baldwin.




package geneseo.cs.sc;

import java.awt.*;
import javax.swing.JFrame;
import java.util.Vector;




/**
 * Represents a drawing made up of one or more sets of connected line segments.
 * Each of these drawings is associated with a window that makes it visible to
 * users. These windows define a coordinate system relative to which to define
 * lines. The center of the window corresponds to coordinate (0,0), the coordinate
 * system extends symmetrically out from the window's center. Coordinates measure in
 * units of pixels. To place lines in a drawing, think of the drawing containing a
 * (invisible) pen, which you can move for a specified distance in a specified direction.
 * Messages to line drawings allow you to say where this pen should start from (if you
 * don't say, it starts wherever it last ended, thus connecting line segments), what
 * direction it should move in, how far it should move, et cetera.
 */

public class LineDrawing extends Canvas {




    // A line drawing is a Canvas (on which the lines can be drawn). Internally
    // a line drawing also keeps track of...
    //   o A frame in which to display the canvas
    //   o A vector of the sets of line segments that comprise the picture. Each element
    //     of this vector is itself a vector of vertices that define the ends of the segments
    //     in that set.
    //   o The sine and cosine of the angle that the pen draws at. This angle is measure
    //     counterclockwise from due right.
    //   o The color in which the pen is currently drawing.
    
    private JFrame window;
    
    private Vector lines;
    
    private double sine;
    private double cosine;
    
    private Color penColor;
    
    
    
    
    // A vertex of a line segment is a record that stores the vertex's coordinates, and
    // the color of the segment ending at that vertex.
    
    private class Vertex {
    
    public double x;
    public double y;
    public Color color; 
        
    public Vertex( double x, double y, Color c ) {
        this.x = x;
        this.y = y;
        this.color = c;
    }
    }




    /**
     * Initialize a line drawing.
     */
     
    // This requires creating the frame that displays the drawing, placing the line
    // drawing in it, and making the whole group visible. Then it initializes the
    // drawing proper by erasing it.
    
    public LineDrawing() {
    
    final int preferedWidth = 400;              // Width and height of a line
    final int preferedHeight = 400;             // drawing
        
    window = new JFrame( "Line Drawing" );
    window.setSize( preferedWidth, preferedHeight );
    window.getContentPane().add( this );
    window.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
    window.show();
        
    this.clear();

    }
    
    
    
    
    /**
     * Erase a line drawing and prepare any new segments to start at (0,0), with the pen
     *   moving straight right and drawing in black.
     */
     
    // This works by replacing any existing vector of lines with a new one, and initializing
    // it with a single line that contains only one point, namely (0,0).
    
    public void clear() {
        
        
    // Set the pen's direction and color:
    
    sine = 0.0;
    cosine = 1.0;
    
    penColor = Color.black;
        
    
    // Set up the set of lines:
        
    lines = new Vector();
    Vector firstLine = new Vector();
    firstLine.add( new Vertex( 0.0, 0.0, penColor ) );
    lines.add( firstLine );
    }
    
    
    
    
    /**
     * Position the pen to start a new set of line segments.
     * @param x The x coordinate at which the first segment of the set will start.
     * @param y The y coordinate at which the first segment of the set will start.
     */
    
    // This saves the point specified by "x" and "y" as either the start of a new set
    // of line segments, if the current set has more than one point in it, or as a
    // replacement for the starting point in the current set if the current set contains
    // only one point (a set of segments with only one point doesn't have enough points
    // to define even one segment, that point must therefore be a starting point that
    // has never really been used).
    
    public void setPosition( double x, double y ) {
    
    Vertex thisPoint = new Vertex( x, y, penColor );
    
    Vector curSegment = (Vector)lines.elementAt( lines.size()-1 );
        
    if ( curSegment.size() <= 1 ) {
        curSegment.setElementAt( thisPoint, 0 );
    }
    else {
        Vector newLine = new Vector();
        newLine.add( thisPoint );
        lines.add( newLine );
    }
    }
    
    
    
    
    /**
     * Set the direction the pen will move next time a line drawing receives a "movePen"
     * message.
     * @param angle The counterclockwise angle from due right that the pen should move,
     *   measured in degrees.
     */
    
    // This sets the angle by saving its sine and cosine in the corresponding member
    // variables. The angle has to be converted from degrees to radians before taking
    // its sine and cosine.
    
    public void setAngle( double angle ) {
    
    double radians = angle * Math.PI / 180.0;
        
    sine = Math.sin( radians );
    cosine = Math.cos( radians );
    }
    
    
    
    
    /**
     * Set the color in which the pen draws future line segments.
     * @param newColor The color to use for future line segments.
     */
    
    public void setColor( Color newColor ) {
    penColor = newColor;
    }
    
    
    
    
    /**
     * Find out the X coordinate of the pen's position.
     * @return The X coordinate of the pen position.
     */
    
    // This works by returning the x coordinate of the last vertex in the
    // last line segment in the current drawing.
     
    public double getPositionX() {
    Vector lastSegment = (Vector)lines.elementAt( lines.size() - 1 );
    Vertex lastPoint = (Vertex)lastSegment.elementAt( lastSegment.size() - 1 );
    return lastPoint.x;
    }
    
    
    
    
    /**
     * Find out the Y coordinate of the pen's position.
     * @return The Y coordinate of the pen position.
     */
    
    // This works by returning the y coordinate of the last vertex in the
    // last line segment in the current drawing.
     
    public double getPositionY() {
    Vector lastSegment = (Vector)lines.elementAt( lines.size() - 1 );
    Vertex lastPoint = (Vertex)lastSegment.elementAt( lastSegment.size() - 1 );
    return lastPoint.y;
    }
    
    
    
    
    /**
     * Find out what direction the pen is moving.
     * @return The pen's current heading, in degrees counterclockwise from
     *   straight right.
     */
     
    // The computation in this method starts with the member variable that holds
    // the cosine of the current angle. Taking the arccosine of this value gives
    // me an angle between 0 and Pi radians, which is either the true angle or its
    // negative (since theta and -theta have the same cosine). To tell which, I
    // look at the saved sine of the angle -- if it is negative, then the angle
    // is between Pi and 2*Pi radians, and the arccosine calculation basically
    // gave me its negative. Once I have the right angle in radians, I convert
    // it to degrees to return.
    
    public double getAngle() {
    
    double angle = Math.acos( cosine );
        
    if ( sine < 0 ) {
        angle = -angle;
    }
        
    return angle * 180.0 / Math.PI;
    }
    
    
    
    
    /**
     * Find out what color the pen is currently drawing in.
     * @return The current color.
     */
    
    public Color getColor() {
    return penColor;
    }
    
    
    
    
    /**
     * Move the pen, adding a line segment to the current set of segments. The new segment
     * starts at the pen's current position and has the pen's current color.
     * @param distance The distance the pen should move.
     */
    
    // I calculate a vector that gives the pen's displacement from the previous point as
    // a result of this move. The displacement is simply the distance moved times the
    // cosine of the current angle in the horizontal direction, and the distance moved
    // times the sine of the current angle in the vertical direction. Once I have this
    // displacement, I calculate the endpoint of the new segment by adding the displacement
    // to the endpoint of the previous segment (i.e., the last point recorded in the
    // current set of segments). I repaint the drawing after adding the new segment so
    // that users can see their drawings develop without having to remember to repaint for
    // themselves.
    
    public void movePen( double distance ) {
    
    double hDisplacement = distance * cosine;
    double vDisplacement = distance * sine;
        
    Vector currentSegments = (Vector)lines.elementAt( lines.size()-1 );
        
    Vertex oldEnd = (Vertex)currentSegments.elementAt( currentSegments.size()-1 );
        
    Vertex newEnd = new Vertex( oldEnd.x + hDisplacement,
                    oldEnd.y + vDisplacement,
                    penColor );
        
    currentSegments.add( newEnd );
    
    this.repaint( 1 );
    }
    
    
    
    
    /**
     * Draw the contents of a line drawing. Normally called by the Java runtime system
     * when the line drawing needs to be (re)displayed, not called directly by client
     * code.
     * @param g The graphics context for drawing the lines.
     */
    
    // This steps through each element of the "lines" vector. Each of these elements is
    // itself a vector of points, so this method draws line segments connecting each pair
    // of consecutive points in that vector. If a vector only contains one point (i.e., too
    // few to have even one pair) this method does nothing with that vector. Also note that
    // this method has to transform points as recorded in "lines", where the coordinate
    // system has (0,0) at the center of the window and Y coordinates increasing upward, to
    // the coordinate system used by awt, which has (0,0) at the upper left corner of the
    // window and Y coordinates increasing down. Thus this adds the horizontal coordinate of
    // the window's center to the horizontal coordinates of points from "lines" to get
    // drawable horizontal coordinates, and subtracts vertical coordinates from "lines"
    // from half the window's height to get drawable vertical coordinates. This method can
    // get called before a line drawing is fully initialized, in which case "lines" will
    // be null and there is nothing for this method to do.
    
    public void paint( Graphics g ) {
    
    if ( lines != null ) {
        
        double centerV = this.getHeight() / 2.0;
        double centerH = this.getWidth() / 2.0;
            
        for ( int line = 0; line < lines.size(); line++ ) {
            
        Vector currentLine = (Vector)lines.elementAt( line );
            
        for ( int pt = 1; pt < currentLine.size(); pt++ ) {
            
            Vertex prevPoint = (Vertex)currentLine.elementAt( pt-1 );
            Vertex curPoint = (Vertex)currentLine.elementAt( pt );
                    
            g.setColor( curPoint.color );
                    
            g.drawLine( (int)Math.round( prevPoint.x + centerH ),
                (int)Math.round( centerV - prevPoint.y ),
                (int)Math.round( curPoint.x + centerH ),
                (int)Math.round( centerV - curPoint.y )  );
        }
        }
    }
    }
    
}