import java.util.Enumeration;
import java.io.*;

/**
   A repræsentation of a bipartite graph.

   The graph is represented only by the nodes - the edges are not
   stored explicit in the graph. The nodes are put into two lists,
   <i>V1</i> and <i>V2</i>.

   @author Martin Geisler <gimpster@gimpster.com>
   @author Jérémy Auneau <jeremy.auneau@skjoldhoej.dk>
*/
public class BipartiteGraph {

    private Node[] v1 = new Node[0];
    private Node[] v2 = new Node[0];
    private int n1 = 0;
    private int n2 = 0;
    
    /**
       Run the graph coloring algorithm.

       The graph is colored by application of the coloring transition
       system. The algorithm runs in <i>O</i>(<i>|m| + |n|</i>), where
       <i>m</i> and <i>n</i> are the edges and nodes from the graph
       which nodes are those from <i>V1</i> and <i>V2</i>.

       @return a random terminal node
    */
    public Node runTransitionSystem() {
        Sequence m1 = new Sequence(); // Edges from V1 to V2
        Sequence m2 = new Sequence(); // Edges from V2 to V1
        Node v0 = null; // A random initial node.
	
        for (int i = 0; i < n1; i++) {
            if (v1[i].inDegree() == 0) {
                v1[i].color = "black";
                Enumeration e = v1[i].outEdges();
                while (e.hasMoreElements()) {
                    m1.insertLast((Edge)e.nextElement());
                }
            }
        }
        
        while (m1.size() != 0 || m2.size() != 0) {
            while (m1.size() != 0) {
		Edge e = (Edge)m1.removeLast();
                if (e.to.outDegree() == 0) {
                    e.to.color = "black";
		    /* We pick a random terminal node: */
		    v0 = e.to;
		    /* We also store the edge from which we came, so
                       that we can find our way back again later: */
		    e.to.prev = e;
		} else {
                    e.to.color = "gray";
		    /* We store the node which we came from, so that
                       we can find our way back again later: */
		    e.to.prev = e;
		    Edge edge = e.to.anyOutEdge(); // There is exactly one.
		    if (edge.to.color.equals("white")) {
			m2.insertLast(edge);
                    }
                }
            }

            while (m2.size() != 0) {
                Edge e = (Edge)m2.removeLast();
                e.to.color = "gray";
                Enumeration enum = e.to.outEdges();
                while (enum.hasMoreElements()) {
                    Edge edge = (Edge)enum.nextElement();
                    if (edge.to.color.equals("white")) {
                        m1.insertLast(edge);
                    }
                }
            }
        }
        return v0;
    }

    /**
       Turns the edges around.
       
       The edges are reorientated along a random path leading back
       from the terminal node <code>v0</code> to an initial node. It
       runs in <i>O</i>(<i>|m'|</i>) where <i>m'</i> are the edges that lie
       on the path.

       The terminal node <code>v0</code> <i>must</i> come from a call
       to {@link #runTransitionSystem} because that method also stores
       information needed to find a path back to an initial node.
       
       @param v0 a terminal node as returned by runTransitionSystem()
       @see #runTransitionSystem()
    */
    public void turnEdges(Node v0) {
        Sequence m = new Sequence();
        Edge e1 = null; // An Edge from V1 to V2
        Edge e2 = null; // An Edge from V2 to V1
        Node v1 = null; // A Node in V1
        Node v2 = null; // A Node in V2

        e1 = v0.prev;
        v1 = e1.from;
        m.insertLast(e1);

        while (!v1.color.equals("black")) {
            e2 = v1.anyInEdge(); // There is exactly one edge.
            v2 = e2.from;
            e1 = v2.prev;
            v1  = e1.from;
            m.insertLast(e1);
            m.insertLast(e2);
        }
        
        Enumeration e = m.elements();
        while (e.hasMoreElements()) {
            Edge edge = (Edge)e.nextElement();
            Node a = edge.from;
            Node b = edge.to;
            edge.remove();
            new Edge(b, a);
        }

    }

    /**
       Colors the nodes white
       
       All the nodes composing the graph are run through. The color of
       the nodes is systematically changed to white. The algorithm
       runs in <i>O</i>(<i>|n|</i>), where <i>n</i> are the nodes
       listed in <i>V1</i> and <i>V2</i> (which means the total amount
       of nodes in the graph).
    */
    public void removeColors() {
        for (int i = 0; i < n1; i++)
            v1[i].color = "white";
        for (int i = 0; i < n2; i++)
            v2[i].color = "white";
    }
    
    /**
       Loads an input graph from a file
       
       Input data are read from a file and the corresponding graph is
       constructed. The algorithm runs in <i>O</i>(<i>|n| + |m|</i>) where
       <i>m</i> are the edges listed in the input file and <i>|n|</i>
       the number of nodes composing the graph.
       
       @param filename an input data file describing a graph.
    */
    public void loadDataFromFile(String filename) {

        try {
            BufferedReader input =
                new BufferedReader(new FileReader(filename));

            n1 = Integer.parseInt(input.readLine());
            v1 = new Node[n1];
            for (int i = 0; i < n1; i++)
                v1[i] = new Node("A" + (i+1), i);

            n2 = Integer.parseInt(input.readLine());
            v2 = new Node[n2];
            for (int i = 0; i < n2; i++)
                v2[i] = new Node("B" + (i+1), i);
            
            int  m = Integer.parseInt(input.readLine());
            for (int i = 0; i < m; i++) {
                String line = input.readLine();
                int s = line.indexOf(" ", 0);
                String a = line.substring(0, s);
                String b = line.substring(s+1, line.length());
                new Edge(v1[Integer.parseInt(a)],
                         v2[Integer.parseInt(b)]);
            }
            
            /*
              Extension: it is possible to specify the names of the
              nodes in the lines that follow the last edge. The
              format is: one name per line, starting with the names
              of the nodes in V1.
            */
            for (int i = 0; i < n1 && input.ready(); i++) {
                v1[i].name = input.readLine();
            }

            for (int i = 0; i < n2 && input.ready(); i++) {
                v2[i].name = input.readLine();
            }
        } catch (IOException e) {
            System.err.println("There was an IO error while " +
                               "reading the file - aborting...");
            System.exit(1);
        } catch (NumberFormatException e) {
            System.err.println("There was a number format error while " +
                               "reading the file - aborting...");
        }
    }

    /**
       Produces a graphical LaTeX output of a graph. The output is an
       Xy-pic matrix which can be included in another LaTeX document.
    
       @param filename the LaTeX output will be saved here.
    */ 
    public void toLaTeX(String filename) {
        toLaTeX(filename, false);
    }

    /**
       Produces a graphical LaTeX output of a graph. The output is an
       Xy-pic matrix which can be included in another LaTeX document.
       
       @param filename the LaTeX output will be saved here.
       @param only_pairs boolean that indicates whether the produced
       graph should contain all the edges or only the pair edges.
    */
    public void toLaTeX(String filename, boolean only_pairs) {
        Enumeration enum = null;
        String str = "\\xymatrix@!0{\n";
        int i = 0;
        int extra_columns = 8;
        while (i < n1 || i < n2) {
            if (i < n1) {
                str += "  {\\rbox{" + v1[i].name + "\\;}" +
                    "\\" + v1[i].color.charAt(0) + "box}";
                if (!only_pairs) {
                    enum = v1[i].outEdges();
                    while (enum.hasMoreElements()) {
                        Edge e = (Edge)enum.nextElement();
                        Node n = e.to;
                        str += " \\ar[" + (n.rankLaTeX - i) +
                               "," + extra_columns + "] ";
                    }
                }
            }
            
            str += " ";
            for (int c = 0; c < extra_columns; c++) {
                str += "&";
            }
            str += " ";

            if (i < n2) {
                enum = v2[i].outEdges();
                while (enum.hasMoreElements()) {
                    Edge e = (Edge)enum.nextElement();
                    Node n = e.to;
                    str += "\\ar@*{[thicker]}[" + (n.rankLaTeX - i) +
                           ",-" + extra_columns + "] ";
                }
                str += "{\\" + v2[i].color.charAt(0) + "circ" +
                    "\\lbox{\\;" + v2[i].name + "}}";
            }

            i++;
            if ((i < n1 && n1 > n2) || (i < n2 && n2 > n1)) {
                str += " \\\\\n";
            }
        }


        str = str + "\n}\n";

        BufferedWriter out;
        try {
            out = new BufferedWriter(new FileWriter(filename));
            out.write(str);
            out.close();
        } catch (IOException e) {
            System.err.println("There was an IO problem with the file " +
                               filename + " - no LaTeX output produced.");
        }
    }
    
    /**
       Returns a string representation of the graph.

       This should only be used when debugging.
       
       @return an enumeration of the nodes in <i>V1</i> and <i>V2</i>.
    */
    public String toString() {

        Enumeration e = null;
        String result = "Nodes in V1:\n";
        for (int i = 0; i < v1.length; i++) {
            result = result + v1[i] + ":\n  In:  ";
            
            e = v1[i].inEdges();
            while (e.hasMoreElements())
                result = result + (Edge)e.nextElement() + " ";
            
            result = result + "\n  Out: ";

            e = v1[i].outEdges();
            while (e.hasMoreElements())
                result = result + (Edge)e.nextElement() + " ";
            
            result = result + "\n";
        }

        result = result + "Nodes in V2:\n";
        for (int i = 0; i < v2.length; i++) {
            result = result + v2[i] + ":\n  In:  ";
            
            e = v2[i].inEdges();
            while (e.hasMoreElements())
                result = result + (Edge)e.nextElement() + " ";

            result = result + "\n  Out: ";

            e = v2[i].outEdges();
            while (e.hasMoreElements())
                result = result + (Edge)e.nextElement() + " ";
            
            result = result + "\n";
        }
        
        return result;
    }

    /**
       Get a string representation of the edges that are part of the
       matching.
       
       This is done by finding the edges that goes from V2 to V1. It
       is done in <i>O</i>(<i>|V1|</i>) since we only have to ask each
       node in V1 once to find out if there is an incoming edge. If
       so, then it takes a constant amount of time to acquire the
       edge.
        
       @return a string representation of the edges in the matching,
       or the empty string if the matching is the empty set.
    */
    public String getMatches() {
        String str = "";
        
        for (int i = 0; i < n1; i++) {
            if (v1[i].inDegree() == 1) {
                str += ", " + v1[i].anyInEdge().toString();
            }
        }
        return str.substring(2);
    }

} // BipartiteGraph