package Algorithms;

import java.util.ArrayList;

import Model.LayeredGraphEdge;
import Model.LayeredGraphNode;


/**
 * Der aus der Vorlesung bekannte Sweep Algorithmus zur Minimierung von Kantenkreuzungen
 * Es wird immer genau ein sweep durchgef�hrt
 * @author kolja
 *
 */
public class SweepCrossingMinimizer {

    
    public void minimizeCrossings( LayeredGraphNode graph ) {
        ArrayList< ArrayList< LayeredGraphNode > > layers = graph.getContainedLayers();
        System.out.println( "Number of Detected Crossings before minimization: " + numberOfCrossings( layers ) );
        for( int i = 1; i < layers.size(); i++ )
        { // Gehe alle Layer durch
            ArrayList<LayeredGraphNode> l1 = layers.get( i - 1 );
            ArrayList<LayeredGraphNode> l2 = layers.get( i );
            graph.setOrderedLayer( reduceCrossingsBetweenLayers( l1, l2, false ), i ); // minnimiere die Kreuzungen zwischen den letzten beiden Layern
        }
        for( int i = layers.size() - 2; i >= 0; i-- )
        { // Gehe alle Layer r�ckw�rts durch
            ArrayList<LayeredGraphNode> l1 = layers.get( i + 1 );
            ArrayList<LayeredGraphNode> l2 = layers.get( i );
            graph.setOrderedLayer( reduceCrossingsBetweenLayers( l1, l2, true ), i );  // minnimiere die Kreuzungen zwischen den letzten beiden Layern
        }
        System.out.println( "Number of Detected Crossings after minimization: " + numberOfCrossings( layers ) );
    }
    
    /**
     * Minnimiert die Kantenkreuzungen zwischen zwei layern
     * @param l1 Eine geordnete Liste mit Knoten aus dem ersten Layer
     * @param l2 Eine geordnete Liste mit Knoten aus dem zweiten Layer
     * @param rev Gibt an, in welche Richtung die Kanten verlaufen (true, falls von l2 nach l1)
     * @return Gibt eine Liste mit Gewichten zur�ck, nach denen die Knoten sortiert werden sollen
     */
    private ArrayList<Double> reduceCrossingsBetweenLayers( ArrayList<LayeredGraphNode> l1, ArrayList<LayeredGraphNode> l2, boolean rev)
    {
        ArrayList< Double > gewicht = new ArrayList< Double >();
        for( LayeredGraphNode n : l2 )
        { // F�r jeden Knoten in Layer 2
            ArrayList< LayeredGraphEdge > edges = null;
            if( rev )
                edges = n.getOutgoingEdges();
            else
                edges = n.getIncomingEdges(); // Kanten nach Layer 1
            double g = 0;
            for( LayeredGraphEdge e : edges )
            { // Ermittle das Gewicht des Knotens
                if( rev )
                    g += l1.indexOf( e.getTargets().get( 0 ) );
                else
                    g += l1.indexOf( e.getSources().get( 0 ) );
            }
            g /= edges.size(); // Teile das Gewicht durch die Anzahl der Kanten
            gewicht.add( g );
        }
        return gewicht;
    }
    
    /**
     * Z�hlt die Anzahl der Kantenkreuzungen in einem Graph
     * @param layers Eine Liste mit Sortierten Layern
     * @return Die Anzahl der Kreuzungen
     */
    private int numberOfCrossings(ArrayList<ArrayList<LayeredGraphNode>> layers)
    {
        ArrayList<LayeredGraphNode> lastLayer = null;
        int crossings = 0;
        for( ArrayList<LayeredGraphNode> l : layers )
        {
            if( lastLayer != null )
                crossings += numberOfCrossings( lastLayer, l);
            lastLayer = l;
        }
        return crossings;
    }
    
    /**
     * Z�hlt die Anzahl der Kantenkreuzungen zwischen zwei Layern (Es wird angenommen dass alle Kanten von l1 nach l2 verlaufen)
     * @param l1 Der erste Layer
     * @param l2 Der zweite Layer
     * @return Die Anzahl der Kreuzungen
     */
    private int numberOfCrossings( ArrayList<LayeredGraphNode> l1, ArrayList<LayeredGraphNode> l2 )
    {
        // Algoritmus aus der Vorlesung
        ArrayList< Integer > serie = new ArrayList<>();
        for( LayeredGraphNode n : l1 )
        {
            ArrayList<LayeredGraphEdge> edges = n.getOutgoingEdges();
            int i = 0;
            for( LayeredGraphNode n2 : l2 )
            {
                for( LayeredGraphEdge e : edges )
                {
                    if( e.getTargets().contains( n2 ) )
                    {
                        serie.add( i );
                        break;
                    }
                }
                i++;
            }
        }
        int crossings = 0;
        for( int i = 1; i < serie.size(); i++ )
        {
            if( serie.get( i ) < serie.get( i - 1 ) )
            {
                int j = i - 1;
                for( ; j > 0 && serie.get( i ) < serie.get( j - 1 ) ; j-- );
                crossings += i - j;
                int v = serie.get( i );
                serie.remove( i );
                serie.add( j, v);
                i = Math.max( 1, j );
            }
        }
        return crossings;
    }
}
