import java.awt.Color;
import java.awt.Font;
import java.awt.Point;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import edu.southern.computing.oopj.CartesianViewport;
import edu.southern.computing.oopj.GraphicalObject;
import edu.southern.computing.oopj.VPoint;
import edu.southern.computing.oopj.ViewFrame;

class GeoPoint extends GraphicalObject {
    private static final long serialVersionUID = 1L;

    /** The default size of a visual point. */
    protected static final int WIDTH = 8;

    /** The location of the point's center. */
    protected Point center;
    /** The point's displayed color. */
    protected Color color;

    public GeoPoint(Point center, Color color) {
        super(WIDTH, WIDTH);
        setLocation(center.x - WIDTH / 2, center.y - WIDTH / 2);
        this.center = new Point(center.x, center.y);
        this.color = color;
        this.setMovable(true);
    }

    @Override
    public void draw() {
        // super.draw();
        // drawRectangle(0, 0, WIDTH - 1, WIDTH - 1, color);
        drawRectangle(0, 0, getWidth() - 1, getHeight() - 1, color);
    }

    /** Relocate the center of a point to (<i>x</i>,<i>y</i>). */
    public void putCenter(int x, int y) {
        setLocation(x - WIDTH / 2, y - WIDTH / 2);
        center.x = x;
        center.y = y;
    }

    /** Dragging a point repositions its center. */
    @Override
    public void mouseDragged() {
        // System.out.println("x = " + getX() + ", y = " + getY());
        super.mouseDragged();
        int width = getWidth();
        center.x = getX() + width / 2;
        center.y = getY() + width / 2;
        getParent().repaint();
    }

    /** The point is enlarged when the mouse cursor moves over it. */
    @Override
    public void mouseEntered() {
        int newWidth = 2*WIDTH;
        setSize(newWidth, newWidth);
        setLocation(center.x - WIDTH, center.y - WIDTH);
        super.mouseEntered();
    }

    /**
     * The point returns to its original visual size when the mouse cursor is no
     * longer over it.
     */
    @Override
    public void mouseExited() {
        setSize(WIDTH, WIDTH);
        setLocation(center.x - WIDTH / 2, center.y - WIDTH / 2);
        super.mouseExited();
    }

    /** Returns the location of the point's center. */
    public Point getCenter() {
        return center;
    }

}

class GeometryViewport extends CartesianViewport {
    private static final long serialVersionUID = -7653863764671395066L;

    // protected ArrayList<GeoPoint> points;
    protected GeoPoint p1;
    protected GeoPoint p2;
    protected GeoPoint p3;
    protected GeoPoint p4;
    protected GeoPoint intersectionPoint;

    GeometryViewport(int x, int y, int width, int height, double minX,
            double maxX, double minY, double maxY) {
        super(x, y, width, height, minX, maxX, minY, maxY);
        // points = new ArrayList<>();
        p1 = p2 = p3 = p4 = intersectionPoint = null; // No points initially
    }

    void drawExtendedLine(GeoPoint p1, GeoPoint p2, Color color) {
        // Draw line passing through p1 and p2
        setColor(color);

        // Translate GeoPoint device coordinates to view coordinates
        double p1x = transform.toViewX(p1.getCenter().x), p1y = transform
                .toViewY(p1.getCenter().y), p2x = transform.toViewX(p2
                .getCenter().x), p2y = transform.toViewY(p2.getCenter().y);
        // Do the math
        GLine line = new GLine(p1x, p1y, p2x, p2y);
        double m = line.getSlope(), b = line.getIntercept();
        GPoint i1, i2;
        if (m == Double.MAX_VALUE) { // Vertical line
            i1 = new GPoint(p1x, -minY);
            i2 = new GPoint(p1x, minY);
        } else if (m >= -1.0 && m <= 1.0) { // Shallow slope
            GLine ln1 = new GLine(m, b), 
                  ln2 = new GLine(Double.MAX_VALUE, minX);
            i1 = ln1.intersection(ln2);
            ln2= new GLine(Double.MAX_VALUE, maxX);
            i2 = ln1.intersection(ln2);
        } else { // Steep slope
            GLine ln1 = new GLine(m, b), 
                  ln2 = new GLine(0, minY);
            i1 = ln1.intersection(ln2);
            ln2 = new GLine(0, maxY);
            i2 = ln1.intersection(ln2);
        }
        // Translate back to device coordinates and draw
        drawLine(i1.x, i1.y, i2.x, i2.y, 2);
    }

    protected void printPoint(GeoPoint pt) {
        VPoint p = transform.toView(pt.getCenter());
        GPoint gp = new GPoint(p.x, p.y);
        drawString(gp.toString(), pt.center.x + 10, pt.center.y + 10);
        //drawRectangle(20.0, 10.0, 20.0, 10.0);
        //drawRectangle(20, 10, 20, 10);
    }

    protected void printPoint(Point pt) {
        VPoint p = transform.toView(pt);
        GPoint gp = new GPoint(p.x, p.y); 
        fillRectangle(pt.x - 4, pt.y - 4, 8, 8);
        drawString(gp.toString(), pt.x + 10, pt.y + 10);
    }

    @Override
    public void draw() {
        // super.draw();
        drawAxes(10, 10);
        // for (Point p : points) {
        // drawPoint(p);
        // }
        if (p1 != null) {
            setColor(DARK_GREEN);
            drawingGraphics.setFont(new Font(Font.MONOSPACED, Font.BOLD, 18));
            printPoint(p1);
            if (p2 != null) {
                printPoint(p2);
                // Draw line passing through p1 and p2
                drawExtendedLine(p1, p2, DARK_GREEN);
                VPoint vp1 = transform.toView(p1.getCenter());
                VPoint vp2 = transform.toView(p2.getCenter());
                GLine line1 = new GLine(vp1.x, vp1.y, vp2.x, vp2.y);
                String eq = line1.toString();
                drawString(eq, 10, getHeight() - 40);
                if (p3 != null) {
                    setColor(BLUE);
                    printPoint(p3);
                    if (p4 != null) {
                        printPoint(p4);
                        // Draw line passing through p3 and p4
                        drawExtendedLine(p3, p4, BLUE);
                        VPoint vp3 = transform.toView(p3.getCenter()),
                               vp4 = transform.toView(p4.getCenter());
                        GLine line2 = new GLine(vp3.x, vp3.y, vp4.x, vp4.y);
                        eq = line2.toString();
                        drawString(eq, 10, getHeight() - 20);
                        // Compute intersection of the two lines
                        GPoint igp = line1.intersection(line2);
                        if (igp.x != Double.MAX_VALUE) {
                            VPoint intersection = new VPoint(igp.x, igp.y);
                            // Display intersection, if it exists
                            if (intersection != VPoint.NO_POINT) {
                                Point inter = transform.toDevice(intersection);
                                setColor(RED);
                                printPoint(inter);
                            }
                        }
                    }
                }
            }
        }
    }

    // @Override
    // public void mouseReleased(MouseEvent e) {
    // System.out.println("x=" + e.getX() +", y=" + e.getY());
    // super.mouseReleased(e);
    // }

    @Override
    public void mouseReleased(MouseEvent e) {
        super.mouseReleased(e);
        Point cartesianPoint = new Point(e.getX(), e.getY());
        //System.out.println("(" + cartesianPoint.x + "," + cartesianPoint.y
        //        + ")");
        // points.add(new GeoPoint(cartesianPoint, RED));
        GeoPoint newPoint = null;
        if (p1 == null) {
            newPoint = p1 = new GeoPoint(cartesianPoint, DARK_GREEN);
        } else if (p2 == null) {
            newPoint = p2 = new GeoPoint(cartesianPoint, DARK_GREEN);
        } else if (p3 == null) {
            newPoint = p3 = new GeoPoint(cartesianPoint, BLUE);
        } else if (p4 == null) {
            newPoint = p4 = new GeoPoint(cartesianPoint, BLUE);
        }
        if (newPoint != null) {
            add(newPoint);
        }
        repaint();
    }
    

    @Override
    public void keyReleased(KeyEvent e) {
        // TODO Auto-generated method stub
        super.keyReleased(e);
        if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
            remove(p1);
            remove(p2);
            remove(p3);
            remove(p4);
            p1 = p2 = p3 = p4 = intersectionPoint = null; // No points initially    
        }
        repaint();
        
    }
    
    
    
}

@SuppressWarnings("serial")
public class GeometryWindow extends ViewFrame implements
        ComponentListener {

    /**
     * Make a new TicTacToeWindow. Establish its dimensions and attach
     * listeners.
     */
    public GeometryWindow() {
        super("Visual Geometry [3]", new GeometryViewport(200, 100, 500,
                500, -100, 100, -100, 100));
        addComponentListener(this);

        setBackground(Color.WHITE);
    }

    /** Unused */
    public void componentHidden(ComponentEvent arg0) {
    }

    /** Unused */
    public void componentMoved(ComponentEvent arg0) {
    }

    private boolean makingSquare = false;

    /**
     * Force the window to be square
     */
    public void componentResized(ComponentEvent arg) {
        if (!makingSquare) {
            // Insets insets = getInsets();
            int horizInsets = insets.left + insets.right, vertInsets = insets.top
                    + insets.bottom;
            int w = super.getWidth() - horizInsets, h = super.getHeight()
                    - vertInsets;
            // System.out.println("w=" + w + ", h=" + h);
            if (w != h) {
                int shortSide = (w < h) ? w : h;
                shortSide = (shortSide < 400) ? 400 : shortSide;
                setSize(shortSide + horizInsets, shortSide + vertInsets);
                makingSquare = true; // Prevents calling setSize again when
                // forcing the window to be square
            }
            ((CartesianViewport) getViewport()).adjustView();
        } else {
            makingSquare = false;
        }
    }
    
    /** Unused */
    public void componentShown(ComponentEvent arg0) {
    }

    public static void main(String[] args) {
        new GeometryWindow();
    }
}
