import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

import java.util.ArrayList;

/**
 * This class defines a panel where the user can sketch
 * curves, rectangles, and ovals.  The shapes can be of 
 * different colors.  Each shape has a "symmetry" option; 
 * if it uses symmetry, then the horizontal and vertical 
 * reflections of the original shape are also drawn.  
 * A menu bar is available with menus for selecting the 
 * current drawing color, for selecting the current tool,
 * and a few other commands.  (The tool determines which
 * shape the user is drawing.)  This class also has a main()
 * routine that just creates a window showing a panel
 * of type SeveralThings along with its menu bar.
 */
public class SeveralThings extends JPanel {

   public static void main(String[] args) {
       JFrame window = new JFrame("SeveralThings");
       SeveralThings c = new SeveralThings();
       window.setContentPane(c);
       window.setJMenuBar(c.createMenuBar());
       window.pack();
       window.setLocation(200,100);
       window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
       window.setVisible(true);
   }
   
   private static final int CURVE = 0;       // Codes for the possible kinds of tool/shape.
   private static final int RECTANGLE = 1;
   private static final int OVAL = 2;
   
   
   private ArrayList<Drawable> things;  // List of Drawable things in the picture.
   private Color currentColor;  // The color that the user is currently using for drawing.

   private int currentTool;  // The code for the type of tool that the user is currently using.
                             //  (The value will be CURVE, RECTANGLE, or OVAL.)
                             
   private JCheckBoxMenuItem useSymmetryCheckbox;  // Determines whether user is drawing with symmetry.


   public SeveralThings() {
      things = new ArrayList<Drawable>();  // Create the list, initially empty.
      currentColor = Color.BLACK;          // Initial drawing color.
      currentTool = CURVE;                 // Initial drawing tool.
      setPreferredSize( new Dimension(500,500) );
      setBackground(Color.WHITE);
      setBorder( BorderFactory.createLineBorder(Color.GRAY,2) );
      MouseHandler mh = new MouseHandler();  // Handler for drag operations on this panel.
      addMouseListener(mh);
      addMouseMotionListener(mh);
   }
   
   
   /**
    * Create a menu bar of commands that can be applied to this panel.
    */
   public JMenuBar createMenuBar() {
      MenuHandler ah = new MenuHandler();  // Handler for ActionEvents from commands in the menus.

      JMenu controlMenu = new JMenu("Control");   // Create the control and add items to it.
      JMenuItem clear = new JMenuItem("Clear");
      clear.addActionListener(ah);
      controlMenu.add(clear);
      JMenuItem undo = new JMenuItem("Undo");
      undo.addActionListener(ah);
      controlMenu.add(undo);
      useSymmetryCheckbox = new JCheckBoxMenuItem("Use Symmetry");
      useSymmetryCheckbox.setSelected(true);
      controlMenu.add(useSymmetryCheckbox);
      controlMenu.addSeparator();
      JMenuItem quit = new JMenuItem("Quit");
      quit.addActionListener(ah);
      controlMenu.add(quit);
      
      JMenu colorMenu = new JMenu("Color");  // Create the Color menu and add items to it.
      String[] colors = new String[] { "Black", "Red", "Green", "Blue", "Cyan", "Magenta", "Yellow" };
      for (int i = 0; i < colors.length; i++) {
         JMenuItem c = new JMenuItem(colors[i]);
         c.addActionListener(ah);
         colorMenu.add(c);
      }
      
      JMenu toolMenu = new JMenu("Tool");   // Create the Tool menu and add items to it.
      JMenuItem curveTool = new JMenuItem("Curve");
      curveTool.addActionListener(ah);
      toolMenu.add(curveTool);
      JMenuItem rectTool = new JMenuItem("Rectangle");
      rectTool.addActionListener(ah);
      toolMenu.add(rectTool);
      JMenuItem ovalTool = new JMenuItem("Oval");
      ovalTool.addActionListener(ah);
      toolMenu.add(ovalTool);
      
      JMenuBar menubar = new JMenuBar();  // Create the menu bar and add the menus to it.
      menubar.add(controlMenu);
      menubar.add(colorMenu);
      menubar.add(toolMenu);
      
      return menubar;
   }  // end createMenuBar
   
   
   /**
    * Draw the picture by drawing all the items in the list of things in the picture.
    */
   protected void paintComponent(Graphics g) {
      super.paintComponent(g);
      for (int i = 0; i < things.size(); i++) {
         Drawable thng = things.get(i);
         thng.draw(g);
      }
   }
   
   
   /**
    *  Make a Drawable shape of the specified type, where type is
    *  CURVE, RETCTANGLE, or OVAL.  (NOTE:  This is the ONLY place
    *  where any testing of the type is done.)
    */
   private Drawable createDrawable(int type) {
      if (type == CURVE)
         return new CurveData();
      else if (type == RECTANGLE)
         return new RectangleData();
      else // type is OVAL
         return new OvalData();
   }
   
   /**
    *  This class defines ALL the thing that can be done with 
    *  Drawable objects in this program -- except for creating
    *  them.  Aside from constructing the objects, they can
    *  be manipulated "abstractly", by using variables and
    *  methods specified in this abstract class.
    */
   private abstract class Drawable {
      Color color;          // Color to be used for drawing this Drawable object.
      boolean useSymmetry;  // Should the symmetric reflections of the original object be drawn?
      abstract void draw(Graphics g);          // Draw this Drawable in the graphics context g.
      abstract void firstPoint(int x, int y);  // Specify the first point of the drawable (where user starts dragging).
      abstract void nextPoint(int x, int y);   // Specify a subsequent point -- generated by a mouseDragged event.
   }
   
   
   /**
    *  A Drawable object that is a curve, specified by a list of points.
    */
   private class CurveData extends Drawable{
      ArrayList<Point> points;  // the list of points on the curve.
      void draw(Graphics g) {
         int w = getWidth();
         int h = getHeight();
         g.setColor(color);  // Use this curve's color (inherited from the Drawable class!).
         for (int j = 1; j < points.size(); j++) {
            Point thisPt = points.get(j);
            Point lastPt = points.get(j-1);
            g.drawLine(lastPt.x, lastPt.y, thisPt.x, thisPt.y); // original curve
            if (useSymmetry) {  // Draw reflections only if this curve uses symmetry.
               g.drawLine(w-lastPt.x, lastPt.y, w-thisPt.x, thisPt.y);       // horizontal reflection
               g.drawLine(lastPt.x, h-lastPt.y, thisPt.x, h-thisPt.y);       // vertical reflection
               g.drawLine(w-lastPt.x, h-lastPt.y, w-thisPt.x, h-thisPt.y);   // double reflection
            }
         }
      }
      void firstPoint(int x, int y) {  // Given the first point on the curve, make the list object and add the point to it.
          points = new ArrayList<Point>();
          points.add( new Point(x,y) );
      }
      void nextPoint(int x, int y) {  // For a curve, points from mouseDragged are just added to the list.
         points.add( new Point(x,y) );
      }
   }
   
   
   /**
    *  A Drawable that is a rectangle, specified by its two corners.
    */
   private class RectangleData extends Drawable {
       int x1, y1, x2, y2;  // The corners of the rectangle are (x1,y1) and (x2,y2).
       void drawRect(Graphics g, int a1, int b1, int a2, int b2) { // Draw a (filled) rectangle, corners at (a1,b1) and (a2,b2).
               // This is tricky, since the g.fillRect() method needs the UPPER LEFT corner
                //                   plus the width and height of the rectangle.
           int amin = Math.min(a1,a2);   // x-coord of upper left corner
           int bmin = Math.min(b1,b2);   // y-coord of upper left corner
           int amax = Math.max(a1,a2);   // x-coord of lower right corner  (width is amax-amin)
           int bmax = Math.max(b1,b2);   // y-coord of lower right corner  (height is bmax-bmin)
           g.fillRect(amin, bmin, amax-amin, bmax-bmin);
       }
       void draw(Graphics g) {
           g.setColor(color);
           drawRect(g,x1,y1,x2,y2);
           if (useSymmetry) {
              int w = getWidth();
              int h = getHeight();
              drawRect(g,x1,h-y1,x2,h-y2);
              drawRect(g,w-x1,y1,w-x2,y2);
              drawRect(g,w-x1,h-y1,w-x2,h-y2);
           }
       }
       void firstPoint(int x, int y) {  // Specified the first corner of the rectangle.
          x1 = x;
          y1 = y;
          x2 = x;  // There is only one point so far -- put both corners at that point.
          y2 = y;
       }
       void nextPoint(int x , int y) {  // Specifies the second corner of the rectangle.
          x2 = x;  // NOTE:  (x,y) will REPLACE the previous value of the second corner.
          y2 = y;  //    (As the user drags the mouse, the second corner follows the mouse.)
       }
   }
   
   
   /**
    *  A Drawable object that is an oval.  The oval is just contained in a rectangle whose
    *  corners are specified.
    */
   private class OvalData extends Drawable {
       int x1, y1, x2, y2;
       void drawOval(Graphics g, int a1, int b1, int a2, int b2) {  // Draw a (filled) oval in rect with corners at (a1,b1), (a2,b2)
           int amin = Math.min(a1,a2);
           int bmin = Math.min(b1,b2);
           int amax = Math.max(a1,a2);
           int bmax = Math.max(b1,b2);
           g.fillOval(amin, bmin, amax-amin, bmax-bmin);
       }
       void draw(Graphics g) {
           g.setColor(color);
           drawOval(g,x1,y1,x2,y2);
           if (useSymmetry) {
              int w = getWidth();
              int h = getHeight();
              drawOval(g,x1,h-y1,x2,h-y2);
              drawOval(g,w-x1,y1,w-x2,y2);
              drawOval(g,w-x1,h-y1,w-x2,h-y2);
           }
       }
       void firstPoint(int x, int y) {
          x1 = x;
          y1 = y;
       }
       void nextPoint(int x , int y) {
          x2 = x;
          y2 = y;
       }
   }
   
   
   /**
    *  This class defines an action handler that will handle all events from the menus.
    */
   private class MenuHandler implements ActionListener {
      public void actionPerformed(ActionEvent evt) {
         String cmd = evt.getActionCommand();
         if (cmd.equals("Clear")) {
            things.clear();
            repaint();
         }
         else if (cmd.equals("Undo")) {
             if (things.size() > 0) {
                things.remove(things.size()-1);
                repaint();
             }
         }
         else if (cmd.equals("Quit"))
             System.exit(0);
         else if (cmd.equals("Black"))
             currentColor = Color.BLACK;
         else if (cmd.equals("Red"))
             currentColor = Color.RED;
         else if (cmd.equals("Green"))
             currentColor = Color.GREEN;
         else if (cmd.equals("Blue"))
             currentColor = Color.BLUE;
         else if (cmd.equals("Cyan"))
             currentColor = Color.CYAN;
         else if (cmd.equals("Magenta"))
             currentColor = Color.MAGENTA;
         else if (cmd.equals("Yellow"))
             currentColor = Color.YELLOW;
         else if (cmd.equals("Curve"))
             currentTool = CURVE;
         else if (cmd.equals("Rectangle"))
             currentTool = RECTANGLE;
         else if (cmd.equals("Oval"))
             currentTool = OVAL;
      }
   }
   
   
   /**
    *  This class defines a handler that will handle mouse drags on the panel.
    */
   private class MouseHandler implements MouseListener, MouseMotionListener {
      boolean dragging;    // This is true if a drag operation is in progress.
      int startX, startY;  // The point where the mouse was first pressed, from mousePressed()
      Drawable thing;      // The Drawable that the user is currently drawing.  This is
                           //    null until it is created in mouseDragged() the first time
                           //    mouse moves.
      public void mousePressed(MouseEvent evt) {
         startX = evt.getX();  // Record starting point of drag.
         startY = evt.getY();
         thing = null;         // Object will be created in mouseDragged()
         dragging = true;
      }
      public void mouseDragged(MouseEvent evt) {
         if (!dragging)
            return;
         if (thing == null) {  // This is the first mouseDragged() of the drag operation -- create the Drawable.
            thing = createDrawable(currentTool);  // Create a Drawable of a type determined by the current tool.
            thing.color = currentColor;           // Use the current color for this drawable.
            thing.useSymmetry = useSymmetryCheckbox.isSelected();  // Use the current symmetry setting for this drawable.
            thing.firstPoint(startX, startY);     // Add the first point of the draw operation to the Drawable.
            things.add(thing);  // Add the drawable to the list of all the things in the picture.
         }
         thing.nextPoint(evt.getX(),evt.getY());  // Set the next point in the Drawable (different meaning for different objects!)
         repaint();  // make sure that the picture is redrawn to show the change.
      }
      public void mouseReleased(MouseEvent evt) {
         if (!dragging)
            return;
         thing = null;
         dragging = false;
      }
      public void mouseMoved(MouseEvent evt) {
      }
      public void mouseEntered(MouseEvent evt) {
      }
      public void mouseExited(MouseEvent evt) {
      }
      public void mouseClicked(MouseEvent evt) {
      }
   }
   
   
}

