/*
    Copyright © 2011 MLstate

    This file is part of Opa.

    Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

/**
 * Library for functional graphs.
 *
 * @author Mathieu Barbin
 * @category algorithmic
 * @destination public
 * @stabilization Totally untested, beware by using this code
 */


/**
 * {1 About this module}
 *
 * This module contains a high-level interface for manipulating functional graphs.
 * The kind of graph is
 * + directed
 * + vertex-labeled
 * + edge-labeled
 *
 * It is inspired and adapted from OcamlGraph Signatures and Persitent implementation.
 *
 * This representation is adapted for several cases, from simple directed graphs to automatons.
 *
 * {1 Where should I start?}
 *
 * {1 What if I need more?}
 */

/**
 * {1 Types defined in this module}
 */

/**
 * The type for indexing the nodes of a graph.
 * We use the native type [int] as unique identifier. Creating a new node
 * will use a call to a fresh maker, which is on the server. If a client uses
 * this lib, there will be some request to the server for generating nodes.
 */
type FGraph.index = int
type FGraph.index.order = Order.default

/**
 * The type for a vertex. A vertex is indexed with an unique index, generated
 * via a fresh maker.
 * The label is an info you can attach to a vertex. Different vertices can
 * share the same label, comparison between vertex is always computed only
 * from the index. You cannot change the label once the vertex is created.
 *
 * <!> The type should be private, and not manipulated by user of the FGraph lib.
 * (currently not possible in opa). Please do not use the implementation in your code.
 */
type FGraph.vertex('vertex_label) = {
     index : FGraph.index ;
     label : 'vertex_label ;
}

type FGraph.VertexMap.t('vertex_label, 'elem) = ordered_map(FGraph.vertex('vertex_label), 'elem, FGraph.index.order)
// type FGraph.IndexSet.t('vertex_label) = ordered_set(FGraph.index, FGraph.index.order)

/**
 * The type for an edge. <!> Beware, the label is important for identifying edges.
 * 2 edges between 2 vertices with the same label are in fact the same edge (e.g. automaton)
 * In a graph, you may have several edges between vertices with different labels.
 *
 * <!> The type should be private, and not manipulated by user of the FGraph lib.
 * (currently not possible in opa). Please do not use the implementation in your code.
 */
type FGraph.edge('edge_label, 'vertex_label) = {
     src : FGraph.vertex('vertex_label) ;
     label : 'edge_label ;
     dst : FGraph.vertex('vertex_label) ;
}

/**
 * The type of a node.
 * In this implementation, we have a support for explicitly maintaining edge sets
 * of predecessors. Crucial for algorithms that do a lot of backwards traversal.
 *
 * Some assertion about a [node] pointed in [graph.vertices] by a [vertex] :
 *
 * + all [dst] fields of edges in [in] are physically equal to [vertex]
 * + all [src] fields of edges in [out] are physically equal to [vertex]
 * + in [in] and [out], 2 edges in the list have necessary not the same label,
 *   wrt the [compare_label] function given as argument of the functor FGraph.
 * + the list of edges are ordered wrt the [compare_label] function given as argument in X
 *
 * <!> The type should be private, and not manipulated by user of the FGraph lib.
 * (currently not possible in opa). Please do not use the implementation in your code.
 *
 */
type FGraph.node('edge_label, 'vertex_label) = {
     in : FGraph.VertexMap.t('vertex_label, list(FGraph.edge('edge_label, 'vertex_label))) ;
     out : FGraph.VertexMap.t('vertex_label, list(FGraph.edge('edge_label, 'vertex_label))) ;
}

/**
 * The type of a fgraph.
 * The size is the size of the vertices map, cached just for optimization.
 *
 * <!> The type should be private, and not manipulated by user of the FGraph lib.
 * (currently not possible in opa). Please do not use the implementation in your code.
 */
type FGraph.t('edge_label, 'vertex_label) = {
     vertices : FGraph.VertexMap.t('vertex_label, FGraph.node('edge_label, 'vertex_label)) ;
     size : int ;
}

/**
 * The argument to build a instance of module FGraph.
 * A FGraph module is specialized by :
 * + the type of the label of the edges,
 * + the type of the label of the vertex,
 *
 * The string_of functions are used for producing dot.
 *
 * FIXME: this should be a module interface, not a record interface.
 * Update when this feature will be integrated
 */
type FGraph.X.interface('edge_label, 'vertex_label, 'ordering_edge, 'ordering_vertex) = {
     order_edge_label : order('edge_label, 'ordering_edge);
     order_vertex_label : order('vertex_label, 'ordering_vertex);

     string_of_edge_label : 'edge_label -> string ;
     string_of_vertex_label : 'vertex_label -> string ;
}

/**
 * {1 Interface}
 */

/**
 * The functor for creating a new instance of FGraph, depending on the type of
 * the label of the edges. For non labeled edges or vertex, you can use the type [void]
 *
 * FIXME: Define clean interfaces for this module and sub module.
 */
FGraph(X : FGraph.X.interface('edge_label, 'vertex_label, 'ordering_edge, 'ordering_vertex)) = {{

  /**
   * {2 Manipulation of vertices}
   */

  Vertex = {{

    /**
     * {3 Vertices are Comparable}
     */

    ordering(v1: FGraph.vertex, v2: FGraph.vertex)= Int.ordering(v1.index, v2.index)
    compare(v1, v2) = @toplevel.compare((v1 : FGraph.vertex).index, (v2 : FGraph.vertex).index)
    hash(v) = (v : FGraph.vertex).index
    equal(v1, v2) = (v1 : FGraph.vertex).index == (v2 : FGraph.vertex).index

    /**
     * Comparison based on label
     */
    ordering_label(v1: FGraph.vertex, v2: FGraph.vertex)= Order.ordering(v1.label, v2.label, X.order_vertex_label)
    compare_label(v1, v2) = @opensums(ordering_label(v1, v2))
    /* Type constraint added to prevent value restriction from rejecting. */
    order_label = (Order.make(ordering_label) :
                     order(FGraph.vertex, 'vertex_label))

    /**
     * {3 Vertices are labeled}
     */

    /**
     * The private fresh make used by the FGraph library.
    **/
    @private cpt_vertex = Fresh.server(i->i)

    /**
     * Create a new vertex with a given label. This function returns a new node each time it is called.
     * 2 different vertex can share the same label, there will be still different.

     */
    create(label) =
      index = cpt_vertex()
      ~{ index label }

    label(v1) = (v1 : FGraph.vertex).label

  }}

  VertexOrder = Order.make(Vertex.ordering) : order(FGraph.vertex('vertex_label), FGraph.index.order)
  VertexMap = Map_make(VertexOrder)
// VertexSet = Set_make(VertexOrder)

  /**
   * {2 Manipulation of edges}
   */
  Edge = {{

    /**
     * {3 Edges are Ordered}
     */

    /**
     * The comparison is down in lexicographic order
     * wrt the order :(src, dst, label). Comparison on vertex is based on unique index,
     * and comparison on label is based on the [compare_label] given in argument [X].
     */
    ordering(e1, e2) =
      { src=src1 label=label1 dst=dst1 } = e1
      { src=src2 label=label2 dst=dst2 } = e2
      match Vertex.ordering(src1, src2) with
         | {eq} ->
           match Vertex.ordering(dst1, dst2) with
             | {eq} -> Order.ordering(label1, label2, X.order_edge_label)
             |  r   -> r
           end
         | r    -> r
       end

    compare(e1, e2) = @opensums(ordering(e1, e2))
    /* Type constraint added to prevent value restriction from rejecting. */
    order = (Order.make(ordering):
              order({ dst : FGraph.vertex('vertex_label) ;
                      label : 'edge_label ;
                      src : FGraph.vertex('vertex_label) },
                    'edge_label))

    /**
     * Comparison based on label
     */
    ordering_label(e1, e2) = Order.ordering((e1 : FGraph.edge).label, (e2 : FGraph.edge).label, X.order_edge_label)
    compare_label(e1, e2) = @opensums(ordering_label(e1, e2))
    /* Type constraint added to prevent value restriction from rejecting. */
    order_label = (Order.make(ordering_label):
                    order(FGraph.edge('edge_label, 'vertex_label), 'edge_label))

    /**
     * {3 Edges are directed}
     */

    src(e) = (e : FGraph.edge('edge_label, 'vertex_label)).src
    dst(e) = (e : FGraph.edge('edge_label, 'vertex_label)).dst

    /**
     * {3 Edges are labeled}
     */

    /**
     * Creating a new edge from its [src], [dst] vertices and its [label].
     * If you create 2 times the same edge, you'll get 2 different objects in memory,
     * but there would be equal wrt the [Edge.compare] function, which assure the good
     * behavior of other primitives manipulating edges.
     */
    create(src, label, dst) = ~{ src label dst} : FGraph.edge('edge_label, 'vertex_label)

    label(edge) = (edge : FGraph.edge('edge_label, 'vertex_label)).label
  }}

  /**
   * private functions: (monomorphized access + utilities)
   * TODO: do not export in the interface (OPA missing feature).
   * Please do not use in your code.
   */

  @private get_src(e) = (e : FGraph.edge).src

  @private get_dst(e) = (e : FGraph.edge).dst
  @private get_vertices(t) = (t : FGraph.t).vertices
  @private get_size(t) = (t : FGraph.t).size
  @private get_node(t, vertex) =
    vertices = get_vertices(t)
    VertexMap.get(vertex, vertices)

  @private cardinal_edges(inout) =
    fold(_, edges, acc) = List.length(edges) + acc
    VertexMap.fold(fold, inout, 0)

  @private cardinal_edges_label(inout, label) =
    fold(_, edges, acc) =
      fold(edge, acc) =
        if Order.equals((edge : FGraph.edge).label,label,  X.order_edge_label) then acc + 1
        else acc
      List.fold(fold, edges, acc)
    VertexMap.fold(fold, inout, 0)


  /**
   * {2 Size functions}
   */

  is_empty(t) = get_size(t) == 0
  nb_vertices = get_size

  nb_edges(t) =
    vertices = get_vertices(t)
    fold(_, node, acc) = cardinal_edges(node.out) + acc
    VertexMap.fold(fold, vertices, 0)

  /**
   * Compute the out (resp. in) degree of a vertex [vertex] ignoring labels.
   * This is the number of vertices pointed by (resp which points to) [vertex] with at least 1 edge.
   * If the vertex is not in the graph, the function returns [0].
   */

  out_neighbors(t, vertex) =
    match get_node(t, vertex) with
    | { none } -> 0
    | { some = node } -> VertexMap.size(node.out)

  in_neighbors(t, vertex) =
    match get_node(t, vertex) with
    | { none } -> 0
    | { some = node } -> VertexMap.size(node.in)

  /**
   * Computing the out (resp. in) degree of a vertex [vertex] distinguishing edges by their labels.
   * If there are several edges between 2 vertices, they are counted several times.
   * If the vertex is not in the graph, the function returns [0].
   */

  out_degree(t, vertex) =
    match get_node(t, vertex) with
    | { none } -> 0
    | { some = node } -> cardinal_edges(node.out)

  in_degree(t, vertex) =
    match get_node(t, vertex) with
    | { none } -> 0
    | { some = node } -> cardinal_edges(node.in)

  /**
   * Computing the out (resp. in) degree of a vertex [vertex] filtered by a specific given label.
   * If the vertex is not in the graph, the function returns [0].
   */

  out_degree_label(t, vertex, label) =
    match get_node(t, vertex) with
    | { none } -> 0
    | { some = node } -> cardinal_edges_label(node.out, label)

  in_degree_label(t, vertex, label) =
    match get_node(t, vertex) with
    | { none } -> 0
    | { some = node } -> cardinal_edges_label(node.in, label)

  /**
   * {2 Membership functions}
   */

  /**
   * Test the occurrence of a vertex in a graph.
   * Remember that vertex_label are ignored by the comparison function.
   */
  mem_vertex(t, vertex) =
    vertices = get_vertices(t)
    VertexMap.mem(vertex, vertices)

  /**
   * Test the oriented connection between 2 vertices in a graph.
   * If one of the vertices in not in the graph, the function returns [false]
   */
  mem_neighbor(t, src, dst) =
    vertices = get_vertices(t)
    match VertexMap.get(src, vertices) with
    | { some = node } -> VertexMap.mem(dst, node.out)
    | { none } -> false

  /**
   * Test the occurrence of a labeled edge between 2 vertices in a graph with a specific label
   * If one of the vertices in not in the graph, the function returns [false]
   */
  mem_edge(t, src, label, dst) =
    vertices = get_vertices(t)
    match VertexMap.get(src, vertices) with
    | { some = node } ->
      match VertexMap.get(dst, node.out) with
      | { none } -> false
      | { some = edges } ->
        // TODO: the list is ordered wrt the X.compare_edge_label function, this can be optimized
        // This has probably no impact for small list of edges
        exists(edge) = Order.equals(edge.label, label, X.order_edge_label)
        List.exists(exists, edges)
      end
    | { none } -> false

  /**
   * Sugar of interface for [mem_edge_label], working on an edge.
   */
  mem_edge_e(t, (edge : FGraph.edge)) = mem_edge(t, edge.src, edge.label, edge.dst)

  /**
   * Find the list of all edges between 2 vertices. If one a the vertices is not in the graph, returns [List.empty]
   */
  find_edge(t, src, dst) =
    vertices = get_vertices(t)
    match VertexMap.get(src, vertices) with
    | { some = node } -> VertexMap.get(dst, node.out) ? List.empty
    | { none } -> List.empty

  /**
   * {2 Successors and predecessors}
   */

  /**
   * Do not construct these lists for iteration, but rather use directly FGraph iterators.
   */

  /**
   * Returns the successors (resp. predecessors) of a [vertex].
   * The returned list does not contains doubloons, although they could be several
   * edges between the [vertices]. The returned list is empty if the vertex is not in the graph.
   */

  succ(t, src) =
    match get_node(t, src) with
    | { none } -> List.empty
    | { some = node } -> VertexMap.To.key_list(node.out)

  pred(t, dst) =
    match get_node(t, dst) with
    | { none } -> List.empty
    | { some = node } -> VertexMap.To.key_list(node.in)

  /**
   * Same, successors / predecessors but restricting the label of the edges
   */

  succ_label(t, src, label) =
    match get_node(t, src) with
    | { none } -> List.empty
    | { some = node } ->
      fold(_, edges, acc) =
        fold(edge, acc) =
          if Order.equals((edge : FGraph.edge).label, label,  X.order_edge_label) then List.cons(edge.dst, acc)
          else acc
        List.fold(fold, edges, acc)
      VertexMap.fold(fold, node.out, List.empty)

  pred_label(t, dst, label) =
    match get_node(t, dst) with
    | { none } -> List.empty
    | { some = node } ->
      fold(_, edges, acc) =
        fold(edge, acc) =
          if Order.equals((edge : FGraph.edge).label, label, X.order_edge_label) then List.cons(edge.src, acc)
          else acc
        List.fold(fold, edges, acc)
      VertexMap.fold(fold, node.in, List.empty)

  succ_e(t, src) =
    match get_node(t, src) with
    | { none } -> List.empty
    | { some = node } -> VertexMap.To.val_list(node.out)

  pred_e(t, dst) =
    match get_node(t, dst) with
    | { none } -> List.empty
    | { some = node } -> VertexMap.To.val_list(node.in)

  /**
   * {2 Graph iterators}
   */

  /**
   * Iterate on all vertices of a graph.
   */
  iter_vertex(iter, t) =
    vertices = get_vertices(t)
    VertexMap.iter(vertex, _ -> iter(vertex), vertices)

  /**
   * Fold on all vertices of a graph.
   */
  fold_vertex(fold, t, acc) =
    vertices = get_vertices(t)
    VertexMap.fold(vertex, _, acc -> fold(vertex, acc), vertices, acc)

  /**
   * Iterate on all edges.
   */
  iter_edge(iter, t) =
    iter(node) =
      out = (node : FGraph.node).out
      VertexMap.iter(_, edges -> List.iter(edge -> iter(edge.src, edge.label, edge.dst), edges), out)
    vertices = get_vertices(t)
    VertexMap.iter(_, node -> iter(node), vertices)

  /**
   * Fold on all edges.
   */
  fold_edge(fold, t, acc) =
    fold(node, acc) =
      out = (node : FGraph.node).out
      VertexMap.fold(_, edges, acc -> List.fold(edge, acc -> fold(edge.src, edge.label, edge.dst, acc), edges, acc), out, acc)
    vertices = get_vertices(t)
    VertexMap.fold(_, node, acc -> fold(node, acc), vertices, acc)

  /**
   * Iterate on all neighbors. There is only one iteration, even if there are several edges between 2 vertices
   */
  iter_neighbor(iter, t) =
    iter(src, node) =
      out = (node : FGraph.node).out
      VertexMap.iter(dst, _ -> iter(src, dst), out)
    vertices = get_vertices(t)
    VertexMap.iter(iter, vertices)

  /**
   * Fold on all neighbors.
   * There is only one iteration for an oriented connection,
   * even if there are several edges between 2 vertices
   */
  fold_neighbor(fold, t, acc) =
    fold(src, node, acc) =
      out = (node : FGraph.node).out
      VertexMap.fold(dst, _, acc -> fold(src, dst, acc), out, acc)
    vertices = get_vertices(t)
    VertexMap.fold(fold, vertices, acc)

  /**
   * Sugar of interface for [iter_edge], working on [FGraph.edge]
   */
  iter_edge_e(iter, t) =
    iter(node) =
      out = (node : FGraph.node).out
      VertexMap.iter(_, edges -> List.iter(iter, edges), out)
    vertices = get_vertices(t)
    VertexMap.iter(_, node -> iter(node), vertices)

  /**
   * Sugar of interface for [fold_edge], working on [FGraph.edge]
   */
  fold_edge_e(fold, t, acc) =
    fold(node, acc) =
      out = (node : FGraph.node).out
      VertexMap.fold(_, edges, acc -> List.fold(fold, edges, acc), out, acc)
    vertices = get_vertices(t)
    VertexMap.fold(_, node, acc -> fold(node, acc), vertices, acc)

  /**
   * {2 Vertex iterators}
   */

  /**
   * Iterate/fold on all successors/predecessors of a vertex. There is no doubloon in iteration, even if there are several
   * edges between 2 vertices.
   */

  iter_succ(iter, t, src) =
    match get_node(t, src) with
    | { none } -> void
    | { some = node } ->
      iter(dst, _) = iter(dst)
      VertexMap.iter(iter, node.out)

  iter_pred(iter, t, dst) =
    match get_node(t, dst) with
    | { none } -> void
    | { some = node } ->
      iter(src, _) = iter(src)
      VertexMap.iter(iter, node.in)

  fold_succ(fold, t, src, acc) =
    match get_node(t, src) with
    | { none } -> void
    | { some = node } ->
      fold(dst, _, acc) = fold(dst, acc)
      VertexMap.fold(fold, node.out, acc)

  fold_pred(fold, t, dst, acc) =
    match get_node(t, dst) with
    | { none } -> void
    | { some = node } ->
      fold(src, _, acc) = fold(src, acc)
      VertexMap.fold(fold, node.in, acc)

  iter_succ_e(iter, t, src) =
    match get_node(t, src) with
    | { none } -> void
    | { some = node } ->
      iter(_, edges) = List.iter(iter, edges)
      VertexMap.iter(iter, node.out)

  iter_pred_e(iter, t, dst) =
    match get_node(t, dst) with
    | { none } -> void
    | { some = node } ->
      iter(_, edges) = List.iter(iter, edges)
      VertexMap.iter(iter, node.in)

  fold_succ_e(fold, t, src, acc) =
    match get_node(t, src) with
    | { none } -> void
    | { some = node } ->
      fold(_, edges, acc) = List.fold(fold, edges, acc)
      VertexMap.fold(fold, node.out, acc)

  fold_pred_e(fold, t, dst, acc) =
    match get_node(t, dst) with
    | { none } -> void
    | { some = node } ->
      fold(_, edges, acc) = List.fold(fold, edges, acc)
      VertexMap.fold(fold, node.in, acc)

  /**
   * If needed, we can add iter_succ_label, fold_succ_label
   */

  /**
   * {2 Add / Remove operations}
   */

  /**
   * The empty persistent graph
   */
  empty = { vertices = VertexMap.empty ; size = 0 } : FGraph.t

  /**
   * Add a vertex to a graph. Initially, this vertex has no neighbor.
   */
  add_vertex(t, vertex) =
    vertices = get_vertices(t)
    if VertexMap.mem(vertex, vertices) then t
    else
      size = t.size + 1
      in = VertexMap.empty
      out = VertexMap.empty
      node = ~{ in out }
      vertices = VertexMap.add(vertex, node, vertices)
      ~{ vertices size }

  /**
   * Remove a vertex from a graph.
   * Remove also all the edges related to this vertex.
   */
  remove_vertex(t, vertex) =
    vertices = get_vertices(t)
    if VertexMap.mem(vertex, vertices)
    then
      remove_edge(edges) =
        fold(v2, edges, acc) =
          filter(edge : FGraph.edge) =
            not(Vertex.equal(vertex, edge.src) || Vertex.equal(vertex, edge.dst))
          edges = List.filter(filter, edges)
          VertexMap.add(v2, edges, acc)
        VertexMap.fold(fold, edges, VertexMap.empty)
      remove_node(node : FGraph.node) =
        in = remove_edge(node.in)
        out = remove_edge(node.out)
        ~{ node with in out }
      vertices =
        fold(v2, node, acc) =
          if Vertex.equal(vertex, v2) then acc
          else
            node = remove_node(node)
            VertexMap.add(v2, node, acc)
        VertexMap.fold(fold, vertices, VertexMap.empty)
      size = t.size - 1
      ~{ vertices size }

    else t

  /**
   * Add a edge to a graph. If the vertices are not yet in the graph, add the vertices too.
   */
  add_edge_e(t, edge) =
    vertices = get_vertices(t)
    src = (edge : FGraph.edge).src
    dst = edge.dst
    size = t.size
    do jlog("add_edge:HOOK-01")

    (size, src_node) =
      match VertexMap.get(src, vertices) with
      | { none } ->
        in = VertexMap.empty
        out = VertexMap.singleton(dst, [edge])
        size = size + 1
        node = ~{ in out }
        (size, node)

      | { some = node } ->
        do jlog("add_edge:HOOK-02")
        edges = VertexMap.get(dst, node.out) ? List.empty
        do jlog("Size before : {List.length(edges)}")
        edges = List.add_uniq(Edge.ordering_label, edge, edges)
        do jlog("Size after : {List.length(edges)}")
        out = VertexMap.add(dst, edges, node.out)
        node = ~{ node with out }
        (size, node)

    (size, dst_node) =
      match VertexMap.get(dst, vertices) with
      | { none } ->
        in = VertexMap.singleton(src, [edge])
        out = VertexMap.empty
        size = size + 1
        node = ~{ in out }
        (size, node)

      | { some = node } ->
        do jlog("add_edge:HOOK-03")
        edges = VertexMap.get(src, node.in) ? List.empty
        do jlog("Size before : {List.length(edges)}")
        edges = List.add_uniq(Edge.ordering_label, edge, edges)
        do jlog("Size after : {List.length(edges)}")
        in = VertexMap.add(src, edges, node.in)
        node = ~{ node with in }
        (size, node)

    vertices = VertexMap.add(src, src_node, vertices)
    vertices = VertexMap.add(dst, dst_node, vertices)
    ~{ vertices size }

  /**
   * A Sugar of interface. See [add_edge_e]
   */
  add_edge(t, src, label, dst) =
    edge = ~{ src label dst }
    add_edge_e(t, edge)

  /**
   * Remove an edge of the graph. If the edge is not in the graph, returns the same graph.
   * FIXME: we can return physically the same graph, if we have the function List.filter_stable.
   * If one of the vertices is not in the graph, the function considers just
   * that the edge is not in the graph (no error).
   */
  remove_edge(t, src, label, dst) =
    vertices = get_vertices(t)
    initial_vertices = vertices

    exists(edge) = Order.equals((edge : FGraph.edge).label, label, X.order_edge_label)

    vertices =
      match VertexMap.get(src, vertices) with
      | { none } -> vertices
      | { some = node } ->
        match VertexMap.get(dst, node.out) with
        | { none } -> vertices
        | { some = edges } ->
          // replace by filter stable
          if List.exists(exists, edges)
          then
            edges = List.remove_p(exists, edges)
            out = VertexMap.add(dst, edges, node.out)
            node = ~{ node with out }
            VertexMap.add(src, node, vertices)
          else vertices

    vertices =
      match VertexMap.get(dst, vertices) with
      | { none } -> vertices
      | { some = node } ->
        match VertexMap.get(src, node.in) with
        | { none } -> vertices
        | { some = edges } ->
          // replace by filter stable
          if List.exists(exists, edges)
          then
            edges = List.remove_p(exists, edges)
            in = VertexMap.add(src, edges, node.in)
            node = ~{ node with in }
            VertexMap.add(dst, node, vertices)
          else vertices

    if vertices === initial_vertices then t
    else
      ~{ t with vertices }

  /**
   * A Sugar of interface. See [remove_edge]
   */
  remove_edge_e(t, edge : FGraph.edge) =
    remove_edge(t, edge.src, edge.label, edge.dst)

  /**
   * Remove all oriented edges between 2 vertices from [src] to [dst].
   * Returns the same graph if not any edges exists between the 2 vertices.
   */
  remove_all_edge(t, src, dst) =
    vertices = get_vertices(t)
    initial_vertices = vertices

    vertices =
      match VertexMap.get(src, vertices) with
      | { none } -> vertices
      | { some = node } ->
        out = VertexMap.add(dst, List.empty, node.out)
        node = ~{ node with out }
        VertexMap.add(src, node, vertices)

    vertices =
      match VertexMap.get(dst, vertices) with
      | { none } -> vertices
      | { some = node } ->
        in = VertexMap.add(src, List.empty, node.in)
        node = ~{ node with in }
        VertexMap.add(dst, node, vertices)

    if vertices === initial_vertices then t
    else
      ~{ t with vertices }


  /**
   * {2 FGraph.Export}
   */
  Export = {{
    to_dot(t) =
      text = Text.cons("# OPA graph : FGraph.Export.to_dot \n")
      text = Text.insert_right(text, "digraph G \{\n")
      fold(vertex : FGraph.vertex, text) =
        label = X.string_of_vertex_label(vertex.label)
        i = vertex.index
        Text.insert_right(text, "{i} [label=\"{label}\"]\n")
      text = fold_vertex(fold, t, text)
      fold(edge : FGraph.edge, text) =
        src = edge.src.index
        dst = edge.dst.index
        label = X.string_of_edge_label(edge.label)
        Text.insert_right(text, "{src} -> {dst} [label=\"{label}\"]\n")
      text = fold_edge_e(fold, t, text)
      text = Text.insert_right(text, "}\n")
      Text.to_string(text)

    to_xhtml(t) =
      dot = to_dot(t)
      Dot.to_xhtml(dot)
  }}

}}
