package DirectedGraphJava;

import java.util.HashSet;
import java.util.HashMap;
import java.util.Iterator;

public class DirectedGraph implements IDirectedGraph {
    private HashSet<Integer> _vertices = new HashSet<Integer>();
    private HashMap<Integer, HashSet<Integer>> _inbounds = new HashMap<Integer, HashSet<Integer>>();
    private HashMap<Integer, HashSet<Integer>> _outbounds = new HashMap<Integer, HashSet<Integer>>();
    private HashMap<Pair<Integer,Integer>,Integer> _edges = new HashMap<Pair<Integer,Integer>,Integer>();
    
    public DirectedGraph() {
        //nothing to do
    }

    public DirectedGraph reindex(){
        HashMap<Integer,Integer> oldToNew = new HashMap<Integer,Integer>();
        int i = 0;
        for (Integer vertex : _vertices) {
            oldToNew.put(vertex,i);
            i++;
        }
        DirectedGraph newGraph = new DirectedGraph();
        for (Integer vertex : _vertices) {
            newGraph.addVertex(oldToNew.get(vertex));
        }
        for (Pair<Integer,Integer> edge : _edges.keySet()) {
            newGraph.addEdge(oldToNew.get(edge.first),oldToNew.get(edge.second),_edges.get(edge));
        }
        return newGraph;
    }

    public Integer numVertices() {
        return _vertices.size();
    }

    public Integer numEdges() {
        return _edges.size();
    }

    public Iterator<Integer> vertices() {
        return _vertices.iterator();
    }

    public Iterator<Pair<Pair<Integer,Integer>,Integer>> edges() {
        HashSet<Pair<Pair<Integer,Integer>,Integer>> keyValues = new HashSet<Pair<Pair<Integer,Integer>,Integer>>();
        for (Pair<Integer,Integer> key : this._edges.keySet()) {
            Pair<Pair<Integer,Integer>,Integer> keyValuePair = new Pair<Pair<Integer,Integer>,Integer>(key,this._edges.get(key)); 
            keyValues.add(keyValuePair);
        }
        return keyValues.iterator();
    }

    public boolean isEdge(Integer v1, Integer v2) {
        if(!_vertices.contains(v1) || !_vertices.contains(v2)) {
            throw new IllegalArgumentException("Vertex does not exist");
        }
        return _edges.containsKey(new Pair<Integer,Integer>(v1,v2));
    }

    public int inDegree(Integer v) {
        if(!_vertices.contains(v)) {
            throw new IllegalArgumentException("Vertex does not exist");
        }
        return _inbounds.get(v).size();
    }

    public int outDegree(Integer v) {
        if(!_vertices.contains(v)) {
            throw new IllegalArgumentException("Vertex does not exist");
        }
        return _outbounds.get(v).size();
    }

    public Iterator<Integer> inbounds(Integer v) {
        if(!_vertices.contains(v)) {
            throw new IllegalArgumentException("Vertex does not exist");
        }
        return _inbounds.get(v).iterator();
    }

    public Iterator<Integer> outbounds(Integer v) {
        if(!_vertices.contains(v)) {
            throw new IllegalArgumentException("Vertex does not exist");
        }
        return _outbounds.get(v).iterator();
    }

    public Integer weight(Integer v1, Integer v2) {
        if(!_vertices.contains(v1) || !_vertices.contains(v2)) {
            throw new IllegalArgumentException("Vertex does not exist");
        }
        if(!_edges.containsKey(new Pair<Integer,Integer>(v1,v2))) {
            throw new IllegalArgumentException("Edge does not exist");
        }
        return _edges.get(new Pair<Integer,Integer>(v1,v2));
    }

    public void weight(Integer v1, Integer v2, Integer w) {
        if(!_vertices.contains(v1) || !_vertices.contains(v2)) {
            throw new IllegalArgumentException("Vertex does not exist");
        }
        if(!_edges.containsKey(new Pair<Integer,Integer>(v1,v2))) {
            throw new IllegalArgumentException("Edge does not exist");
        }
        _edges.put(new Pair<Integer,Integer>(v1,v2),w);
    }

    public void addVertex(Integer v) {
        if(_vertices.contains(v)) {
            throw new IllegalArgumentException("Vertex already exists");
        }
        _vertices.add(v);
        _inbounds.put(v,new HashSet<Integer>());
        _outbounds.put(v,new HashSet<Integer>());
    }

    public void removeVertex(Integer v) {
        if(!_vertices.contains(v)) {
            throw new IllegalArgumentException("Vertex does not exist");
        }
        _vertices.remove(v);
        for (Integer vertex : _inbounds.get(v)) {
            _outbounds.get(vertex).remove(v);
            _edges.remove(new Pair<Integer,Integer>(vertex,v));
        }
        for (Integer vertex : _outbounds.get(v)) {
            _inbounds.get(vertex).remove(v);
            _edges.remove(new Pair<Integer,Integer>(v,vertex));
        }
        _inbounds.remove(v);
        _outbounds.remove(v);
    }

    public void addEdge(Integer v1, Integer v2, Integer w) {
        if(!_vertices.contains(v1) || !_vertices.contains(v2)) {
            throw new IllegalArgumentException("Vertex does not exist");
        }
        if(_edges.containsKey(new Pair<Integer,Integer>(v1,v2))) {
            throw new IllegalArgumentException("Edge already exists");
        }
        _edges.put(new Pair<Integer,Integer>(v1,v2),w);
        _inbounds.get(v2).add(v1);
        _outbounds.get(v1).add(v2);
    }

    public void removeEdge(Integer v1, Integer v2) {
        if(!_vertices.contains(v1) || !_vertices.contains(v2)) {
            throw new IllegalArgumentException("Vertex does not exist");
        }
        if(!_edges.containsKey(new Pair<Integer,Integer>(v1,v2))) {
            throw new IllegalArgumentException("Edge does not exist");
        }
        _edges.remove(new Pair<Integer,Integer>(v1,v2));
        _inbounds.get(v2).remove(v1);
        _outbounds.get(v1).remove(v2);
    }

}