/*************************************************************************
 *  Kompilacja:  javac FordFulkerson.java
 *  Wykonanie:    java FordFulkerson V E
 *  Zależności: FlowNetwork.java FlowEdge.java Queue.java
 *
 *  Algorytm Forda-Fulkersona do wyznaczania przepływu maksymalnego i
 *  przekroju minimalnego za pomocą reguły najkrótszej ścieżki powiększającej.
 *
 *********************************************************************/

public class FordFulkerson {
    private boolean[] marked;     // marked[v] = prawda, jeśli ścieżka s->v występuje w grafie rezydualnym
    private FlowEdge[] edgeTo;    // edgeTo[v] = ostatnia krawędź na najkrótszej ścieżce rezydualnej s->v
    private double value;         // Bieżąca wartość przepływu maksymalnego
  
    // Przepływ maksymalny z s do t w sieci przepływowej G 
    public FordFulkerson(FlowNetwork G, int s, int t) {
        value = excess(G, t);
        if (!isFeasible(G, s, t)) {
            throw new RuntimeException("Początkowy przepływ jest niemożliwy");
        }

        // Dopóki istnieje ścieżka powiększająca, należy jej używać
        while (hasAugmentingPath(G, s, t)) {

            // Obliczanie przepustowości będącej wąskim gardłem
            double bottle = Double.POSITIVE_INFINITY;
            for (int v = t; v != s; v = edgeTo[v].other(v)) {
                bottle = Math.min(bottle, edgeTo[v].residualCapacityTo(v));
            }

            // Powiększanie przepływu
            for (int v = t; v != s; v = edgeTo[v].other(v)) {
                edgeTo[v].addResidualFlowTo(v, bottle); 
            }

            value += bottle;
        }

        // Sprawdzanie warunków optymalności
        assert check(G, s, t);
    }

    // Zwracanie wartości przepływu maksymalnego
    public double value()  {
        return value;
    }

    // Czy v znajduje się po stronie s minimalnego przekroju s-t?
    public boolean inCut(int v)  {
        return marked[v];
    }


    // Zwraca ścieżkę powiększającą, jeśli istnieje (w przeciwnym razie zwraca null)
    private boolean hasAugmentingPath(FlowNetwork G, int s, int t) {
        edgeTo = new FlowEdge[G.V()];
        marked = new boolean[G.V()];

        // Przeszukiwanie wszerz
        Queue<Integer> q = new Queue<Integer>();
        q.enqueue(s);
        marked[s] = true;
        while (!q.isEmpty()) {
            int v = q.dequeue();

            for (FlowEdge e : G.adj(v)) {
                int w = e.other(v);
                
                if (e.residualCapacityTo(w) > 0) {
                    if (!marked[w]) {
                        edgeTo[w] = e;
                        marked[w] = true;
                        q.enqueue(w);
                    }
                }
            }
        }

        // Czy istnieje ścieżka powiększająca?
        return marked[t];
    }



    // Zwraca nadmiarowy przepływ w wierzchołku v
    private double excess(FlowNetwork G, int v) {
        double excess = 0.0;
        for (FlowEdge e : G.adj(v)) {
            if (v == e.from()) excess -= e.flow();
            else               excess += e.flow();
        }
        return excess;
    }

    private boolean isFeasible(FlowNetwork G, int s, int t) {
        double EPSILON = 1E-11;

        // Sprawdza, czy ograniczenia przepustowości są spełnione
        for (int v = 0; v < G.V(); v++) {
            for (FlowEdge e : G.adj(v)) {
                if (e.flow() < 0 || e.flow() > e.capacity()) {
                    System.err.println("Krawędź nie spełnia ograniczeń przepustowości: " + e);
                    return false;
                }
            }
        }

        // Sprawdza, czy przepływ netto w wierzchołku jest równy zero (nie dotyczy to źródła i ujścia)
        if (Math.abs(value + excess(G, s)) > EPSILON) {
            System.err.println("Nadmiar w źródle    = " + excess(G, s));
            System.err.println("Przepływ maksymalny = " + value);
            return false;
        }
        if (Math.abs(value - excess(G, t)) > EPSILON) {
            System.err.println("Nadmiar w ujściu    = " + excess(G, t));
            System.err.println("Przepływ maksymalny = " + value);
            return false;
        }
        for (int v = 0; v < G.V(); v++) {
            if (v == s || v == t) continue;
            else if (Math.abs(excess(G, v)) > EPSILON) {
                System.err.println("Przepływ netto w " + v + " nie jest równy zero");
                return false;
            }
        }
        return true;
    }



    // Sprawdzanie warunków optymalności
    private boolean check(FlowNetwork G, int s, int t) {

        // Sprawdzanie, czy przepływ jest możliwy
        if (!isFeasible(G, s, t)) {
            System.err.println("Przepływ jest niemożliwy");
            return false;
        }

        // Sprawdzanie, czy s znajduje się po stronie źródła w przekroju minimalnym,
        //	a t nie znajduje się po tej stronie
        if (!inCut(s)) {
            System.err.println("źródło " + s + " nie znajduje się po stronie źródła w przekroju minimalnym");
            return false;
        }
        if (inCut(t)) {
            System.err.println("ujście " + t + " znajduje się po stroie źródła w przekoju minimalnym");
            return false;
        }

        // Sprawdzanie, czy wartość przekroju minimalnego = wartość przekroju maksymalnego
        double mincutValue = 0.0;
        for (int v = 0; v < G.V(); v++) {
            for (FlowEdge e : G.adj(v)) {
                if ((v == e.from()) && inCut(e.from()) && !inCut(e.to()))
                    mincutValue += e.capacity();
            }
        }

        double EPSILON = 1E-11;
        if (Math.abs(mincutValue - value) > EPSILON) {
            System.err.println("Wartość przekroju maksymalnego = " + value + ", wartość przekroju minimalnego = " + mincutValue);
            return false;
        }

        return true;
    }


    // Klient testowy, który tworzy losową sieć, rozwiązuje problem
    //	przekroju maksymalnego i wyświetla wyniki
    public static void main(String[] args) {

        // Tworzy sieć przepływową o V wierzchołkach i E krawędziach
        int V = Integer.parseInt(args[0]);
        int E = Integer.parseInt(args[1]);
        int s = 0, t = V-1;
        FlowNetwork G = new FlowNetwork(V, E);
        StdOut.println(G);

        // Oblicza przepływ maksymalny i przekrój minimalny
        FordFulkerson maxflow = new FordFulkerson(G, s, t);
        StdOut.println("Przepływ maksymalny z " + s + " do " + t);
        for (int v = 0; v < G.V(); v++) {
            for (FlowEdge e : G.adj(v)) {
                if ((v == e.from()) && e.flow() > 0)
                    StdOut.println("   " + e);
            }
        }

        // Wyświetlanie przekroju minimalnego
        StdOut.print("Przekrój minimalny: ");
        for (int v = 0; v < G.V(); v++) {
            if (maxflow.inCut(v)) StdOut.print(v + " ");
        }
        StdOut.println();

        StdOut.println("Wartość przepływu maksymalnego = " +  maxflow.value());
    }

}
