package automatvgi.components;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import automatvgi.LatexColor;
import automatvgi.tools.Point;
import automatvgi.tools.Vector;
import road.Quotient;
import road.action.Flip;
import road.tools.Edge;

public class RoadAutomaton {
	private static final double CONST = 1.2;
	private static final int ECART = 20;
	private static final int VERTICAL = 5;
	private Automaton quo; // ensemble de composants automate pour le dessin du quotient
	private Quotient q;
	private List<List<Integer>> edgesMemory;
	private Point[] points;
	private Point[] centers;
	private static RoadAutomaton initialGraph; // 
	
	public RoadAutomaton (Quotient q) {
	   this.q = q;
	}
	
	public void updateEdgesMemory(){
		// edgesMemory conserve les transitions entre deux findStablePair
		edgesMemory = new ArrayList<List<Integer>>();
		for (int c = 0; c < q.getG().arity(); c++) {
			edgesMemory.add(new ArrayList<Integer>());
		}
		for (int c = 0; c < q.getG().arity(); c++) {
			for (int i= 0; i < q.getG().size(); i++) {
			  edgesMemory.get(c).add(q.getG().getEdges().get(c).get(i));
			}
		}
	}
	// points are recomputed 
	public void updateStateComponents(){
		   points = new Point[q.getG().size()];
		   centers = new Point[q.getG().size()];
		   computePoint();
		   quo = new Automaton(q.getG().size());
		   for (int x = 0; x < q.getG().size(); x++) {
			   if (q.getPartition().find(x) == x) {
				  quo.addStateComponent(points[x],new Integer(x).toString(),x);
			   }
		   }
	}
	public void updateTransitionComponents(){
		   quo.setLTC(new LinkedList<TransitionComponent>());
		   for (int x = 0; x < q.getG().size(); x++) {
			   if (q.getPartition().find(x) == x) {
				   for (int c = 0; c < q.getG().arity(); c++) {
					   char car = (char) ((char) 'a'+ c);
					   String l = new String()+car;
					   LatexColor lineColor;
					   if (c == 0) {
						   lineColor = LatexColor.getColorByName("Red");
					   } else {
						   lineColor = LatexColor.getColorByName("Blue");
					   }
					   TransitionComponent ac =  null;
					   if (edgeQMemory(x,c) != x) {
						   ac = new EdgeComponent(quo.getTab()[x],quo.getTab()[edgeQMemory(x,c)],l);
					   } else {
						   ac = new LoopComponent(quo.getTab()[x],l);
					   }
					   ac.setLineColor(lineColor);
					   quo.addTransitionComponent(ac);
				   }
			   }
			}
	}
	
	
	public void updateRoadAutomaton(){
	   //System.out.print("updateRoadAutomaton:");  System.out.println(q);
		updateEdgesMemory();
		updateStateComponents();
		updateTransitionComponents();
	}
	 

	public void doMemoryFlip(Flip flip) {
		Edge e1 = flip.getE1();		
		int x1 = e1.getX(); int b1 = e1.getY(); int y1 = e1.getZ();
		Edge e2 = flip.getE2();
		int b2 = e2.getY(); int y2 = e2.getZ();
		edgesMemory.get(b1).set(x1,y2);
		edgesMemory.get(b2).set(x1,y1);
	}
	
	
	public List<List<Integer>> getEdgesMemory() {
		return edgesMemory;
	}

	public int edgeMemory(int x, int c){
	    return edgesMemory.get(c).get(x);
	 }

    public int edgeQMemory(int x, int c) {
    	if (q.getPartition().find(x) != x) throw new IllegalArgumentException();
		return q.getPartition().find(edgeMemory(x,c));
    }
    
	public Automaton getQuo() {
		return quo;
	}

	public void setQuo(Automaton quo) {
		this.quo = quo;
	}


	public Quotient getQ() {
		return q;
	}

	public void setQ(Quotient q) {
		this.q = q;
	}


	
    ///////////////////// Q ////////////////////////////////////////////////
	// depth first search of the quotient which computes the data for each vertex
	// computes the points on the cycles
	// O(n)
    private void startComputePoint() {
        int n = q.getG().size();
        int clusterNumber= 0;
        int[] mark = new int[n];
        Deque<Integer> stack = new ArrayDeque<Integer>();
        for (int i = 0; i < n; i++) {
        	mark[i] = 0;
        }
        Point center = new Point(0,0);
        for (int i = 0; i < n; i++) {
        	int x = q.getPartition().find(i);
        	if (mark[x] == 0) {
        		clusterNumber++;
        		center.setX((clusterNumber-1)*ECART);
        		explore(x, mark, stack, clusterNumber,center);
        	}
	    }
    }	
	
	private void explore(int i, int[] mark, Deque<Integer> stack, int clusterNumber, Point center) {
		 //System.out.print("explore:"); System.out.println(i);
		 mark[i] = 1; 
		 stack.addFirst(i);
		 int j = q.edgeQuotient(i,0);
		 if (mark[j] == 0) {
			explore(j, mark, stack, clusterNumber,center);
			if (mark[i] != 3) {
			}
		 } else {
			 if (mark[j] == 1) {
				 int x = stack.getFirst(); // it is the node i
				 Deque<Integer> cycle = new ArrayDeque<Integer>();
				 while (x != j) {
					 cycle.add(x);
					 mark[x] = 3; 
					 stack.removeFirst();
					 x = stack.getFirst();
				 }
				 cycle.add(j);
				 mark[j] = 3; 
				 stack.removeFirst();
				 int n = cycle.size(); 
				 double radius = CONST * n; 
				 if (n == 1) radius = 0;
				 int k = 0;
				 Iterator<Integer> it = cycle.descendingIterator(); 
				 Point pc = new Point(center);
				 while (it.hasNext()) {
					 int r = it.next();
					 Point po = new Point();
					 po.setX(center.getX()+radius*Math.cos(Math.PI/2 - 2*Math.PI*k/n));
					 po.setY(center.getY()+radius*Math.sin(Math.PI/2 - 2*Math.PI*k/n));
					 points[r] =po; 
					 centers[r]= pc;
					 k++;
				 }
				 
			 } else { 
				// mark[j] = 2 or 3
				// root[i] <--- root[j] 
			 }
		 }
		 if (mark[i] != 3) {
			 mark[i] = 2; 
			 stack.removeFirst();
		 }
	 }
	
	
//////////////////////////////////////////////////////////////////////////////////
	
	private void computePoint() {
		startComputePoint();
    	int n = q.getG().size();
    	for (int i = 0; i < n; i++) {
    		if (q.getPartition().find(i) == i) {
    		   if (q.getData().get(i).getLevel() == 0) {
    			    computePoint(i);
    		   }
    		}
    	}	
    }
    
    private void computePoint(int r) {
    	int nb = q.getInv().edges(r,0).size()-1;
    	Point p1 = points[r];
    	Point p0 = centers[r]; // en fonction du cluster
    	Vector v = new Vector(p0,p1);
    	if (v.equals(new Vector(0,0))) {
    		v = new Vector(0,VERTICAL); 
    	}
    	Point q1 = p1.addTo(v);
    	v.rot(Math.PI/2);
    	//v.scal(0.5);
    	if (nb%2 == 1) {
    		 int n = (nb-1)/2;
    		 Vector w = new Vector(v);
    		 w.scal(n);
    	     q1 = q1.addTo(w);
    	} else {
    		int n = nb/2;
   		    Vector w = new Vector(v);
   		    w.scal(n-0.5);
   	        q1 = q1.addTo(w);
    	}
        v.scal(-1);
    	for (int s : q.getInv().edges(r,0)) {
    		if (s != q.getData().get(r).getPred()) {
    			     points[s] = q1; 
    				 computePoint(s,r);
    				 q1 = q1.addTo(v);
    				 
    		}
    	}
    }
    	
    private void computePoint(int s, int r) {
    	int nb = q.getInv().edges(s,0).size();
    	Point p1 = points[s];
    	Point p0 = points[r];
    	Vector v = new Vector(p0,p1);
    	Point q1 = p1.addTo(v); 
    	v.rot(Math.PI/2);
    	//v.scal(0.5);
    	if (nb%2 == 1) {
   		 int n = (nb-1)/2;
   		 Vector w = new Vector(v);
   		 w.scal(n);
   	     q1 = q1.addTo(w);
     	} else {
   		int n = nb/2;
  		    Vector w = new Vector(v);
  		    w.scal(n-0.5);
  	        q1 = q1.addTo(w);
     	}
        v.scal(-1);
        for (int x : q.getInv().edges(s,0)) {
    				 points[x] = q1;
    				 computePoint(x,s);
    				 q1= q1.addTo(v);	 
        }
    }

	public static void setInitialGraph(RoadAutomaton initialGraph) {
		RoadAutomaton.initialGraph = initialGraph;
	}

	public static RoadAutomaton getInitialGraph() {
		return initialGraph;
	}
}
