content: /*
   * Copyright 2010 Aalto University, ComNet
   * Released under GPLv3. See LICENSE.txt for details.
   */
  package movement.map;
  
  import java.io.Serializable;
  import java.util.ArrayList;
  import java.util.List;
  import java.util.Map;
  
  import core.Coord;
  
  /**
   * A simulation map for node movement.
   */
  public class SimMap implements Serializable {
  	private Coord minBound;
  	private Coord maxBound;
  	/** list representation of the map for efficient list-returning */
  	private ArrayList<MapNode> nodes;
  	/** hash map presentation of the map for efficient finding node by coord */
  	private Map<Coord, MapNode> nodesMap;
  	/** offset of map translations */
  	private Coord offset;
  	/** is this map data mirrored after reading */
  	private boolean isMirrored;
  
  	/** is re-hash needed before using hash mode (some coordinates changed) */
  	private boolean needsRehash = false;
  
  	public SimMap(Map<Coord, MapNode> nodes) {
  		this.offset = new Coord(0,0);
  		this.nodes = new ArrayList<MapNode>(nodes.values());
  		this.nodesMap = nodes;
  		this.isMirrored = false;
  		setBounds();
  	}
  
  	/**
  	 * Returns all the map nodes in a list
  	 * @return all the map nodes in a list
  	 */
  	public List<MapNode> getNodes() {
  		return this.nodes;
  	}
  
  	/**
  	 * Returns a MapNode at given coordinates or null if there's no MapNode
  	 * in the location of the coordinate
  	 * @param c The coordinate
  	 * @return The map node in that location or null if it doesn't exist
  	 */
  	public MapNode getNodeByCoord(Coord c) {
  		if (needsRehash) { // some coordinates have changed after creating hash
  			nodesMap.clear();
  			for (MapNode node : getNodes()) {
  				nodesMap.put(node.getLocation(), node); // re-hash
  			}
  		}
  
  		return nodesMap.get(c);
  	}
  
  	/**
  	 * Returns the upper left corner coordinate of the map
  	 * @return the upper left corner coordinate of the map
  	 */
  	public Coord getMinBound() {
  		return this.minBound;
  	}
  
  	/**
  	 * Returns the lower right corner coordinate of the map
  	 * @return the lower right corner coordinate of the map
  	 */
  	public Coord getMaxBound() {
  		return this.maxBound;
  	}
  
  	/**
  	 * Returns the offset that has been caused by translates made to
  	 * this map (does NOT take into account mirroring).
  	 * @return The current offset
  	 */
  	public Coord getOffset() {
  		return this.offset;
  	}
  
  	/**
  	 * Returns true if this map has been mirrored after reading
  	 * @return true if this map has been mirrored after reading
  	 * @see #mirror()
  	 */
  	public boolean isMirrored() {
  		return this.isMirrored;
  	}
  
  	/**
  	 * Translate whole map by dx and dy
  	 * @param dx The amount to translate X coordinates
  	 * @param dy the amount to translate Y coordinates
  	 */
  	public void translate(double dx, double dy) {
  		for (MapNode n : nodes) {
  			n.getLocation().translate(dx, dy);
  		}
  
  		minBound.translate(dx, dy);
  		maxBound.translate(dx, dy);
  		offset.translate(dx, dy);
  
  		needsRehash = true;
  	}
  
  	/**
  	 * Mirrors all map coordinates around X axis (x'=x, y'=-y).
  	 */
  	public void mirror() {
  		assert !isMirrored : "Map data already mirrored";
  
  		Coord c;
  		for (MapNode n : nodes) {
  			c=n.getLocation();
  			c.setLocation(c.getX(), -c.getY());
  		}
  		setBounds();
  		this.isMirrored = true;
  		needsRehash = true;
  	}
  
  	/**
  	 * Updates the min & max bounds to conform to the values of the map nodes.
  	 */
  	private void setBounds() {
  		double minX, minY, maxX, maxY;
  		Coord c;
  		minX = minY = Double.MAX_VALUE;
  		maxX = maxY = -Double.MAX_VALUE;
  
  		for (MapNode n : nodes) {
  			c = n.getLocation();
  			if (c.getX() < minX) {
  				minX = c.getX();
  			}
  			if (c.getX() > maxX) {
  				maxX = c.getX();
  			}
  			if (c.getY() < minY) {
  				minY = c.getY();
  			}
  			if (c.getY() > maxY) {
  				maxY = c.getY();
  			}
  		}
  		minBound = new Coord(minX, minY);
  		maxBound = new Coord(maxX, maxY);
  	}
  
  	/**
  	 * Returns a String representation of the map
  	 * @return a String representation of the map
  	 */
  	public String toString() {
  		return this.nodes.toString();
  	}
  }
  