package eu.socialsensor.graphbased.clust;

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

import edu.uci.ics.jung.graph.Graph;

/**
 * Class implementing the local community detection method by Luo, Wang and Promislow
 * appearing in the paper "Exploring Local Community Structures in Large Networks", WI 2006.
 * 
 * @author papadop
 *
 * @param <O>
 */
public class LWPCommunityDetector<V,E> {
	
	public Community<V,E> getCommunity(
			Graph<V, E> graph, V seed) {
		
		int cId = 1;
		Community<V,E> community = new Community<V,E>(cId, graph);
		community.addMember(seed);
		Set<V> neighbourSet = new HashSet<V>(graph.getNeighbors(seed));
		
		//int counter = 0;
		
		Set<V> Q = new HashSet<V>();
		
		do {
			Q = new HashSet<V>();
			LocalModularity lastModularity = getLWPModularity(community);
			
			//System.out.println(++counter + " " + lastModularity);
			
			/* addition step */
			Iterator<V> nIter = neighbourSet.iterator();
			Set<V> toRemove = new HashSet<V>();
			while (nIter.hasNext()){
				V uj = nIter.next();
				if (toRemove.contains(uj)){
					continue;
				}
				community.addMember(uj);
				LocalModularity newModularity = getLWPModularity(community);
				//System.out.println("\tADD " + newModularity + " " + community.getNumberOfMembers());
				if (newModularity.getValue() > lastModularity.getValue()){
					lastModularity = newModularity;
					Q.add(uj);
					toRemove.add(uj);
				} else {
					community.removeMember(uj);
				}
			}
			Iterator<V> removeIter = toRemove.iterator();
			while (removeIter.hasNext()){
				neighbourSet.remove(removeIter.next());
			}
			
			/* deletion step */
			Set<V> deleteQ = new HashSet<V>();
			do {
				deleteQ = new HashSet<V>();
				List<V> communityVertices = community.getMembers();
				for (int i = 0; i < communityVertices.size(); i++){
					V currentVertex = communityVertices.get(i);
					community.removeMember(currentVertex);
					LocalModularity newModularity = getLWPModularity(community);
					//System.out.println("\tDEL " + newModularity + " " + community.getNumberOfMembers());
					if ( (newModularity.getValue() > lastModularity.getValue()) && (community.isConnected()) ){
						lastModularity = newModularity;
						deleteQ.add(currentVertex);
						if (Q.contains(currentVertex)){
							Q.remove(currentVertex);
						}
					} else {
						community.addMember(currentVertex);
					}
				}
			} while (!deleteQ.isEmpty() );
			
			/* add vertices to neighbourSet */
			Iterator<V> nIterK = Q.iterator();
			while (nIterK.hasNext()){
				Iterator<V> candIterator = graph.getNeighbors(nIterK.next()).iterator();
				while (candIterator.hasNext()){
					V al = candIterator.next();
					if ((!community.contains(al)) && (!neighbourSet.contains(al))){
						neighbourSet.add(al);
					}
				}
			}
			
		} while (!Q.isEmpty());
		
		if (getLWPModularity(community).getValue() > 0.0 && community.contains(seed)){
			return community;
		} else {
			System.err.println("Empty community returned, because the output community does not" +
					" contain the seed node!");
			return new Community<V,E>(cId, graph);
		}		
	}

	/**
	 * Compute the LPW modularity measure introduced by Luo, Wang and Promislow. This method
	 * has been made public so that other algorithms can use the same measure (but a different
	 * search strategy).
	 * 
	 * @param community Input community.
	 * @return
	 */
	public LocalModularity getLWPModularity(Community<V,E> community){
		/* check community validity*/
		if (!community.isValid()) throw new IllegalArgumentException(
				"You should provide a valid community as argument to the algorithm!");

		Graph<V, E> graph = community.getReferenceGraph();
		
		List<V> communityMembers = community.getMembers();
		int M = community.getNumberOfMembers();
		int indS = 0;
		int outdS = 0;
		for (int i = 0; i < M; i++){
			V currentMember = communityMembers.get(i);
			int currentMemberInDegree = 0;
			for (int j = 0; j < M; j++){
				if (i==j) continue;
				if (graph.findEdge(currentMember, communityMembers.get(j))!= null){
					currentMemberInDegree++;
				}
			}
			/* update out- and in-degree counts of community */
			outdS += graph.degree(currentMember) - currentMemberInDegree;
			/* in-edges were counted twice so divide by 2 */
			indS += (int)Math.round(currentMemberInDegree/2.0); 
		}
		return new LocalModularity(indS, outdS);
	}
	
	public LocalModularity getIncrementalLWPModularity(Community<V,E> community, V candMember, LocalModularity mod) {
		int indS0 = mod.getInDegree();
		int outdS0 = mod.getOutDegree();
		
		Graph<V, E> graph = community.getReferenceGraph();
		List<V> communityMembers = community.getMembers();
		int M = community.getNumberOfMembers();
		
		int currentMemberInDegree = 0;
		for (int i = 0; i < M; i++){
			if (graph.findEdge(candMember, communityMembers.get(i))!= null){
				currentMemberInDegree++;
			} 
		}
		/* update out- and in-degree counts of community */
		outdS0 += graph.degree(candMember) - currentMemberInDegree;
		/* in-edges were counted twice so divide by 2 */
		//indS0 += (int)Math.round(currentMemberInDegree/2.0); // this is probably wrong!
		indS0 += currentMemberInDegree;
		
		return new LocalModularity(indS0, outdS0);

	}
	
	

}