/*
 * 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;

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

public class Image implements NGP.Graphic, NGP.Locatable, NGP.Sizeable {

  /** Our size */
  private java.awt.Dimension _dimension;
  /** Our location */
  private java.awt.Point _point;
  /** Our DrawingPanel */
  private NGP.Containers.DrawingPanel _dpanel;
  /** The actual image */
  private java.awt.Image _awtImage;
  /** Our rotation */
  private double _rotationAngle;
  /** The bounding rectangle */
  private java.awt.Rectangle _bounds;
  /** Indicate whether we should wrap or not */
  private boolean _wrapped = false;
  /** Indicate whether this Image is active for dragging */
  private boolean _active = false;

  /**
   * Create an image from a filename.
   *
   * @param dp the DrawingPanel for this image
   * @param file the filename to take this image form (gif or jpg)
   */
  public Image(NGP.Containers.DrawingPanel dp, String file) {
    _awtImage = java.awt.Toolkit.getDefaultToolkit().getImage(file);
    this.setup(dp);
  }

  /**
   * Create an image from a URL.
   *
   * @param dp the DrawingPanel for this image
   * @param url the URL to take this image form (gif or jpg)
   * @see NGP.Utilities#createURL(String) createURL
   */
  public Image(NGP.Containers.DrawingPanel dp, java.net.URL url) {
    _awtImage = java.awt.Toolkit.getDefaultToolkit().getImage(url);
    this.setup(dp);
  }

  /**
   * Normal users need not use this!
   *<p>
   * Use the media tracker to load the image, then initialize everything else.
   */
  protected void setup(NGP.Containers.DrawingPanel dp) {
    _dpanel = dp;
    java.awt.MediaTracker tracker = new java.awt.MediaTracker(_dpanel);
    tracker.addImage(_awtImage, 0);
    try {
      tracker.waitForAll();
    }
    catch(InterruptedException e) { }
    if (tracker.isErrorAny()) {
      System.out.println("Image failed to load.");
      System.out.println("Execution will continue but this image won't work, and you can probably expect some other errors.");
      return;
    }
    _point = new java.awt.Point(0, 0);
    _dimension = new java.awt.Dimension(_awtImage.getWidth(_dpanel),
          _awtImage.getHeight(_dpanel));
    _rotationAngle = 0;
    _bounds = new java.awt.Rectangle(_point, _dimension);
    this.show();
  }

  /**
   * Set the dimensions of this Image
   *
   * @param d the new <code>Dimension</code> for this Image
   */
  public void setDimension(java.awt.Dimension d) {
    java.awt.Rectangle oldBounds = this.getBounds();
    _dimension = d;
    _bounds.setSize(_dimension);
    _awtImage = _awtImage.getScaledInstance(_dimension.width,
              _dimension.height,
              _awtImage.SCALE_DEFAULT);
    _dpanel.repaint(this.getBounds().union(oldBounds));
  }

  /**
   * Get the dimensions of this Image
   *
   * @return the <code>Dimension</code> for this Image
   */
  public java.awt.Dimension getDimension() {
    return _dimension;
  }

  /**
   * Set the location of this Image
   *
   * @param p the new <code>Point</code> for this Image
   */
  public void setLocation(java.awt.Point p) {
    java.awt.Rectangle oldBounds = this.getBounds();
    _point = p;
    if (_wrapped) {
      java.awt.Dimension panelSize = _dpanel.getDimension();
      int x = _point.x%panelSize.width;
      int y = _point.y%panelSize.height;
      if (x < 0) x += panelSize.width;
      if (y < 0) y += panelSize.height;
      _point.x = x;
      _point.y = y;
    }
    _bounds.setLocation(_point);
    //_dpanel.repaint(this.getBounds().union(oldBounds));
    _dpanel.repaint();
  }

  /**
   * Set the location of the Image so that its center point is
   * at the specified location.
   *
   * @param p the new <code>Point</code> for the center of this
   * Image
   */
  public void setCenterLocation(java.awt.Point p) {
    this.setLocation(new java.awt.Point(p.x - _dimension.width/2,
					p.y - _dimension.height/2));
  }

  /**
   * Get the location of this Image
   *
   * @return the <code>Point</code> for this Image
   */
  public java.awt.Point getLocation() {
    return _point;
  }

  /** Hide the Image so it won't paint */
  public void hide() {
    if (_dpanel != null) {
      _dpanel.removeGraphic(this);
      _dpanel.repaint(this.getBounds());
    }
  }

  /** Show the Image so it paints */
  public void show() {
    _dpanel.addGraphic(this);
    _dpanel.repaint(this.getBounds());
  }

  /**
   * Set the DrawingPanel for this Image
   *
   * @param dp the new <code>DrawingPanel</code> for the Image
   */
  public void setDrawingPanel(NGP.Containers.DrawingPanel dp) {
    this.hide();
    _dpanel = dp;
    this.show();
  }

  /**
   * Get the DrawingPanel for this Image
   *
   * @return the <code>DrawingPanel</code> for the Image
   */
  public NGP.Containers.DrawingPanel getDrawingPanel() {
    return _dpanel;
  }

  /**
   * Get the AWT Image that we are using, not necessary for most users.
   *
   * @return the real AWT image
   */
  public java.awt.Image getAWTImage() {
    return _awtImage;
  }

  /**
   * See if this Image contains the Point
   *
   * @param p the <code>Point</code> to test
   */
  public boolean contains(java.awt.Point p) {
    if (0 != _rotationAngle) {
      double x = _bounds.getCenterX();
      double y = _bounds.getCenterY();
      java.awt.geom.AffineTransform trans =
        java.awt.geom.AffineTransform.getRotateInstance(_rotationAngle, x, y);
      java.awt.Shape s = trans.createTransformedShape(_bounds);
      return s.contains(p);
    }
    return _bounds.contains(p);
  }

  /**
   * Set the rotation for this Image
   *
   * @param degrees the degrees (clockwise) that this should rotate
   */
  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 for this Image
   *
   * @return the degrees (clockwise) that this should rotate
   */
  public int getRotation() {
    return (int)(_rotationAngle * (180/Math.PI));
  }

  /**
   * Normal users need not use this!
   *<p>
   * Rotate the Graphics Context if necessary, the paint, then reset the
   * rotation
   */
  public void paint(java.awt.Graphics2D g) {
    if (_rotationAngle != 0) {
      double centerX = _bounds.getCenterX();
      double centerY = _bounds.getCenterY();
      g.rotate(_rotationAngle, centerX, centerY);
      g.drawImage(_awtImage, _point.x, _point.y, _dpanel);
      g.rotate(_rotationAngle*-1, centerX, centerY);
    }
    else g.drawImage(_awtImage, _point.x, _point.y, _dpanel);
  }

  /** Set this image so that it will always display in its DrawingPanel */
  public void wrap() {
    _wrapped = true;
  }

  /**
   * Set this image so that it will does not necessarily display in its
   * DrawingPanel
   */
  public void unwrap() {
    _wrapped = false;
  }

  /**
   * Get the rectangle that indicates the bounds of this <code>Image</code>
   *
   * @return the <code>java.awt.Rectangle</code> that indicates our bounds.
   */
  public java.awt.Rectangle getBounds() {
    java.awt.Rectangle r = _bounds;
    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(_bounds);
      r = s.getBounds();
    }
    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);
  }

  /**
   * Determine whether the passed in <code>Graphic</code> intersects with
   * this <code>Image</code>.
   *
   * @param g the <code>Graphic</code> to test
   * @return <code>true</code> if they do intersect, <code>false</code>
   * otherwise
   */
  public boolean intersects(NGP.Graphic g) {
    return this.getBounds().intersects(g.getBounds());
  }

  /** Override to do something useful. */
  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 Image, {@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 Image 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 Image to be active.  This means that if the mouse is
    * subsequently dragged, the drag(MouseEvent e) method of the Image 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 image inactive if it is active. Ensures that mouse drags
    * that originate outside of this Image 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) { }

}


