import java.awt.*;
import java.beans.*;

/*
 * An encapsulation of a rectangle.
 * 
 * This implementation differs in significant ways from both the Rectangle class
 * and the Rectangle2D inner classes. Perhaps most importantly, it's
 * attributes are private so that it can serve as a property change subject.
 * 
 * @author  Prof. David Bernstein, James Madison Univerity
 * @version 1.0
 */
public class Rectangle
{
  private static final int  SQUARE_WIDTH = 3;  
  private static final int  SQUARE_GAP   = 1;  
  
  private boolean selected;
  private int height, width, x, y;
  private PropertyChangeSupport support;
  
  /**
   * Explicit value constructor.
   * 
   * @param x       The x-coordinate of the upper-left corner
   * @param y       The y-coordinate of the upper-left corner
   * @param width   The width
   * @param height  The height
   */
  public Rectangle(int x, int y, int width, int height)
  {
    this.x = x;
    this.y = y;
    this.width = width;
    this.height = height;
    selected = false;
    
    support = new PropertyChangeSupport(this);
  }
  
  /**
   * Add a PropertyChangeListener to this Rectangle.
   * The given object will actually listen to four different properties:
   * x, y, width, and height.
   * 
   * @param listener  The listener to add
   */
  public void addPropertyChangeListener(PropertyChangeListener listener)
  {
    support.addPropertyChangeListener("x", listener);
    support.addPropertyChangeListener("y", listener);
    support.addPropertyChangeListener("width", listener);
    support.addPropertyChangeListener("height", listener);
  }
  
  /**
   * Create a square visual grabber (for dragging a corner of this Rectangle).
   * 
   * @param x       The x-coordinate of the point of interest
   * @param y       The y-coordinate of the point of interest
   * @param deltaX  The horizontal offset (-1 for West, 1 for East)
   * @param deltaY  The vertical offset (-1 for North, 1 for South)
   * @return
   */
  private int[] createGrabber(int x, int y, int deltaX, int deltaY)
  {
    int x1 = x + deltaX * SQUARE_GAP;
    int y1 = y + deltaY * SQUARE_GAP;
    int x2 = x1 + deltaX * SQUARE_WIDTH;
    int y2 = y1 + deltaY * SQUARE_WIDTH;
    
    int sx = Math.min(x1, x2);
    int sy = Math.min(y1, y2);
    
    int[] result = {sx, sy, SQUARE_WIDTH, SQUARE_WIDTH};
    
    return result;
  }

  /**
   * Return true if this Rectangle contains the given Point.
   * 
   * @param p  The Point of interest
   * @return   true if p is contained in this Rectangle; false otherwise
   */
  public boolean contains(Point p)
  {
    return (p.x >= x) && (p.x <= x + width) && (p.y >= y) && (p.y <= y + height);
  }
  
  /**
   * Get the four corners for this Rectangle.
   * 
   * @return  The four corners;
   */
  public Point[] corners()
  {
    Point[] result = new Point[4];
    result[0] = new Point(x, y);                  // Upper-left
    result[1] = new Point(x + width, y);          // Upper-right
    result[2] = new Point(x + width, y + height); // Lower-left
    result[3] = new Point(x, y + height);         // Lower-left
    
    return result;
  }
  
  /**
   * Draw a rectangular area.
   * 
   * @param g  The rendering engine to use
   * @param r  The x, y, width, and height of the rectangular area
   */
  private void drawRect(Graphics g, int...r )
  {
    g.drawRect(r[0], r[1], r[2], r[3]);
  }
  
  /**
   * Fill a rectangular area.
   * 
   * @param g  The rendering engine to use
   * @param r  The x, y, width, and height of the rectangular area
   */
  private void fillRect(Graphics g, int...r )
  {
    g.fillRect(r[0], r[1], r[2], r[3]);
  }
  
  /**
   * Get the height.
   * 
   * @return  The height
   */
  public int getHeight()
  {
    return height;
  }
  
  /**
   * Get the width.
   * 
   * @return  The width
   */
  public int getWidth()
  {
    return width;
  }
  
  /**
   * Get the x-coordinate of the upper-left corner.
   * 
   * @return  The x-coordinate
   */
  public int getX()
  {
    return x;
  }
  
  /**
   * Get the y-coordinate of the upper-left corner.
   * 
   * @return  The x-coordinate
   */
  public int getY()
  {
    return y;
  }

  /**
   * Return true if this Rectangle is currently selected.
   * 
   * @return true if selected; false otherwise
   */
  public boolean isSelected()
  {
    return selected;
  }

  /**
   * Render this Rectangle.
   * 
   * @param g  The rendering engine to use
   */
  public void paint(Graphics g)
  {
    drawRect(g, x,  y,  width,  height);
    
    if (selected)
    {
      Color old = g.getColor();
      g.setColor(Color.BLUE);
      
      paintGrabbers(g, x, y);
      paintGrabbers(g, x+width, y);
      paintGrabbers(g, x+width, y+height);
      paintGrabbers(g, x, y+height);
      
      g.setColor(old);
    }
  }
  
  /**
   * Render the visual grabbers for a particular point/corner.
   * 
   * @param g  The rendering engine to use
   * @param x  The x-coordinate of the point/corner
   * @param y  The y-coordinate of the point/corner
   */
  private void paintGrabbers(Graphics g, int x, int y)
  {
    fillRect(g, createGrabber(x, y, -1, -1)); // NORTH_WEST grabber
    fillRect(g, createGrabber(x, y, -1,  1)); // NORTH_EAST grabber
    fillRect(g, createGrabber(x, y,  1,  1)); // SOUTH_EAST grabber
    fillRect(g, createGrabber(x, y,  1, -1)); // SOUTH_WEST grabber
  }

  /**
   * Remove a PropertyChangeListener (for all properties).
   * 
   * @param listener  The listener to remove.
   */
  public void removePropertyChangeListener(PropertyChangeListener listener)
  {
    support.removePropertyChangeListener(listener);
  }

  /**
   * Select this Rectangle or not.
   * 
   * @param selected  true to select; false to unselect
   */
  public void setSelected(boolean selected)
  {
    this.selected = selected;
  }
  
  /**
   * Set the height.
   * 
   * @param height The height
   */
  public void setHeight(int height)
  {
    support.firePropertyChange("height", this.height, height);
    this.height = height;
  }
  
  /**
   * Set the width.
   * 
   * @param width The width
   */
  public void setWidth(int width)
  {
    support.firePropertyChange("width", this.width, width);
    this.width = width;
  }
  
  /**
   * Set the x-coordinate of the upper-left corner.
   * 
   * @param x The x-coordinate
   */
  public void setX(int x)
  {
    support.firePropertyChange("x", this.x, x);
    this.x = x;
  }
  
  /**
   * Set the y-coordinate of the upper-left corner.
   * 
   * @param y The y-coordinate
   */
  public void setY(int y)
  {
    support.firePropertyChange("y", this.y, y);
    this.y = y;
  }
}
