# adjacency.rb
#
# $Id$
#
# The DirectedAdjacencyGraph class implements a generalized adjacency list
# graph structure. An AdjacencyGraph is basically a two-dimensional structure
# (ie, a list of lists). Each element of the first dimension represents a
# vertex. Each of the vertices contains a one-dimensional structure that is
# the list of all adjacent vertices.
#
# The class for representing the adjacency list of a vertex is, by default, a
# Set. This can be configured by the client, however, when an AdjacencyGraph
# is created.

require 'rgl/mutable'
require 'set'

module RGL

  class DirectedAdjacencyGraph

    include MutableGraph

    # Shortcut for creating a DirectedAdjacencyGraph:
    #
    #  RGL::DirectedAdjacencyGraph[1,2, 2,3, 2,4, 4,5].edges.to_a.to_s =>
    #    "(1-2)(2-3)(2-4)(4-5)"
    #
    def self.[](*a)
      result = new
      0.step(a.size - 1, 2) { |i| result.add_edge(a[i], a[i + 1]) }
      result
    end

    # Returns a new empty DirectedAdjacencyGraph which has as its edgelist
    # class the given class. The default edgelist class is Set, to ensure
    # set semantics for edges and vertices.
    #
    # If other graphs are passed as parameters their vertices and edges are
    # added to the new graph.
    #
    def initialize(edgelist_class = Set, *other_graphs)
      @edgelist_class = edgelist_class
      @vertices_dict = Hash.new
      other_graphs.each do |g|
        g.each_vertex { |v| add_vertex v }
        g.each_edge { |v, w| add_edge v, w }
      end
    end

    # Copy internal vertices_dict
    #
    def initialize_copy(orig)
      @vertices_dict = orig.instance_eval { @vertices_dict }.dup
      @vertices_dict.keys.each do |v|
        @vertices_dict[v] = @vertices_dict[v].dup
      end
    end

    # Iterator for the keys of the vertices list hash.
    #
    def each_vertex(&b)
      @vertices_dict.each_key(&b)
    end

    def each_adjacent(v, &b) # :nodoc:
      adjacency_list = (@vertices_dict[v] or raise NoVertexError, "No vertex #{v}.")
      adjacency_list.each(&b)
    end

    # Returns true.
    #
    def directed?
      true
    end

    # Complexity is O(1), because the vertices are kept in a Hash containing
    # as values the lists of adjacent vertices of _v_.
    #
    def has_vertex? (v)
      @vertices_dict.has_key?(v)
    end

    # Complexity is O(1), if a Set is used as adjacency list. Otherwise,
    # complexity is O(out_degree(v)).
    #
    # ---
    # MutableGraph interface.
    #
    def has_edge? (u, v)
      has_vertex?(u) && @vertices_dict[u].include?(v)
    end

    # See MutableGraph#add_vertex.
    #
    # If the vertex is already in the graph (using eql?), the method does
    # nothing.
    #
    def add_vertex(v)
      @vertices_dict[v] ||= @edgelist_class.new
    end

    # See MutableGraph#add_edge.
    #
    def add_edge(u, v)
      add_vertex(u) # ensure key
      add_vertex(v) # ensure key
      basic_add_edge(u, v)
    end

    # See MutableGraph#remove_vertex.
    #
    def remove_vertex(v)
      @vertices_dict.delete(v)

      # remove v from all adjacency lists
      @vertices_dict.each_value { |adjList| adjList.delete(v) }
    end

    # See MutableGraph::remove_edge.
    #
    def remove_edge(u, v)
      @vertices_dict[u].delete(v) unless @vertices_dict[u].nil?
    end

    # Converts the adjacency list of each vertex to be of type _klass_. The
    # class is expected to have a new constructor which accepts an enumerable as
    # parameter.
    #
    def edgelist_class=(klass)
      @vertices_dict.keys.each do |v|
        @vertices_dict[v] = klass.new @vertices_dict[v].to_a
      end
    end

    protected

    def basic_add_edge(u, v)
      @vertices_dict[u].add(v)
    end

  end # class DirectedAdjacencyGraph

  # AdjacencyGraph is an undirected Graph. The methods add_edge and
  # remove_edge are reimplemented: if an edge (u,v) is added or removed,
  # then the reverse edge (v,u) is also added or removed.
  #
  class AdjacencyGraph < DirectedAdjacencyGraph

    # Always returns false.
    #
    def directed?
      false
    end

    # Also removes (v,u)
    #
    def remove_edge(u, v)
      super
      @vertices_dict[v].delete(u) unless @vertices_dict[v].nil?
    end

    protected

    def basic_add_edge(u, v)
      super
      @vertices_dict[v].add(u) # Insert backwards edge
    end

  end # class AdjacencyGraph

  module Graph

    # Convert a general graph to an AdjacencyGraph. If the graph is directed,
    # returns a DirectedAdjacencyGraph; otherwise, returns an AdjacencyGraph.
    #
    def to_adjacency
      result = (directed? ? DirectedAdjacencyGraph : AdjacencyGraph).new
      each_vertex { |v| result.add_vertex(v) }
      each_edge { |u, v| result.add_edge(u, v) }
      result
    end

    # Return a new DirectedAdjacencyGraph which has the same set of vertices.
    # If (u,v) is an edge of the graph, then (v,u) is an edge of the result.
    #
    # If the graph is undirected, the result is self.
    #
    def reverse
      return self unless directed?
      result = DirectedAdjacencyGraph.new
      each_vertex { |v| result.add_vertex v }
      each_edge { |u, v| result.add_edge(v, u) }
      result
    end

    # Return a new AdjacencyGraph which has the same set of vertices. If (u,v)
    # is an edge of the graph, then (u,v) and (v,u) (which are the same edges)
    # are edges of the result.
    #
    # If the graph is undirected, the result is self.
    #
    def to_undirected
      return self unless directed?
      AdjacencyGraph.new(Set, self)
    end

  end # module Graph

end # module RGL
