package de.tud.kom.p2psim.impl.topology.placement;

import java.util.ArrayList;
import java.util.Random;

import de.tud.kom.p2psim.api.topology.TopologyComponent;
import de.tud.kom.p2psim.impl.topology.util.PositionVector;
import de.tudarmstadt.maki.simonstrator.api.Randoms;
import de.tudarmstadt.maki.simonstrator.api.util.XMLConfigurableConstructor;

public class CustomRandomPositionDistribution extends PositionDistribution {

	Random rnd = Randoms.getRandom(RandomPositionDistribution.class);

	double maxDist = 0;

	double minRange = 50;

	long sinkId;

	boolean sinkCenter = false;

	int maxNeighbors = 0;

	TopologyComponent currentComp;

	ArrayList<PositionVector> positions = new ArrayList<PositionVector>();

	@XMLConfigurableConstructor({ "sinkId", "sinkCenter", "maxDist", "minDist",
			"maxNeighbors" })
	public CustomRandomPositionDistribution(long sinkId, boolean sinkCenter,
			double maxDist, double minDist, int maxNeighbors) {
		this.sinkId = sinkId;
		this.sinkCenter = sinkCenter;
		this.minRange = minDist;
		this.maxDist = maxDist - minRange;
		this.maxNeighbors = maxNeighbors;
	}

	@Override
	public void addComponent(TopologyComponent comp) {
		currentComp = comp;
		super.addComponent(comp);
	}

	@Override
	public PositionVector getNextPosition() {
		// find the maximum range between this node and another node
		// this code leads to Infinity; set manually instead
		// if (maxDist == 0) {
		// try {
		// maxDist = ((WifiTopologyView) Binder
		// .getComponent(Topology.class)
		// .getTopologyView(PhyType.WIFI)).getRange() - minRange;
		// } catch (ComponentNotAvailableException e) {
		// throw new AssertionError(
		// "Cannot get maximum range because Topology was not found!");
		// }
		// // System.out.println("Maximum distance is: " + maxDist);
		// // maxDist = 200 - minRange;
		// }

		// sink placement
		if (currentComp.getHost().getId().value() == sinkId) {
			PositionVector pos = currentComp.getTopology().getWorldDimensions()
					.clone();
			if (sinkCenter) {
				// places sink in center
				pos.multiplyScalar(0.5);
			} else {
				// places sink in corner
				pos.setLongitudeOrX(100);
				pos.setLatitudeOrY(100);
			}
			positions.add(pos);
			return pos;
		} else {
			// place all other nodes within range of at least one other node
			double[] vec = new double[getDimensions()];
			PositionVector position;
			boolean isValidPosition = true;
			do {
				isValidPosition = true;
				double angle = Math.toRadians(rnd.nextInt(360));
				double distance = minRange + rnd.nextInt((int) maxDist);
				// choose one random other node
				// try to place new node within its range
				PositionVector chosenPos = positions
						.get(rnd.nextInt(positions.size()));

				for (int i = 0; i < getDimensions(); i++) {
					switch (i) {
					case 0:
						vec[i] = chosenPos.getX() + Math.cos(angle) * distance;
						if (vec[i] < 0
								|| vec[i] > getWorldDimensions().getX()) {
							isValidPosition = false;
							continue;
						}
						break;

					case 1:
						vec[i] = chosenPos.getY() + Math.sin(angle) * distance;
						if (vec[i] < 0
								|| vec[i] > getWorldDimensions().getY()) {
							isValidPosition = false;
							continue;
						}
						break;

					default:
						vec[i] = 0;
					}
				}

				position = new PositionVector(vec);
				// check if new node is not too close to other nodes and does
				// not have too many neighbors
				int neighbors = 0;
				for (PositionVector v : positions) {
					if (position.distanceTo(v) < minRange) {
						isValidPosition = false;
					}
					if (position.distanceTo(v) < minRange + maxDist) {
						neighbors++;
					}
					// if maxNeighbores is 1 or 0, ignore neighbor count
					if (maxNeighbors > 1 && neighbors > maxNeighbors) {
						isValidPosition = false;
					}
				}
			} while (!isValidPosition);

			positions.add(position);
			return position;
		}
	}

}
