/*
 * Copyright 1999 by Brown University Computer Science
 * 115 Waterman Street, 4th Floor, Providence, Rhode Island, 02906, U.S.A
 * All rights reserved.
 *
 * Permission is hereby granted for free use and modification of this
 * software.  However, this copyright must remain at all times.
 * Re-distribution is restricted to those with the express written consent
 * of the copyright holder.  Permission can be obtained by mailing
 * cs015headtas@cs.brown.edu.  
 */

package NGP.Graphics;

/**
 * A geometric object that knows how to draw itself on the screen.
 *
 * @author Matt Chotin (<a href="mailto:mhc@cs.brown.edu">mhc</a>)
 */

public abstract class Shape implements NGP.Graphic, NGP.Colorable {

  /** The DrawingPanel that contains this Shape */
  protected NGP.Containers.DrawingPanel _dpanel;
  /** The AWT geometric shape used for drawing */
  protected java.awt.Shape _awtShape;
  /** This shape's color */
  private java.awt.Color _color;
  /** The angle of this shape's rotation (initially 0) */
  private double _rotationAngle;
  /** Indicates whether the current Image is active for dragging */
  private boolean _active = false;

  /**
   * Create a Shape with the specified DrawingPanel
   *
   * @param dpanel the DrawingPanel for this Shape
   */
  public Shape(NGP.Containers.DrawingPanel dpanel, java.awt.Shape s) {
    _dpanel = dpanel;
		_awtShape = s;
    _color = DEFAULT_GRAY;
    _rotationAngle = 0;
  }

  /**
   * Hide the Shape so it won't be drawn anymore (NGP will lose it's reference
   * to it).
   */
  public void hide() {
    if (_dpanel != null) {
      _dpanel.removeGraphic(this);
      _dpanel.repaint(this.getBounds());
    }
  }

  /**
   * Show the shape so it will be drawn (NGP now has a reference to it).
   */
  public void show() {
    _dpanel.addGraphic(this);
    java.awt.Rectangle bounds = this.getBounds();
    _dpanel.repaint(bounds);
  }

  /**
   * Normal users need not use this!
   *<p>
   * This is how we draw graphics using Java.  First set the color of the
   * Graphics Context.  Then find out what rotation we want, the finally paint.
   * Then reset the rotation if necessary.
   *
   * @param g the Graphics2D for us to use.
   * @see <a href="http://java.sun.com/products/jdk/1.2/docs/api/java/awt/Graphics2D.html">java.awt.Graphics2D</a>
   */
  public void paint(java.awt.Graphics2D g) {
    g.setPaint(_color);
    if (_rotationAngle != 0) {
      java.awt.Rectangle rect = _awtShape.getBounds();
      double centerX = rect.getCenterX();
      double centerY = rect.getCenterY();
      g.rotate(_rotationAngle, centerX, centerY);
      this.actualPaint(g);
      g.rotate(_rotationAngle*-1, centerX, centerY);
    }
    else this.actualPaint(g);
  }

  /** Subclasses will define how painting actually works, filled or framed */
  protected abstract void actualPaint(java.awt.Graphics2D g);

  /**
   * Set the color of the Shape
   *
   * @param color the <code>Color</code> for the Shape
   * @see <a href="http://java.sun.com/products/jdk/1.2/docs/api/java/awt/Color.html">java.awt.Color</a>
   */
  public void setColor(java.awt.Color color) {
    _color = color;
    _dpanel.repaint(this.getBounds());
  }

  /**
   * Get the color of the Shape
   *
   * @return the <code>Color</code> for the Shape
   */
  public java.awt.Color getColor() {
    return _color;
  }

  /**
   * Set the DrawingPanel of this Shape so that it appears somewhere else.
   *
   * @param dp the new DrawingPanel for the Shape
   */
  public void setDrawingPanel(NGP.Containers.DrawingPanel dp) {
    this.hide();
    _dpanel = dp;
    this.show();
  }

  /**
   * Return the DrawingPanel for this Shape
   *
   * @return the DrawingPanel for this Shape
   */
  public NGP.Containers.DrawingPanel getDrawingPanel() {
    return _dpanel;
  }

  /**
   * Determine if a Point is within this shape.  This works even if
	 * the Shape is rotated.
   *
   * @return <code>true</code> if the Point is within the shape, otherwise
   * <code>false</code>
   */
  public boolean contains(java.awt.Point p) {
		if (0 != _rotationAngle) {
			java.awt.Rectangle r = _awtShape.getBounds();
			double x = r.getCenterX();
			double y = r.getCenterY();
			java.awt.geom.AffineTransform trans = 
				java.awt.geom.AffineTransform.getRotateInstance(_rotationAngle, x, y);
			java.awt.Shape s = trans.createTransformedShape(_awtShape);
			return s.contains(p);
		}
    return _awtShape.contains(p);
  }

  /**
   * Determine if the passed in <code>Graphic</code> intersects with this
   * <code>Shape</code>.
   *
   * @param g the <code>Graphic</code> we are testing against
   * @return <code>true</code> if it does intersect, <code>false</code>
   * if not
   */
  public boolean intersects(NGP.Graphic g) {
		if (0 != _rotationAngle) {
			java.awt.Rectangle r = _awtShape.getBounds();
			double x = r.getCenterX();
			double y = r.getCenterY();
			java.awt.geom.AffineTransform trans = 
				java.awt.geom.AffineTransform.getRotateInstance(_rotationAngle, x, y);
			java.awt.Shape s = trans.createTransformedShape(_awtShape);
			return s.intersects(g.getBounds());
		}
    return _awtShape.intersects(g.getBounds());
  }

  /**
   * Set the rotation of the Shape.
   *
   * @param degrees the degrees (clockwise) to rotate the Shape
   */
  public void setRotation(int degrees) {
		java.awt.Rectangle oldBounds = this.getBounds();
    _rotationAngle = degrees * (Math.PI/180.0);
    _dpanel.repaint(this.getBounds().union(oldBounds));
  }

  /**
   * Get the rotation of the Shape.
   *
   * @return the degrees (clockwise) the Shape is rotated
   */
  public int getRotation() {
    return (int)(_rotationAngle * (180/Math.PI));
  }
	
  /**
   * Find the bounding rectangle of this Shape.  This takes
   * rotation into account.
   *
   * @return the bounding java.awt.Rectangle of this Shape
   */
  public java.awt.Rectangle getBounds() {
    java.awt.Rectangle r = _awtShape.getBounds();
    if (0 != _rotationAngle) {
      double x = r.getCenterX();
      double y = r.getCenterY();
      java.awt.geom.AffineTransform trans = 
	java.awt.geom.AffineTransform.getRotateInstance(_rotationAngle, x, y);
			java.awt.Shape s = trans.createTransformedShape(_awtShape);
			r = s.getBounds();
    }
    //java.awt.Rectangle rect = new java.awt.Rectangle(r);
    //System.out.println("RECT IS " + rect.width + " " + rect.height);
    //System.out.println("r is " + r.width + " " + r.height);
    //r.grow(1,1);
    //return rect;
    return r;
  }

  /**
   * Return the Point that represents the center of the bounding rectangle
   * of this shape.
   *
   * @return the java.awt.Point of the center of this shape
   */
  public java.awt.Point getCenterLocation() {
    java.awt.Rectangle r = this.getBounds();
    return new java.awt.Point(r.x + r.width/2, r.y + r.height/2);
  }
  
  /** 
   * Override to do something useful. Called when the mouse is clicked
   * within this Shape
   */
  public void react() {	}

  /** Override to do something useful. */
  public void drag(java.awt.event.MouseEvent e) { }

  /**
   * Called when the Panel detects that the mouse was clicked.
   * If the click is within this Shape, {@link #react() react}
   * will be called.
   */
  public void mouseClicked(java.awt.event.MouseEvent e) {
    if (this.contains(e.getPoint())) this.react();
  }

 /**
    * Called when the Panel detects that the mouse was dragged.
    * If this shape is active, then {@link #drag(MouseEvent e) drag}
    * will be called.
    */
  public void mouseDragged(java.awt.event.MouseEvent e) {
    if(_active) this.drag(e);
  }



  /** Called when the Panel detects that the mouse entered.  Does nothing. */
  public void mouseEntered(java.awt.event.MouseEvent e) { }
  /** Called when the Panel detects that the mouse exited.  Does nothing. */
  public void mouseExited(java.awt.event.MouseEvent e) { }


  /** Called when the Panel detects that the mouse was pressed.
    * Sets this Shape to be active.  This means that if the mouse is
    * subsequently dragged, the drag(MouseEvent e) method of the Shape will
    * be called.
    */
  public void mousePressed(java.awt.event.MouseEvent e) { 
    if (this.contains(e.getPoint())) _active = true;
  }

  /** Called when the Panel detects that the mouse was released.
    * Makes this shape inactive if it is active. Ensures that mouse drags
    * that originate outside of this shape will not invoke the
    * drag(MouseEvent e) method.
    */
  public void mouseReleased(java.awt.event.MouseEvent e) { _active = false; }

  /** Called when the Panel detects that the mouse was moved. Does nothing.*/
  public void mouseMoved(java.awt.event.MouseEvent e) { }
}

