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

import java.util.Collection;
import core.Connection;
import core.NetworkInterface;
import core.Settings;
import core.VBRConnection;

/**
 * A Network Interface that that takes in to account he distance from the
 * other (connected) interface when determining the capacity of the links.
 * The distance-dependent transmission speeds are given as comma-separated
 * values using setting {@link #TRANSMIT_SPEEDS_S}.
 */
public class DistanceCapacityInterface extends NetworkInterface {

	/**
	 * Comma-separated list of speed values -setting id ({@value} ). The first
	 * value is the speed at distance 0 and the following are speeds at equal
	 * steps until the last one is the speed at the end of the transmit range (
	 * {@link NetworkInterface#TRANSMIT_RANGE_S}). The speed between the steps
	 * is linearly interpolated.
	 */
	public static final String TRANSMIT_SPEEDS_S = "transmitSpeeds";

	protected final int[] transmitSpeeds;

	/**
	 * Reads the interface settings from the Settings file
	 */
	public DistanceCapacityInterface(Settings s)	{
		super(s);
		transmitSpeeds = s.getCsvInts(TRANSMIT_SPEEDS_S);
	}

	/**
	 * Copy constructor
	 * @param ni the copied network interface object
	 */
	public DistanceCapacityInterface(DistanceCapacityInterface ni) {
		super(ni);
		transmitSpeeds = ni.transmitSpeeds;
	}

	public NetworkInterface replicate()	{
		return new DistanceCapacityInterface(this);
	}

	/**
	 * Tries to connect this host to another host. The other host must be
	 * active and within range of this host for the connection to succeed.
	 * @param anotherInterface The interface to connect to
	 */
	public void connect(NetworkInterface anotherInterface) {
		if (isScanning()
				&& anotherInterface.getHost().isRadioActive()
				&& isWithinRange(anotherInterface)
				&& !isConnected(anotherInterface)
				&& (this != anotherInterface)) {

			Connection con = new VBRConnection(this.host, this,
					anotherInterface.getHost(), anotherInterface);
			connect(con,anotherInterface);
		}
	}

	/**
	 * Updates the state of current connections (i.e. tears down connections
	 * that are out of range and creates new ones).
	 */
	public void update() {
		if (optimizer == null) {
			return; /* nothing to do */
		}

		// First break the old ones
		optimizer.updateLocation(this);
		for (int i=0; i<this.connections.size(); ) {
			Connection con = this.connections.get(i);
			NetworkInterface anotherInterface = con.getOtherInterface(this);

			// all connections should be up at this stage
			assert con.isUp() : "Connection " + con + " was down!";

			if (!isWithinRange(anotherInterface)) {
				disconnect(con,anotherInterface);
				connections.remove(i);
			}
			else {
				i++;
			}
		}
		// Then find new possible connections
		Collection<NetworkInterface> interfaces =
			optimizer.getNearInterfaces(this);
		for (NetworkInterface i : interfaces) {
			connect(i);
		}

		/* update all connections */
		for (Connection con : getConnections()) {
			con.update();
		}
	}

	/**
	 * Creates a connection to another host. This method does not do any checks
	 * on whether the other node is in range or active
	 * @param anotherInterface The interface to create the connection to
	 */
	public void createConnection(NetworkInterface anotherInterface) {
		if (!isConnected(anotherInterface) && (this != anotherInterface)) {
			Connection con = new VBRConnection(this.host, this,
					anotherInterface.getHost(), anotherInterface);
			connect(con,anotherInterface);
		}
	}

	/**
	 * Returns the transmit speed to another interface based on the
	 * distance to this interface
	 * @param ni The other network interface
	 */
	@Override
	public int getTransmitSpeed(NetworkInterface ni) {
		double distance;
		double fractionIndex;
		double decimal;
		double speed;
		int index;

		/* distance to the other interface */
		distance = ni.getLocation().distance(this.getLocation());

		if (distance >= this.transmitRange) {
			return 0;
		}

		/* interpolate between the two speeds */
		fractionIndex = (distance / this.transmitRange) *
				(this.transmitSpeeds.length - 1);
		index = (int)(fractionIndex);
		decimal = fractionIndex - index;

		speed = this.transmitSpeeds[index] * (1-decimal) +
				this.transmitSpeeds[index + 1] * decimal;

		return (int)speed;
	}

	/**
	 * Returns a string representation of the object.
	 * @return a string representation of the object.
	 */
	public String toString() {
		return "DistanceCapacityInterface " + super.toString();
	}

}
