content: /*
   * Copyright 2010 Aalto University, ComNet
   * Released under GPLv3. See LICENSE.txt for details.
   */
  package gui.playfield;
  
  import gui.DTNSimGUI;
  
  import java.awt.Color;
  import java.awt.Dimension;
  import java.awt.Graphics;
  import java.awt.Graphics2D;
  import java.awt.event.MouseAdapter;
  import java.awt.event.MouseEvent;
  import java.awt.geom.AffineTransform;
  import java.awt.image.BufferedImage;
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.List;
  
  import javax.swing.JPanel;
  
  import movement.Path;
  import movement.map.SimMap;
  import core.Coord;
  import core.DTNHost;
  import core.World;
  
  /**
   * The canvas where node graphics and message visualizations are drawn.
   *
   */
  @SuppressWarnings("serial")
  public class PlayField extends JPanel {
  	public static final int PLAYFIELD_OFFSET = 10;
  
  	private World w;
  	private DTNSimGUI gui;
  
  	private Color bgColor = Color.WHITE;
  
  	private List<PlayFieldGraphic> overlayGraphics;
  	private boolean autoClearOverlay;	// automatically clear overlay graphics
  	private MapGraphic mapGraphic;
  	private boolean showMapGraphic;
  	private ScaleReferenceGraphic refGraphic;
  	private boolean focusOnClick;
  
  	private BufferedImage underlayImage;
  	private AffineTransform imageTransform;
  	private AffineTransform curTransform;
  	private double underlayImgDx;
  	private double underlayImgDy;
  
  	/**
  	 * Creates a playfield
  	 * @param w The world that contains the actors to be drawn
  	 */
  	public PlayField (World w, DTNSimGUI gui) {
  		this.w = w;
  		this.gui = gui;
  
  		this.refGraphic = new ScaleReferenceGraphic();
  		updateFieldSize();
          this.setBackground(bgColor);
          this.overlayGraphics = Collections.synchronizedList(
  		new ArrayList<PlayFieldGraphic>());
          this.mapGraphic = null;
          this.underlayImage = null;
          this.imageTransform = null;
          this.autoClearOverlay = true;
  
          this.addMouseListener(new MouseAdapter() {
  			@Override
  			public void mouseClicked(MouseEvent e) {
  				if (focusOnClick) {
  					focusClosestNode(e.getX(), e.getY());
  				}
  			}
  		});
  	}
  
  	/**
  	 * Schedule the play field to be drawn
  	 */
  	public void updateField() {
  		this.repaint();
  	}
  
  	/**
  	 * Sets an image to show under the host graphics
  	 * @param image The image to set or null to remove the image
  	 * @param dx X offset of the image
  	 * @param dy Y offset of the image
  	 * @param scale Image scaling factor
  	 * @param rotation Rotatation angle of the image (radians)
  	 */
  	public void setUnderlayImage(BufferedImage image,
  			double dx, double dy, double scale, double rotation) {
  		if (image == null) {
  			this.underlayImage = null;
  			this.imageTransform = null;
  			this.curTransform = null;
  			return;
  		}
  		this.underlayImage = image;
          this.imageTransform = AffineTransform.getRotateInstance(rotation);
          this.imageTransform.scale(scale, scale);
          this.curTransform = new AffineTransform(imageTransform);
          this.underlayImgDx = dx;
          this.underlayImgDy = dy;
  
  		curTransform.scale(PlayFieldGraphic.getScale(),
  				PlayFieldGraphic.getScale());
  		curTransform.translate(this.underlayImgDx, this.underlayImgDy);
  
  	}
  
  	/**
  	 * Sets the zooming/scaling factor
  	 * @param scale The new scale
  	 */
  	public void setScale(double scale) {
  		PlayFieldGraphic.setScale(scale);
  		this.updateFieldSize();
  		if (this.imageTransform != null) {
  			this.curTransform = new AffineTransform(imageTransform);
  			curTransform.scale(scale, scale);
  			curTransform.translate(this.underlayImgDx, this.underlayImgDy);
  		}
  	}
  
  	/**
  	 * Sets the source for the map graphics and enables map graphics showing
  	 * @param simMap The map to show
  	 */
  	public void setMap(SimMap simMap) {
  		this.mapGraphic = new MapGraphic(simMap);
  		this.showMapGraphic = true;
  	}
  
  	/**
  	 * Enables/disables showing of map graphics
  	 * @param show True if the map graphics should be shown (false if not)
  	 */
  	public void setShowMapGraphic(boolean show) {
  		this.showMapGraphic = show;
  	}
  
  	/**
  	 * Enables or disables the automatic clearing of overlay graphics.
  	 * If enabled, overlay graphics are cleared every time a new graphics
  	 * object is set to be drawn.
  	 * @param clear Auto clear is enabled if this is true, disabled on false
  	 */
  	public void setAutoClearOverlay(boolean clear) {
  		this.autoClearOverlay = clear;
  	}
  
  	/**
  	 * Enables or disables the automatic focus on click.
  	 * If enabled, the node that is closest to the clicked location is 
  	 * set on focus
  	 * @param focus Auto focus is enabled if this is true, disabled on false
  	 */
  	public void setFocusOnClick(boolean focus) {
  		this.focusOnClick = focus;
  	}
  
  	/**
  	 * Draws the play field. To be called by Swing framework or directly if
  	 * different context than screen is desired
  	 * @param g The graphics context to draw the field to
  	 */
  	public void paint(Graphics g) {
  		Graphics2D g2 = (Graphics2D)g;
  		g2.setBackground(bgColor);
  
  		g2.translate(PLAYFIELD_OFFSET, PLAYFIELD_OFFSET);
  
  		// clear old playfield graphics
  		g2.clearRect(-PLAYFIELD_OFFSET, -PLAYFIELD_OFFSET,
  				this.getWidth() + PLAYFIELD_OFFSET,
  				this.getHeight() + PLAYFIELD_OFFSET);
  		if (underlayImage != null) {
  			g2.drawImage(underlayImage,curTransform, null);
  		}
  
  		// draw map (is exists and drawing requested)
  		if (mapGraphic != null && showMapGraphic) {
  			mapGraphic.draw(g2);
  		}
  
  		// draw hosts
  		for (DTNHost h : w.getHosts()) {
  			new NodeGraphic(h).draw(g2);
  		}
  
  		// draw overlay graphics
  		for (int i=0, n=overlayGraphics.size(); i<n; i++) {
  			overlayGraphics.get(i).draw(g2);
  		}
  
  		// draw reference scale
  		this.refGraphic.draw(g2);
  	}
  
  
  	/**
  	 * Removes all overlay graphics stored to be drawn
  	 */
  	public void clearOverlays() {
  		this.overlayGraphics.clear();
  	}
  
  	/**
  	 * Adds graphics for message transfer
  	 * @param from Who the message was from
  	 * @param to Who the message was to
  	 */
  	public void addMessageTransfer(DTNHost from, DTNHost to) {
  		autoClear();
  		this.overlayGraphics.add(new MessageGraphic(from,to));
  	}
  
  	/**
  	 * Adds a path to the overlay graphics
  	 * @param path Path to add
  	 */
  	public void addPath(Path path) {
  		autoClear();
  		this.overlayGraphics.add(new PathGraphic(path));
  		this.updateField();
  	}
  
  	/**
  	 * Clears overlay graphics if autoclear is requested
  	 * @see #setAutoClearOverlay(boolean)
  	 */
  	private void autoClear() {
  		if (this.autoClearOverlay) {
  			this.clearOverlays();
  		}
  	}
  
  	/**
  	 * Returns the graphical presentation location for the given world
  	 * location
  	 * @param loc The location to convert
  	 * @return Same location in graphics space
  	 * @see #getWorldPosition(Coord)
  	 */
  	public Coord getGraphicsPosition(Coord loc) {
  		Coord c = loc.clone();
  		c.setLocation(PlayFieldGraphic.scale(c.getX()) + PLAYFIELD_OFFSET,
  				PlayFieldGraphic.scale(c.getY()) + PLAYFIELD_OFFSET);
  		return c;
  	}
  
  	/**
  	 * Returns a world location for a given graphical location. Note that
  	 * there might be inaccuracies because of rounding.
  	 * @param loc The location to convert
  	 * @return Same location in world space
  	 * @see #getGraphicsPosition(Coord)
  	 */
  	public Coord getWorldPosition(Coord loc) {
  		Coord c = loc.clone();
  		c.setLocation(PlayFieldGraphic.invScale(c.getX() - PLAYFIELD_OFFSET),
  				PlayFieldGraphic.invScale(c.getY() - PLAYFIELD_OFFSET));
  		return c;
  	}
  
  	/**
  	 * Updates the playfields (graphical) size to match the world's size
  	 * and current scale/zoom.
  	 */
  	private void updateFieldSize() {
          Dimension minSize = new Dimension(
  		PlayFieldGraphic.scale(w.getSizeX()),
  		PlayFieldGraphic.scale(w.getSizeY()) );
          this.setMinimumSize(minSize);
          this.setPreferredSize(minSize);
          this.setSize(minSize);
  	}
  
  	/**
  	 * Sets the focus on the node that is closest to the given coordinates
  	 * (in the graphic view of the playfield)
  	 * @param x The X coordinate
  	 * @param y The Y coordinate
  	 */
  	private void focusClosestNode(int x, int y) {
  		DTNHost closest = w.getHosts().get(0);
  		double closestDist = Double.MAX_VALUE;
  		double dist;
  
  		Coord clickLoc = getWorldPosition(new Coord(x,y));
  
  		for (DTNHost h : w.getHosts()) {
  			dist = h.getLocation().distance(clickLoc);
  
  			if (dist < closestDist) {
  				closest = h;
  				closestDist = dist;
  			}
  		}
  
  		gui.setFocus(closest);
  	}
  }
  