package graphtea.extensions.reports.spectralreports.maxflowmincut;

import java.util.List;
import graphtea.graph.graph.Edge;
import graphtea.graph.graph.GraphModel;
import graphtea.graph.graph.Vertex;

import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;


/***
 * 
 * @author Hooman Mohajeri Moghaddam
 *	This is the implementation of the Push-Relabel algorithm by Goldberg
 *	For further detail refer to Introduction to Algorithms (Chapter 26)
 *	Throughout the class the Graph is considered connected and the weights non-negative.  
 */

public class PushRelabel extends MaxFlow{



	private int[] height;
	private int[] excess;
	private Integer[] current;
	private final HashMap <Integer, LinkedList<Integer>> neighborMap ;
	private final HashMap <Integer, Iterator<Integer>> iterMap ;
	private final boolean isDirecte;




	public PushRelabel(GraphModel g, Vertex source, Vertex sink, boolean showResult)
	{
		super(g,source,sink,showResult);
		isDirecte = g.isDirected();
		C = g.getWeightedAdjacencyMatrix().getArrayCopy();



		n = C.length;
		current = new Integer[n];


		// a map between the vertex id and a linked-list of simple(undirected) neighbors".
		neighborMap = new HashMap<>();
		iterMap = new HashMap<>();

		//initializing neighbor list
		for(Vertex v: g)
		{
			LinkedHashSet<Integer> simpleNeihbors = new LinkedHashSet<>();
			for (Vertex n: g.getNeighbors(v))
				simpleNeihbors.add(n.getId());	

			for (Vertex n: g.getBackNeighbours(v))
				simpleNeihbors.add(n.getId());

			LinkedList<Integer> neighbors = new LinkedList<>(simpleNeihbors);
			int id = v.getId();
			neighborMap.put(id, neighbors);

			//			iterMap.put(id , neighbors.iterator());
			//			if(!neighbors.isEmpty())
			//				current[id]=neighbors.getFirst();

		}

	}


	public void doAlgorithm()
	{
		relableToFront();
	}
	private void  initializePreflow()
	{
		F = new double[n][n];
		height = new int[n];
		excess = new int[n];
		current = new Integer[n];

		this.t = sink.getId();
		this.s = source.getId();

		height[s] = n;

		LinkedList<Integer> neighbors;
		for(int k=0; k<n ; k++)
		{
			neighbors = neighborMap.get(k);
			iterMap.put(k , neighbors.listIterator(0));
			if(!neighbors.isEmpty())
				current[k]=neighbors.getFirst();
		}

		for(int v : neighborMap.get(s))
		{
			F[s][v]=C[s][v];
			excess[v]=(int)C[s][v];
			excess[s]-=C[s][v];
		}
		//makeLabels();
	}

	private void relabel(int u)
	{ 
		LinkedList<Integer> neighbors = neighborMap.get(u);
		int min = Integer.MAX_VALUE;
		for(Integer v : neighbors)
		{
			if( Cf(u, v) > 0 && height[v] < min )
				min = height[v];

		}
		height[u] = 1+min;
		//makeLabels();
	}

	private void push(int u , int v)
	{
		int deltaF = Math.min(excess[u], Cf(u, v));
		if(C[u][v]>0)
			F[u][v] += deltaF;
		else
			F[v][u] -= deltaF;

		excess[u] -= deltaF;
		excess[v] += deltaF;
		//makeLabels();
	}


	private int Cf(int u, int v)
	{
		return (int)(C[u][v] - F[u][v] + F[v][u]);
	}
	private void discharge(int u)
	{
		Iterator<Integer> iter = iterMap.get(u);
		Integer v;
		while (excess[u] > 0)
		{
			v = current[u];
			if (v==null)
			{
				relabel(u);
				iter=neighborMap.get(u).listIterator(0);
				iterMap.put(u, iter);
				current[u] = iter.next();
			}
			else if(Cf(u,v)>0 && height[u]==height[v]+1) 
				push(u,v);
			else
			{
				if(iter.hasNext())
					current[u]=iter.next();
				else
					current[u] = null;
			}
		}
	}

	private void relableToFront()
	{
		initializePreflow();
		LinkedList<Integer> L = new LinkedList<>();

		for(int i = 0; i < n ; i++)
			if( i != s && i != t )
				L.add(i);
		Iterator<Integer> iter = L.iterator();
		int oldHeight;
		while(iter.hasNext())
		{
			int u = iter.next();
			oldHeight = height[u];
			discharge(u);
			if (height[u] > oldHeight)
			{

				iter.remove();
				L.addFirst(u);
				iter=L.listIterator(0);
				iter.next();
			}
		}
	}

	protected void makeLabels()
	{
		int s,t;
		for(Edge e : g.getEdges())
		{
			s = e.source.getId();
			t = e.target.getId();
			if(isDirecte)
				e.setLabel(F[s][t] + "/" + C[s][t] );
			else
			{
				if(s<t)
					e.setLabel((F[s][t] - F[t][s]) + "/" + C[s][t] );
				else
					e.setLabel((F[t][s] - F[s][t]) + "/" + C[s][t] );
			}
		
			e.setShowWeight(false);
		}
		for(Vertex v : g)
			//v.setLabel(""+height[v.getId()]);
		g.setDrawEdgeLabels(true);
	}

}
