/*
 * Copyright 2010 Aalto University, ComNet
 * Released under GPLv3. See LICENSE.txt for details.
 */
package movement;

import core.Coord;
import core.Settings;
import movement.MovementModel;
import movement.Path;

/**
 * Movement model where all nodes move on a line
 * (work in progress)
 */
public class LinearMovement extends MovementModel {
	/** Name space of the settings (append to group name space) */
	public static final String LINEAR_MOVEMENT_NS = "LinearMovement.";
	/** Per node group setting for defining the start coordinates of
	 * the line ({@value}) */
	public static final String START_LOCATION_S = "startLocation";
	/** Per node group setting for defining the end coordinates of
	 * the line ({@value}) */
	public static final String END_LOCATION_S = "endLocation";

	/**
	 * Nodes' initial location type
	 * <ul><li>0: random (evenly distributed)
	 * <li>1: evenly spaced
	 * </ul>
	  */
	public static final String INIT_LOC_S = "initLocType";
	/**
	 * Nodes' target (where they're heading) type
	 * <ul><li>0: random point on the line
	 * <li>1: far-end of the line
	 * </ul>
	  */
	public static final String TARGET_S = "targetType";

	/* values for the prototype */
	private Coord startLoc; /** The start location of the line */
	private Coord endLoc; /** The start location of the line */
	private int initLocType;
	private int targetType;
	private int nodeCount; /** how many nodes in this formation */
	private int lastIndex; /** index of the previous node */

	/* values for the per-node models */
	private Path nextPath;
	private Coord initLoc;

	/**
	 * Creates a new movement model based on a Settings object's settings.
	 * @param s The Settings object where the settings are read from
	 */
	public LinearMovement(Settings s) {
		super(s);
		int coords[];

		coords = s.getCsvInts(LINEAR_MOVEMENT_NS + START_LOCATION_S, 2);
		this.startLoc = new Coord(coords[0], coords[1]);
		coords = s.getCsvInts(LINEAR_MOVEMENT_NS + END_LOCATION_S, 2);
		this.endLoc = new Coord(coords[0], coords[1]);
		this.initLocType = s.getInt(LINEAR_MOVEMENT_NS + INIT_LOC_S);
		this.targetType = s.getInt(LINEAR_MOVEMENT_NS + TARGET_S);
		this.nodeCount = s.getInt(core.SimScenario.NROF_HOSTS_S);

		this.lastIndex = 0;
	}

	/**
	 * Copy constructor.
	 * @param ilm The LinearFormation prototype
	 */
	public LinearMovement(LinearMovement ilm) {
		super(ilm);
		this.initLoc = calculateLocation(ilm, (ilm.initLocType == 1));
		this.nextPath = new Path(generateSpeed());
		this.nextPath.addWaypoint(initLoc);

		if (ilm.targetType == 0) { /* random target */
			this.nextPath.addWaypoint(calculateLocation(ilm, true));
		} else {
			this.nextPath.addWaypoint(calculateEndTarget(ilm, initLoc));
		}

		ilm.lastIndex++;
	}

	/**
	 * Calculates and returns a location in the line
	 * @param proto The movement model prototype
	 * @param isEven Is the distribution evenly spaced (false for random)
	 * @return a location on the line
	 */
	private Coord calculateLocation(LinearMovement proto, boolean isEven) {
		double dx = 0;
		double dy = 0;
		double placementFraction;

		double xDiff = (proto.endLoc.getX() -  proto.startLoc.getX());
		double yDiff = (proto.endLoc.getY() -  proto.startLoc.getY());
		Coord c = proto.startLoc.clone();

		if (isEven) {
			placementFraction = (1.0 * proto.lastIndex / proto.nodeCount);
			dx = placementFraction * xDiff;
			dy = placementFraction * yDiff;
		} else { /* random */
			dx = rng.nextDouble() * xDiff;
			dy = rng.nextDouble() * yDiff;
		}

		c.translate(dx, dy);
		return c;
	}

	/**
	 * Calculates and returns the far-end of the line
	 * @param proto The movement model prototype
	 * @param initLoc The initial location
	 * @return the coordinates for the far-end of the line
	 */
	private Coord calculateEndTarget(LinearMovement proto, Coord initLoc) {
		return (proto.startLoc.distance(initLoc) >
			proto.endLoc.distance(initLoc) ? proto.startLoc: proto.endLoc);
	}

	/**
	 * Returns the the location of the node in the formation
	 * @return the the location of the node in the formation
	 */
	@Override
	public Coord getInitialLocation() {
		return this.initLoc;
	}

	/**
	 * Returns a single coordinate path (using the only possible coordinate)
	 * @return a single coordinate path
	 */
	@Override
	public Path getPath() {
		Path p = nextPath;
		this.nextPath = null;
		return p;
	}

	/**
	 * Returns Double.MAX_VALUE (no paths available)
	 */
	@Override
	public double nextPathAvailable() {
		if (nextPath == null) {
			return Double.MAX_VALUE;	// no new paths available
		} else {
			return 0;
		}
	}

	@Override
	public int getMaxX() {
		return (int)(endLoc.getX() > startLoc.getX() ? endLoc.getX() :
			startLoc.getX());
	}

	@Override
	public int getMaxY() {
		return (int)(endLoc.getY() > startLoc.getY() ? endLoc.getY() :
			startLoc.getY());
	}


	@Override
	public LinearMovement replicate() {
		return new LinearMovement(this);
	}

}
