package drawing;

import java.util.Vector;

import drawing.Diagram;

/**
 * This class represents a graph drawing algorithm that uses the spring energy
 * @author MC
 *
 */
public class GraphSpringLayout extends Graph{

	/**
	 * D(i,j) is the minimum diatance between vertices i and j
	 */
	private double[][] D;
	/**
	 * forceX(i.j) is the x coordinate of the force applies to the vertice i from j
	 */
	private double[] forceX;
	/**
	 * forceX(i.j) is the y coordinate of the force applies to the vertice i from j
	 */
	private double[] forceY;
	/**
	 * the spring straightness 
	 */
	public static  double k = 1;
	/**
	 * attraction force capacity
	 */
	public static double c = 0.05;
	/**
	 * max repulsive force distance
	 */
	double maxRepulsiveForceDistance = 150;
	/**
	 * the maximum distance whilw moving a vertice
	 */
	static final double maxVertexMvt = 100; 
	/**
	 * the maximum number of iteration of the algorithm
	 */
	static int maxIteration = 100;
	/**
	 * constructor
	 * @param dia
	 */
	public GraphSpringLayout(Diagram dia){
		super(dia);
		D =  new double[Vertices.size()][Vertices.size()];
		forceX =  new double[Vertices.size()];
		forceY =  new double[Vertices.size()];
		//maxRepulsiveForceDistance = Vertices.size() * 10;
	}
	/**
	 * constructor
	 * @param v
	 * @param e
	 */
	public GraphSpringLayout(Vector<Vertice> v, Vector<Edge> e){
		super(v,e);
		D =  new double[Vertices.size()][Vertices.size()];
		forceX =  new double[Vertices.size()];
		forceY =  new double[Vertices.size()];
		
	}
	/**
	 * sets the number of maximum iterations in the algorithm
	 * @param max
	 */
	public static void setMaxIter(int max){
		maxIteration = max;
	}
	/**
	 * @see Graph#ComputeCoordinates()
	 */
	public double[][] ComputeCoordinates() {
		double XY[][] = new double [Vertices.size()][2];
		initDF();
		
		for(int i = 0; i < maxIteration; i++){
			iterationXY();
		}
		
		for(int i = 0; i < Vertices.size(); i++){
			Vertice vi = (Vertice) Vertices.get(i);
			XY[i][0] = vi.x;
			XY[i][1] = vi.y;
		}
		return XY;
	}
	/**
	 * computes only the vertices x coordinate
	 * @return
	 */
	public double[][] ComputeXCoordinates() {
		double XY[][] = new double [Vertices.size()][2];
		initDF();
		
		for(int i = 0; i < maxIteration; i++){
			iterationX();
		}
		
		for(int i = 0; i < Vertices.size(); i++){
			Vertice vi = (Vertice) Vertices.get(i);
			XY[i][0] = vi.x;
			XY[i][1] = vi.y;
		}
		return XY;
	}
	/**
	 * initializes the matrix D, forceX and forceY
	 *
	 */
	private void initDF(){
		Vertice vi;
		Vertice vj;
		double d;
		
		for(int i = 0; i < Vertices.size(); i++){
			vi = (Vertice) Vertices.get(i);
			forceX[i] = 0;
			forceY[i] = 0;
			for(int j = 0; j < Vertices.size(); j++){
				vj = (Vertice) Vertices.get(j);
				d = Math.max(vj.width*vj.width + vj.height*vj.height, vi.width*vi.width + vi.height*vi.height);
				d  = Math.sqrt(d);
				D[i][j] = d;
				
			}
		}
	}
	/**
	 * computes the repulsive forces applies by v1 on v2
	 * @param v1
	 * @param v2
	 */
	private void computeRepulsiveForce(Vertice v1, Vertice v2){
		double dx = v2.x - v1.x;
		double dy = v2.y - v1.y;
		int i1 = Vertices.indexOf(v1);
		int i2 = Vertices.indexOf(v2);
		
		double d2 = dx*dx + dy*dy;
		double dmin = D[i1][i2] + dxMin;
		if(d2 < dmin){
			dx = Math.random()%100 + Math.sqrt(dmin);
			dy = Math.random()%100 + Math.sqrt(dmin);
			d2 = dx*dx + dy*dy;
		}
		
		double d = Math.sqrt(d2);
		if(d < maxRepulsiveForceDistance){
			double force = D[i1][i2]*D[i1][i2]/d;
			forceX[i2] += force*dx/d;
			forceY[i2] += force*dy/d;
			forceX[i1] -= force*dx/d;
			forceY[i1] -= force*dy/d;
		}
	}
	/**
	 * computes the attractive force applies by v1 on v2
	 * @param v1
	 * @param v2
	 */
	private void computeAttractiveForce(Vertice v1, Vertice v2){
		double dx = v2.x - v1.x;
		double dy = v2.y - v1.y;
		int i1 = Vertices.indexOf(v1);
		int i2 = Vertices.indexOf(v2);
		
		double d2 = dx*dx + dy*dy;
		if(d2 < D[i1][i2]){
			dx = Math.random()%100 + Math.sqrt(D[i1][i2]);
			dy = Math.random()%100 + Math.sqrt(D[i1][i2]);
			d2 = dx*dx + dy*dy;
		}
		
		double d = Math.sqrt(d2);
		if(d > maxRepulsiveForceDistance){
			d = maxRepulsiveForceDistance;
			d2 = d*d;
		}
		double force = (d2 - k*k)/d;
		
		forceX[i2] -= force *dx/d;
		forceY[i2] -= force *dy/d;
		forceX[i1] += force *dx/d;
		forceY[i1] += force *dy/d;
	}
	/**
	 * represents an iteration of the algorithm that computes the new vertices coordinates
	 *
	 */
	private void iterationXY(){
		Vertice vi;
		Vertice vj;
		
		for(int i = 0; i < Vertices.size(); i++){
			vi = (Vertice) Vertices.get(i);
			for(int j = 0; j < Vertices.size(); j++){
				vj = (Vertice) Vertices.get(j);
				computeRepulsiveForce(vi, vj);
			}
		}
		
		Edge e;
		for(int i = 0; i < Edges.size(); i++){
			e = (Edge) Edges.get(i);
			computeAttractiveForce(e.start, e.end);
		}
		
		for(int i = 0; i < Vertices.size(); i++){
			double xMove = forceX[i] * c;
			double yMove = forceY[i] * c;
			if(xMove > maxVertexMvt)
				xMove = maxVertexMvt;
			else if(xMove < - maxVertexMvt)
				xMove = - maxVertexMvt;
			
			if(yMove > maxVertexMvt)
				yMove = maxVertexMvt;
			else if(yMove < - maxVertexMvt)
				yMove = - maxVertexMvt;
			
			vi = (Vertice) Vertices.get(i);
			vi.x += xMove;
			vi.y += yMove;
			
			forceX[i] = 0;
			forceY[i] = 0;
		}
	}
	/**
	 * represents an iteration of the algorithm that computes the only the vertices x coordinates
	 *
	 */
	private void iterationX(){
		Vertice vi;
		Vertice vj;
		
		for(int i = 0; i < Vertices.size(); i++){
			vi = (Vertice) Vertices.get(i);
			for(int j = 0; j < Vertices.size(); j++){
				vj = (Vertice) Vertices.get(j);
				computeRepulsiveForce(vi, vj);
			}
		}
		
		Edge e;
		for(int i = 0; i < Edges.size(); i++){
			e = (Edge) Edges.get(i);
			computeAttractiveForce(e.start, e.end);
		}
		
		for(int i = 0; i < Vertices.size(); i++){
			double xMove = forceX[i] * c;
			double yMove = forceY[i] * c;
			if(xMove > maxVertexMvt)
				xMove = maxVertexMvt;
			else if(xMove < - maxVertexMvt)
				xMove = - maxVertexMvt;
			
			if(yMove > maxVertexMvt)
				yMove = maxVertexMvt;
			else if(yMove < - maxVertexMvt)
				yMove = - maxVertexMvt;
			
			vi = (Vertice) Vertices.get(i);
			vi.x += xMove + yMove*0.1;
			//vi.y += yMove;
			
			forceX[i] = 0;
			forceY[i] = 0;
		}
	}

	
}
