content: /*
   * Copyright 2010 Aalto University, ComNet
   * Released under GPLv3. See LICENSE.txt for details.
   */
  package movement;
  
  import input.WKTReader;
  
  import java.io.File;
  import java.util.LinkedList;
  import java.util.List;
  
  import movement.map.DijkstraPathFinder;
  import movement.map.MapNode;
  import movement.map.SimMap;
  import core.Coord;
  import core.Settings;
  
  /**
   * A Class to model movement when people are out shopping or doing other
   * activities with friends. If the node happens to be at some other location
   * than the place where the shopping starts (where it meets its friends), it
   * first travels to the destination along the shortest path.
   *
   * @author Frans Ekman
   */
  public class EveningActivityMovement extends MapBasedMovement
  	implements SwitchableMovement {
  
  	private static final int WALKING_TO_MEETING_SPOT_MODE = 0;
  	private static final int EVENING_ACTIVITY_MODE = 1;
  
  	public static final String NR_OF_MEETING_SPOTS_SETTING = "nrOfMeetingSpots";
  	public static final String EVENING_ACTIVITY_CONTROL_SYSTEM_NR_SETTING =
  		"shoppingControlSystemNr";
  
  	public static final String MEETING_SPOTS_FILE_SETTING = "meetingSpotsFile";
  
  	public static final String MIN_GROUP_SIZE_SETTING = "minGroupSize";
  	public static final String MAX_GROUP_SIZE_SETTING = "maxGroupSize";
  
  	public static final String MIN_WAIT_TIME_SETTING =
  		"minAfterShoppingStopTime";
  	public static final String MAX_WAIT_TIME_SETTING =
  		"maxAfterShoppingStopTime";
  
  	private static int nrOfMeetingSpots = 10;
  
  	private int mode;
  	private boolean ready;
  	private DijkstraPathFinder pathFinder;
  
  	private Coord lastWaypoint;
  	private Coord startAtLocation;
  
  	private EveningActivityControlSystem scs;
  	private EveningTrip trip;
  
  	private boolean readyToShop;
  
  	private int id;
  
  	private static int nextID = 0;
  
  	private int minGroupSize;
  	private int maxGroupSize;
  
  	/**
  	 * Creates a new instance of EveningActivityMovement
  	 * @param settings
  	 */
  	public EveningActivityMovement(Settings settings) {
  		super(settings);
  		super.backAllowed = false;
  		pathFinder = new DijkstraPathFinder(null);
  		mode = WALKING_TO_MEETING_SPOT_MODE;
  
  		nrOfMeetingSpots = settings.getInt(NR_OF_MEETING_SPOTS_SETTING);
  
  		minGroupSize = settings.getInt(MIN_GROUP_SIZE_SETTING);
  		maxGroupSize = settings.getInt(MAX_GROUP_SIZE_SETTING);
  
  		MapNode[] mapNodes = (MapNode[])getMap().getNodes().
  			toArray(new MapNode[0]);
  
  		String shoppingSpotsFile = null;
  		try {
  			shoppingSpotsFile = settings.getSetting(MEETING_SPOTS_FILE_SETTING);
  		} catch (Throwable t) {
  			// Do nothing;
  		}
  
  		List<Coord> meetingSpotLocations = null;
  
  		if (shoppingSpotsFile == null) {
  			meetingSpotLocations = new LinkedList<Coord>();
  			for (int i=0; i<mapNodes.length; i++) {
  				if ((i % (mapNodes.length/nrOfMeetingSpots)) == 0) {
  					startAtLocation = mapNodes[i].getLocation().clone();
  					meetingSpotLocations.add(startAtLocation.clone());
  				}
  			}
  		} else {
  			try {
  				meetingSpotLocations = new LinkedList<Coord>();
  				List<Coord> locationsRead = (new WKTReader()).readPoints(
  						new File(shoppingSpotsFile));
  				for (Coord coord : locationsRead) {
  					SimMap map = getMap();
  					Coord offset = map.getOffset();
  					// mirror points if map data is mirrored
  					if (map.isMirrored()) {
  						coord.setLocation(coord.getX(), -coord.getY());
  					}
  					coord.translate(offset.getX(), offset.getY());
  					meetingSpotLocations.add(coord);
  				}
  			} catch (Exception e) {
  				e.printStackTrace();
  			}
  		}
  
  		this.id = nextID++;
  
  		int scsID = settings.getInt(EVENING_ACTIVITY_CONTROL_SYSTEM_NR_SETTING);
  
  		scs = EveningActivityControlSystem.getEveningActivityControlSystem(scsID);
  		scs.setRandomNumberGenerator(rng);
  		scs.addEveningActivityNode(this);
  		scs.setMeetingSpots(meetingSpotLocations);
  
  		maxPathLength = 100;
  		minPathLength = 10;
  
  		maxWaitTime = settings.getInt(MAX_WAIT_TIME_SETTING);
  		minWaitTime = settings.getInt(MIN_WAIT_TIME_SETTING);
  	}
  
  	/**
  	 * Creates a new instance of EveningActivityMovement from a prototype
  	 * @param proto
  	 */
  	public EveningActivityMovement(EveningActivityMovement proto) {
  		super(proto);
  		this.pathFinder = proto.pathFinder;
  		this.mode = proto.mode;
  		this.id = nextID++;
  		scs = proto.scs;
  		scs.addEveningActivityNode(this);
  		this.setMinGroupSize(proto.getMinGroupSize());
  		this.setMaxGroupSize(proto.getMaxGroupSize());
  	}
  
  	/**
  	 * @return Unique ID of the shopper
  	 */
  	public int getID() {
  		return this.id;
  	}
  
  	@Override
  	public Coord getInitialLocation() {
  
  		MapNode[] mapNodes = (MapNode[])getMap().getNodes().
  			toArray(new MapNode[0]);
  		int index = rng.nextInt(mapNodes.length - 1);
  		lastWaypoint = mapNodes[index].getLocation().clone();
  		return lastWaypoint.clone();
  	}
  
  	@Override
  	public Path getPath() {
  		if (mode == WALKING_TO_MEETING_SPOT_MODE) {
  			// Try to find to the shopping center
  			SimMap map = super.getMap();
  			if (map == null) {
  				return null;
  			}
  			MapNode thisNode = map.getNodeByCoord(lastWaypoint);
  			MapNode destinationNode = map.getNodeByCoord(startAtLocation);
  
  			List<MapNode> nodes = pathFinder.getShortestPath(thisNode,
  					destinationNode);
  			Path path = new Path(generateSpeed());
  			for (MapNode node : nodes) {
  				path.addWaypoint(node.getLocation());
  			}
  			lastWaypoint = startAtLocation.clone();
  			mode = EVENING_ACTIVITY_MODE;
  			return path;
  		} else if (mode == EVENING_ACTIVITY_MODE) {
  			readyToShop = true;
  			if (trip.allMembersPresent()) {
  				Path path = trip.getPath();
  				if (path == null) {
  					super.lastMapNode = super.getMap().
  						getNodeByCoord(lastWaypoint);
  					path = super.getPath(); // TODO Create levy walk path
  					lastWaypoint = super.lastMapNode.getLocation();
  					trip.setPath(path);
  					double waitTimeAtEnd = (maxWaitTime - minWaitTime) *
  						rng.nextDouble() + minWaitTime;
  					trip.setWaitTimeAtEnd(waitTimeAtEnd);
  					trip.setDestination(lastWaypoint);
  				}
  				lastWaypoint = trip.getDestination();
  				ready = true;
  				return path;
  			}
  		}
  
  		return null;
  	}
  
  	@Override
  	protected double generateWaitTime() {
  		if (ready) {
  			double wait = trip.getWaitTimeAtEnd();
  			return wait;
  		} else {
  			return 0;
  		}
  	}
  
  	@Override
  	public MapBasedMovement replicate() {
  		return new EveningActivityMovement(this);
  	}
  
  	/**
  	 * @see SwitchableMovement
  	 */
  	public Coord getLastLocation() {
  		return lastWaypoint.clone();
  	}
  
  	/**
  	 * @see SwitchableMovement
  	 */
  	public boolean isReady() {
  		return ready;
  	}
  
  	/**
  	 * @see SwitchableMovement
  	 */
  	public void setLocation(Coord lastWaypoint) {
  		this.lastWaypoint = lastWaypoint.clone();
  		ready = false;
  		mode = WALKING_TO_MEETING_SPOT_MODE;
  	}
  
  	/**
  	 * Sets the node ready to start a shopping trip.
  	 * @return The coordinate of the place where the shopping trip starts
  	 */
  	public Coord getShoppingLocationAndGetReady() {
  		readyToShop = false; // New shopping round starts
  		trip = scs.getEveningInstructions(id);
  		startAtLocation = trip.getLocation().clone();
  		return startAtLocation.clone();
  	}
  
  
  	public Coord getShoppingLocation() {
  		return scs.getMeetingSpotForID(id).clone();
  	}
  
  
  	/**
  	 * Checks if a node is at the correct place where the shopping begins
  	 * @return true if node is ready and waiting for the rest of the group to
  	 *  arrive
  	 */
  	public boolean isReadyToShop() {
  		return readyToShop;
  	}
  
  	public static void reset() {
  		nextID = 0;
  	}
  
  	public int getMinGroupSize() {
  		return minGroupSize;
  	}
  
  	public void setMinGroupSize(int minGroupSize) {
  		this.minGroupSize = minGroupSize;
  	}
  
  	public int getMaxGroupSize() {
  		return maxGroupSize;
  	}
  
  	public void setMaxGroupSize(int maxGroupSize) {
  		this.maxGroupSize = maxGroupSize;
  	}
  
  }
  