/*
 * Copyright (c) 2005-2010 KOM – Multimedia Communications Lab
 *
 * This file is part of PeerfactSim.KOM.
 * 
 * PeerfactSim.KOM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 * 
 * PeerfactSim.KOM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with PeerfactSim.KOM.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

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

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Stack;
import java.util.Vector;

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

/**
 * Generates random 2D PositionVectors, where distance from center and angle are
 * uniformly distributed. Tests result for connectivity, under the assumption
 * that nodes have a range of 200 meters
 * 
 * @author Benjamin Becker
 * @version 1.0, 27/10/2021
 */
public class RandomPositionDistributionCircle implements PlacementModel {

	private int numberOfComponents = 0;

	private int placedComponents = 0;

	private PositionVector worldDimensions = null;

	/**
	 * Allows us to create a "larger" grid, even if we only have a smaller
	 * number of nodes. Eg., create a 3*3 grid for only 2 nodes
	 */
	private int fakeNumberOfComponents = -1;

	private Random random = Randoms
			.getRandom(RandomPositionDistributionCircle.class);

	private List<PositionVector> positions = new Vector<PositionVector>();

	@Override
	public void addComponent(TopologyComponent comp) {
		if (worldDimensions == null) {
			worldDimensions = comp.getTopology().getWorldDimensions();
		}
		numberOfComponents++;
	}

	@Override
	public PositionVector place(TopologyComponent comp) {
		if (positions.isEmpty()) {
			calcPositions3D();
		}

		PositionVector pos = positions.get(placedComponents);
		placedComponents = (placedComponents + 1) % numberOfComponents;
		return pos;
	}

	public void setFakeNumberOfComponents(int fakeNumberOfComponents) {
		this.fakeNumberOfComponents = fakeNumberOfComponents;
	}

	private void calcPositions3D() {
		if (fakeNumberOfComponents != -1) {
			numberOfComponents = fakeNumberOfComponents;
		}

		while (true) {
			for (int i = 0; i < numberOfComponents; i++)
				positions.add(getNextPosition(i));

			Graph g = new Graph(positions);
			if (g.isConnected()) {
				return;
			}
			positions = new Vector<PositionVector>();
		}

	}

	public PositionVector getNextPosition(int i) {
		double[] vec = new double[3];
		int minDim = Integer.MAX_VALUE;
		minDim = Math.min((int) worldDimensions.getX(), minDim);
		minDim = Math.min((int) worldDimensions.getY(), minDim);

		double angle = Math.PI * random.nextDouble() * 2.;
		
		
		double dist = random.nextDouble() * minDim / 2;

		if (i == 0) {
			//angle=0;
			dist = minDim * .4;
		}

		vec[0] = minDim / 2 + dist * Math.cos(angle);
		vec[1] = minDim / 2 + dist * Math.sin(angle);

		PositionVector position = new PositionVector(vec);
		return position;
	}

}

class Graph {
	ArrayList<Node> nodes = new ArrayList<Node>();

	Graph(List<PositionVector> positions) {
		for (PositionVector pos : positions) {
			nodes.add(new Node(pos.getX(), pos.getY()));
		}
		for (Node u : nodes) {
			for (Node v : nodes) {
				if (isNeighbor(u, v)) {
					u.neighbors.add(v);
				}
			}
		}
	}

	boolean isConnected() {
		Stack<Node> toProcess = new Stack<Node>();
		toProcess.add(nodes.get(0));
		nodes.get(0).processed = true;

		while (!toProcess.isEmpty()) {
			Node next = toProcess.pop();
			for (Node v : next.neighbors) {
				if (!v.processed) {
					v.processed = true;
					toProcess.add(v);
				}
			}
		}

		for (Node u : nodes) {
			if (!u.processed) {
				return false;
			}
		}

		return true;
	}

	boolean isNeighbor(Node u, Node v) {
		return (u.posx - v.posx) * (u.posx - v.posx)
				+ (u.posy - v.posy) * (u.posy - v.posy) < 200 * 200;
	}
}

class Node {
	double posx, posy;

	ArrayList<Node> neighbors;

	boolean processed = false;

	Node(double x, double y) {
		posx = x;
		posy = y;
		neighbors = new ArrayList<Node>();
	}

}
