import java.awt.*;
import javax.swing.*;
import java.util.LinkedList;

    /** This class is designed to provide for the display of a Graph.
     *  The (inclusive) min and max values of the two ranges are settable.
     *  Points are drawn on the graph via the drawPoint method.
     *  Lines are drawn on the graph via the drawLine method.
     * 
     *  This class is not intended for production usage but to provide a
     *  vehicle for teaching about if-statements.
     *  The class deliberately throws an exception on attempts to
     *  plot outside its min/max boundaries.
     *  It also rejects lines for which x1 > x2.
     * 
     *  A controller class should provide checks to stop exception
     *  conditions arising.
     *  All non-public methods are private to limit access from
     *  simple programs.
     */
class Graph extends JComponent {
    // The default values for a chart.
    /** The default minimum for X.
     */
    public static final double DEFAULTMINX = 1;
    /** The default maximum for X.
     */
    public static final double DEFAULTMAXX = 100;
    /** The default minimum for Y.
     */
    public static final double DEFAULTMINY = 1;
    /** The default maximum for Y.
     */
    public static final double DEFAULTMAXY = 100;

        /** Construct the graph with default minima and maxima.
         */
    public Graph() throws RuntimeException {
        this(DEFAULTMINX,DEFAULTMAXX,DEFAULTMINY,DEFAULTMAXY);
    }

        /** Construct the graph with the given minima and maxima.
         *  @throws java.lang.RuntimeException for mis-ordered
         *  ranges.
         */
    public Graph(double minX,double maxX,double minY,double maxY)
                                                throws RuntimeException {
        // Make sure the min/max pairs are well ordered.
        checkOrder(minX,maxX);
        checkOrder(minY,maxY);

        this.minX = minX;
        this.maxX = maxX;
        this.minY = minY;
        this.maxY = maxY;
        final int width = 300, height = 250;
        setPreferredSize(new Dimension(width,height));

        ComponentFrame frame = new ComponentFrame("Graph");
        frame.addComponent(this);
        frame.pack();
    }

    public void paintComponent(Graphics g){
        drawGraph();
        Image i = getImage();
        // Check for resizing since the last paint.
        if(i != null){
            g.drawImage(getImage(),0,0,this);
        }
    }

        /** Reject anything outside the graph area.
         *  @throws java.lang.RuntimeException for an illegal point.
         */
    public void drawPoint(double x, double y) throws RuntimeException {
        GraphPoint p = new GraphPoint(x,y);
        checkPoint(p);
        getPoints().add(p);
        repaint();
    }

        /** Reject anything outside the graph area.
         *  Also, (purely for teaching purposes) only accept (x1 <= x2).
         *  @throws java.lang.RuntimeException for an illegal point or
         *  endpoints out of order.
         */
    public void drawLine(double x1, double y1, double x2, double y2)
                                                throws RuntimeException {
        GraphPoint p1 = new GraphPoint(x1,y1);
        GraphPoint p2 = new GraphPoint(x2,y2);
        checkPoint(p1);
        checkPoint(p2);
        checkOrder(p1.x,p2.x);
        LinkedList lines = getLines();
        lines.add(p1);
        lines.add(p2);
        repaint();
    }

        /** Return the minimum X value.
         */
    public double getMinX(){
        return minX;
    }

        /** Return the minimum Y value.
         */
    public double getMinY(){
        return minY;
    }

        /** Return the maximum X value.
         */
    public double getMaxX(){
        return maxX;
    }

        /** Return the maximum Y value.
         */
    public double getMaxY(){
        return maxY;
    }

        /** An inner class to support points.
         */
    class GraphPoint {
        public GraphPoint(double x, double y){
            this.x = x;
            this.y = y;
        }

        public String toString(){
            return "("+x+","+y+")";
        }

        final public double x, y;
    }

        /** Check that the point is inside the grid area.
         *  @throws java.lang.RuntimeException for an point outside.
         */
    private void checkPoint(GraphPoint p) throws RuntimeException {
        if((p.x < getMinX()) || (p.x > getMaxX()) ||
           (p.y < getMinY()) || (p.y > getMaxY())){
            throw new RuntimeException(p+" is out of bounds.");
        }
    }

        /** Check that the x1 <= x2.
         *  @throws java.lang.RuntimeException if x1 > x2.
         */
    private void checkOrder(double x1, double x2) throws RuntimeException {
        if(x1 > x2){
            throw new RuntimeException("Out of order points: "+
                                       x1+", "+x2);
        }
    }



        /** Draw all the bars on the graphics context of an offscreen
         *  image. Make sure the current image is the right size.
         */
    private void drawGraph(){
        Image i = getImage();
        // Check for resizing.
        final Dimension size = getSize();
        if((i == null) || (i.getWidth(this) != size.width) ||
                        (i.getHeight(this) != size.height)){
            i = createImage(size.width,size.height);
            setImage(i);
        }
        // Where to draw to.
        final Graphics g = i.getGraphics();
        // Clear any old drawing.
        g.setColor(Color.white);
        g.fillRect(0,0,size.width,size.height);

        final int availableWidth = size.width, availableHeight = size.height;
        final double xRange = getMaxX()-getMinX()+1,
                     yRange = getMaxY()-getMinY()+1;

        // Draw the points.
        final LinkedList points = getPoints();
        int numPoints = points.size();
        for(int j = 0; j < numPoints; j++){
            final GraphPoint p = (GraphPoint) points.get(j);
            final int x = mapX(p.x,availableWidth,xRange),
                      y = mapY(p.y,availableHeight,yRange);
            g.setColor(Color.black);
            g.drawLine(x,y,x+1,y);
        }

        // Draw the lines.
        final LinkedList lines = getLines();
        numPoints = lines.size();
        for(int j = 0; j < numPoints; j += 2){
            final GraphPoint p1 = (GraphPoint) lines.get(j);
            final GraphPoint p2 = (GraphPoint) lines.get(j+1);
            final int x1 = mapX(p1.x,availableWidth,xRange),
                      y1 = mapY(p1.y,availableHeight,yRange);
            final int x2 = mapX(p2.x,availableWidth,xRange),
                      y2 = mapY(p2.y,availableHeight,yRange);
            g.setColor(Color.black);
            g.drawLine(x1,y1,x2,y2);
        }
    }

        /** Map an absolute x value to an integer pixel position.
         */
    private int mapX(double x,int width,double xRange){
        return (int) (((x-minX)/xRange)*width);
    }

        /** Map an absolute y value to an integer pixel position.
         * Remember that the (minX,minY) is bottom left.
         */
    private int mapY(double y,int height,double yRange){
        // Adjust by 1 for zero base.
        return (int) ((height-1)-((y-minY)/yRange)*height);
    }

    private LinkedList getPoints(){
        return points;
    }

    private LinkedList getLines(){
        return lines;
    }

    private Image getImage(){
        return image;
    }

    private void setImage(Image i){
        image = i;
    }

    // Limits of the values to be included.
    private final double minX, maxX, minY, maxY;
    // The points and lines to be drawn.
    private LinkedList points = new LinkedList(), lines = new LinkedList();
    // The offscreen image for double-buffering.
    private Image image;
}
