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 util.ParetoRNG;
  
  import movement.map.DijkstraPathFinder;
  import movement.map.MapNode;
  import movement.map.SimMap;
  import core.Coord;
  import core.Settings;
  import core.SimClock;
  
  /**
   * This class models movement at an office. If the node happens to be at some
   * other location than the office, it first walks the shortest path to the
   * office and then stays there until the end of the work day. A node has only
   * works at one office.
   *
   * @author Frans Ekman
   *
   */
  public class OfficeActivityMovement extends MapBasedMovement implements
  	SwitchableMovement {
  
  	private static final int WALKING_TO_OFFICE_MODE = 0;
  	private static final int AT_OFFICE_MODE = 1;
  
  	public static final String WORK_DAY_LENGTH_SETTING = "workDayLength";
  	public static final String NR_OF_OFFICES_SETTING = "nrOfOffices";
  
  	public static final String OFFICE_SIZE_SETTING = "officeSize";
  	public static final String OFFICE_WAIT_TIME_PARETO_COEFF_SETTING =
  		"officeWaitTimeParetoCoeff";
  	public static final String OFFICE_MIN_WAIT_TIME_SETTING =
  		"officeMinWaitTime";
  	public static final String OFFICE_MAX_WAIT_TIME_SETTING =
  		"officeMaxWaitTime";
  	public static final String OFFICE_LOCATIONS_FILE_SETTING =
  		"officeLocationsFile";
  
  	private static int nrOfOffices = 50;
  
  	private int mode;
  	private int workDayLength;
  	private int startedWorkingTime;
  	private boolean ready;;
  	private DijkstraPathFinder pathFinder;
  
  	private ParetoRNG paretoRNG;
  
  	private int distance;
  	private double officeWaitTimeParetoCoeff;
  	private double officeMinWaitTime;
  	private double officeMaxWaitTime;
  
  	private List<Coord> allOffices;
  
  	private Coord lastWaypoint;
  	private Coord officeLocation;
  	private Coord deskLocation;
  
  	private boolean sittingAtDesk;
  
  	/**
  	 * OfficeActivityMovement constructor
  	 * @param settings
  	 */
  	public OfficeActivityMovement(Settings settings) {
  		super(settings);
  
  		workDayLength = settings.getInt(WORK_DAY_LENGTH_SETTING);
  		nrOfOffices = settings.getInt(NR_OF_OFFICES_SETTING);
  
  		distance = settings.getInt(OFFICE_SIZE_SETTING);
  		officeWaitTimeParetoCoeff = settings.getDouble(
  				OFFICE_WAIT_TIME_PARETO_COEFF_SETTING);
  		officeMinWaitTime = settings.getDouble(OFFICE_MIN_WAIT_TIME_SETTING);
  		officeMaxWaitTime = settings.getDouble(OFFICE_MAX_WAIT_TIME_SETTING);
  
  		startedWorkingTime = -1;
  		pathFinder = new DijkstraPathFinder(null);
  		mode = WALKING_TO_OFFICE_MODE;
  
  		String officeLocationsFile = null;
  		try {
  			officeLocationsFile = settings.getSetting(
  					OFFICE_LOCATIONS_FILE_SETTING);
  		} catch (Throwable t) {
  			// Do nothing;
  		}
  
  		if (officeLocationsFile == null) {
  			MapNode[] mapNodes = (MapNode[])getMap().getNodes().
  				toArray(new MapNode[0]);
  			int officeIndex = rng.nextInt(mapNodes.length - 1) /
  				(mapNodes.length/nrOfOffices);
  			officeLocation = mapNodes[officeIndex].getLocation().clone();
  		} else {
  			try {
  				allOffices = new LinkedList<Coord>();
  				List<Coord> locationsRead = (new WKTReader()).
  					readPoints(new File(officeLocationsFile));
  				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());
  					allOffices.add(coord);
  				}
  				officeLocation = allOffices.get(
  						rng.nextInt(allOffices.size())).clone();
  			} catch (Exception e) {
  				e.printStackTrace();
  			}
  		}
  
  		deskLocation = getRandomCoorinateInsideOffice();
  		paretoRNG = new ParetoRNG(rng, officeWaitTimeParetoCoeff,
  				officeMinWaitTime, officeMaxWaitTime);
  	}
  
  	/**
  	 * Copyconstructor
  	 * @param proto
  	 */
  	public OfficeActivityMovement(OfficeActivityMovement proto) {
  		super(proto);
  		this.workDayLength = proto.workDayLength;
  		startedWorkingTime = -1;
  		this.distance = proto.distance;
  		this.pathFinder = proto.pathFinder;
  		this.mode = proto.mode;
  
  		if (proto.allOffices == null) {
  			MapNode[] mapNodes = (MapNode[])getMap().getNodes().
  				toArray(new MapNode[0]);
  			int officeIndex = rng.nextInt(mapNodes.length - 1) /
  				(mapNodes.length/nrOfOffices);
  			officeLocation = mapNodes[officeIndex].getLocation().clone();
  		} else {
  			this.allOffices = proto.allOffices;
  			officeLocation = allOffices.get(
  					rng.nextInt(allOffices.size())).clone();
  		}
  
  		officeWaitTimeParetoCoeff = proto.officeWaitTimeParetoCoeff;
  		officeMinWaitTime = proto.officeMinWaitTime;
  		officeMaxWaitTime = proto.officeMaxWaitTime;
  
  		deskLocation = getRandomCoorinateInsideOffice();
  		this.paretoRNG = proto.paretoRNG;
  	}
  
  	public Coord getRandomCoorinateInsideOffice() {
  		double x_coord = officeLocation.getX() +
  			(0.5 - rng.nextDouble()) * distance;
  		if (x_coord > getMaxX()) {
  			x_coord = getMaxX();
  		} else if (x_coord < 0) {
  			x_coord = 0;
  		}
  		double y_coord = officeLocation.getY() +
  			(0.5 - rng.nextDouble()) * distance;
  		if (y_coord > getMaxY()) {
  			y_coord = getMaxY();
  		} else if (y_coord < 0) {
  			y_coord = 0;
  		}
  		return new Coord(x_coord, y_coord);
  	}
  
  	@Override
  	public Coord getInitialLocation() {
  		double x = rng.nextDouble() * getMaxX();
  		double y = rng.nextDouble() * getMaxY();
  		Coord c = new Coord(x,y);
  
  		this.lastWaypoint = c;
  		return c.clone();
  	}
  
  	@Override
  	public Path getPath() {
  		if (mode == WALKING_TO_OFFICE_MODE) {
  			// Try to find to the office
  			SimMap map = super.getMap();
  			if (map == null) {
  				return null;
  			}
  			MapNode thisNode = map.getNodeByCoord(lastWaypoint);
  			MapNode destinationNode = map.getNodeByCoord(officeLocation);
  			List<MapNode> nodes = pathFinder.getShortestPath(thisNode,
  					destinationNode);
  			Path path = new Path(generateSpeed());
  			for (MapNode node : nodes) {
  				path.addWaypoint(node.getLocation());
  			}
  			lastWaypoint = officeLocation.clone();
  			mode = AT_OFFICE_MODE;
  			return path;
  		}
  
  		if (startedWorkingTime == -1) {
  			startedWorkingTime = SimClock.getIntTime();
  		}
  		if (SimClock.getIntTime() - startedWorkingTime >= workDayLength) {
  			Path path =  new Path(1);
  			path.addWaypoint(lastWaypoint.clone());
  			ready = true;
  			return path;
  		}
  		Coord c;
  		if (sittingAtDesk) {
  			c = getRandomCoorinateInsideOffice();
  			sittingAtDesk = false;
  		} else {
  			c = deskLocation.clone();
  			sittingAtDesk = true;
  		}
  
  		Path path =  new Path(1);
  		path.addWaypoint(c);
  		return path;
  	}
  
  	@Override
  	protected double generateWaitTime() {
  		int timeLeft = workDayLength -
  			(SimClock.getIntTime() - startedWorkingTime);
  
  		int waitTime = (int)paretoRNG.getDouble();
  		if (waitTime > timeLeft) {
  			return timeLeft;
  		}
  		return waitTime;
  	}
  
  	@Override
  	public MapBasedMovement replicate() {
  		return new OfficeActivityMovement(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();
  		startedWorkingTime = -1;
  		ready = false;
  		mode = WALKING_TO_OFFICE_MODE;
  	}
  
  	/**
  	 * @return The location of the office
  	 */
  	public Coord getOfficeLocation() {
  		return officeLocation.clone();
  	}
  
  }
  