package weka.clusterers.forMetisMQI;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.Stack;

import weka.clusterers.forMetisMQI.graph.Bisection;
import weka.clusterers.forMetisMQI.graph.Node;
import weka.clusterers.forMetisMQI.graph.Subgraph;
import weka.clusterers.forMetisMQI.graph.UndirectedGraph;
import weka.clusterers.forMetisMQI.util.CoarserGraphElement;
import weka.clusterers.forMetisMQI.util.Util;

public class GraphAlgorithms {

	
	static public Bisection KLRefinement(Bisection b) {
		int remainingNumberOfSwap = 50;
		Bisection partition = b;
		Bisection result = partition;
		int bestEdgeCut = partition.edgeCut();
		Node u = partition.getCandidate();
		while (u != null && remainingNumberOfSwap > 0) {
			partition.swap(u);
			if (partition.edgeCut() <= bestEdgeCut) {
				bestEdgeCut = partition.edgeCut();
				result = partition.clone();
				remainingNumberOfSwap = 50;
			} else
				remainingNumberOfSwap--;
			u = partition.getCandidate();
		}
		return result;
	}
	
	
	/**
	 * Given an undirected graph, performs the Kernighan-Li algorithm to find a bisection and
	 * then returns it.
	 * @param g
	 * @return
	 */
	static public Bisection KL(UndirectedGraph g) {
		int remainingNumberOfSwap = 50;
		Bisection partition = new Bisection(g);
		Bisection result = partition;
		int bestEdgeCut = partition.edgeCut();
		Node u = partition.getCandidate();
		while (u != null && remainingNumberOfSwap > 0) {
			partition.swap(u);
			if (partition.edgeCut() <= bestEdgeCut) {
				bestEdgeCut = partition.edgeCut();
				result = partition.clone();
				remainingNumberOfSwap = 50;
			} else
				remainingNumberOfSwap--;
			u = partition.getCandidate();
		}
		return result;
	}
	
	static public Bisection metis(UndirectedGraph g, int sizeFinerGraph) {
		Coarse.setFinerSize(sizeFinerGraph);
		Stack<CoarserGraphElement> stack = Coarse.coarse(g);
		Bisection partition = null;
		if (stack.size() > 0) {
			partition = KL(stack.peek().getContracted());
			partition = Uncoarse.uncoarse(stack, partition);
		}
		return partition;
	}

	/**
	 * Given an UndirectedGraph, runs metis+mqi for <code>numberOfCluster</code> times and
	 * returns a set of clusters. With the third parameter you can control the maximum size of the finer
	 * graph during the coarsening phase.  
	 * @param g
	 * @param numberOfCluster
	 * @param sizeFinerGraph
	 */
	static public Set<Set<Node>> metisMqi(UndirectedGraph g, int numberOfCluster, int sizeFinerGraph) {
		System.out.println("Vertex count: " + g.getVertexCount());
		System.out.println("Edges count: " + g.getEdgeCount());
		Iterator<Node> iNodes = g.getVertices().iterator();
		int degreeCounter = 0;
		while(iNodes.hasNext()) {
			Node node = iNodes.next();
			if(g.degree(node) == 1) {
				degreeCounter++;
			}
		}
		Set<Set<Node>> clusters = new HashSet<Set<Node>>();
		UndirectedGraph gclone = g.clone();
//		Util.viewGraph(g);
		for (int i = 0; i < numberOfCluster; i++) {
//			Bisection partition = metis(g,sizeFinerGraph);
			Bisection partition = new Bisection(g);
			System.out.print("Partizione iniziale: ");
			System.out.print("V1: " + partition.getSubgraph().getVertexCount());
			System.out.print(" V2: " + partition.getComplement().getVertexCount());
			System.out.println(" EC: " + partition.edgeCut() * 0.5);
			System.out.println("Conductance: " + 
					((double)partition.edgeCut() / 2) / Math.min(partition.getSubgraph().totalDegree(),partition.getComplement().totalDegree()));
			Set<Node> cluster = MQI.mqi(partition,true);
			
			UndirectedGraph clusterGraph = new Subgraph(gclone,cluster).createInducedSubgraph();
			
//			System.out.println(cluster);
			Bisection mqiBisection = new Bisection(new Subgraph(g,cluster));
			System.out.println("Partizione raffinata (MQI)");
			double newConductance = ((double)mqiBisection.edgeCut() / 2) / Math.min(mqiBisection.getSubgraph().totalDegree(),mqiBisection.getComplement().totalDegree());
			System.out.println("Conductance: " + newConductance);
			
			
			if(newConductance < 1) {
				System.out.println("CLUSTER "+ i + ":  V=" + clusterGraph.getVertexCount() + ", E=" + clusterGraph.getEdgeCount()+".");
				clusters.add(cluster);
			}
			
			System.out.println();
			Iterator<Node> clustersNode = cluster.iterator();
			while(clustersNode.hasNext()){
				g.removeVertex(clustersNode.next());
			}
		}
		Util.viewClusters(gclone, clusters);
		return clusters;
	}

}
