#--
# Copyright (c) 2006 Shawn Patrick Garbett
# Copyright (c) 2002,2004,2005 by Horst Duchene
# 
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
# 
#     * Redistributions of source code must retain the above copyright notice(s),
#       this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above copyright notice,
#       this list of conditions and the following disclaimer in the documentation
#       and/or other materials provided with the distribution.
#     * Neither the name of the Shawn Garbett nor the names of its contributors
#       may be used to endorse or promote products derived from this software
#       without specific prior written permission.
# 
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#++


require 'puppet/external/gratr/edge'
require 'puppet/external/gratr/labels'
require 'puppet/external/gratr/graph_api'

module GRATR
  
  # Using the functions required by the GraphAPI, it implements all the
  # basic functions of a Graph class by using only functions in GraphAPI.
  # An actual implementation still needs to be done, as in Digraph or
  # UndirectedGraph.
  module Graph
    include Enumerable
    include Labels
    include GraphAPI
      
    # Non destructive version of add_vertex!, returns modified copy of Graph
    def add_vertex(v, l=nil) x=self.class.new(self); x.add_vertex!(v,l); end
      
    # Non destructive version add_edge!, returns modified copy of Graph  
    def add_edge(u, v=nil, l=nil) x=self.class.new(self); x.add_edge!(u,v,l); end
      
    # Non destructive version of remove_vertex!, returns modified copy of Graph
    def remove_vertex(v) x=self.class.new(self); x.remove_vertex!(v); end  

    # Non destructive version of remove_edge!, returns modified copy of Graph
    def remove_edge(u,v=nil) x=self.class.new(self); x.remove_edge!(u,v); end
      
    # Return Array of adjacent portions of the Graph
    #  x can either be a vertex an edge.
    #  options specifies parameters about the adjacency search
    #   :type can be either :edges or :vertices (default).
    #   :direction can be :in, :out(default) or :all.
    #
    # Note: It is probably more efficently done in implementation.
    def adjacent(x, options={})
      d = directed? ? (options[:direction] || :out) : :all

      # Discharge the easy ones first
      return [x.source] if x.kind_of? Edge and options[:type] == :vertices and d == :in
      return [x.target] if x.kind_of? Edge and options[:type] == :vertices and d == :out
      return [x.source, x.target] if x.kind_of? Edge and options[:type] != :edges and d == :all

      (options[:type] == :edges ? edges : to_a).select {|u| adjacent?(x,u,d)}
    end

    # Add all objects in _a_ to the vertex set.
    def add_vertices!(*a) a.each {|v| add_vertex! v}; self; end
      
    # See add_vertices!

    def add_vertices(*a) x=self.class.new(self); x.add_vertices(*a); self; end

    # Add all edges in the _edges_ Enumerable to the edge set.  Elements of the
    # Enumerable can be both two-element arrays or instances of DirectedEdge or
    # UnDirectedEdge. 
    def add_edges!(*args) args.each { |edge| add_edge!(edge) }; self; end
      
    # See add_edge!
    def add_edges(*a) x=self.class.new(self); x.add_edges!(*a); self; end

    # Remove all vertices specified by the Enumerable a from the graph by
    # calling remove_vertex!.
    def remove_vertices!(*a) a.each { |v| remove_vertex! v }; end
      
    # See remove_vertices!
    def remove_vertices(*a) x=self.class.new(self); x.remove_vertices(*a); end

    # Remove all vertices edges by the Enumerable a from the graph by
    # calling remove_edge!
    def remove_edges!(*a) a.each { |e| remove_edges! e }; end

    # See remove_edges
    def remove_edges(*a) x=self.class.new(self); x.remove_edges(*a); end

    # Execute given block for each vertex, provides for methods in Enumerable
    def each(&block) vertices.each(&block); end

    # Returns true if _v_ is a vertex of the graph.
    # This is a default implementation that is of O(n) average complexity.
    # If a subclass uses a hash to store vertices, then this can be
    # made into an O(1) average complexity operation.
    def vertex?(v) vertices.include?(v); end  
    
    # Returns true if u or (u,v) is an Edge of the graph.
    def edge?(*arg) edges.include?(edge_convert(*args)); end  

    # Tests two objects to see if they are adjacent.
    # direction parameter specifies direction of adjacency, :in, :out, or :all(default)
    # All denotes that if there is any adjacency, then it will return true.
    # Note that the default is different than adjacent where one is primarily concerned with finding
    # all adjacent objects in a graph to a given object. Here the concern is primarily on seeing
    # if two objects touch. For vertexes, any edge between the two will usually do, but the direction
    # can be specified if need be.
    def adjacent?(source, target, direction=:all)
      if source.kind_of? GRATR::Edge
        raise NoEdgeError unless edge? source
        if target.kind_of? GRATR::Edge
          raise NoEdgeError unless edge? target
          (direction != :out and source.source == target.target) or (direction != :in and source.target == target.source)
        else
          raise NoVertexError unless vertex? target
          (direction != :out and source.source == target)  or (direction != :in and source.target == target)
        end
      else
        raise NoVertexError unless vertex? source
        if target.kind_of? GRATR::Edge
          raise NoEdgeError unless edge? target
          (direction != :out and source == target.target) or (direction != :in and source == target.source)
        else
          raise NoVertexError unless vertex? target
          (direction != :out and edge?(target,source)) or (direction != :in and edge?(source,target))
        end
      end
    end

    # Returns true if the graph has no vertex, i.e. num_vertices == 0.
    def empty?() vertices.size.zero?; end

    # Returns true if the given object is a vertex or Edge in the Graph.
    # 
    def include?(x) x.kind_of?(GRATR::Edge) ? edge?(x) : vertex?(x); end

    # Returns the neighboorhood of the given vertex (or Edge)
    # This is equivalent to adjacent, but bases type on the type of object.
    # direction can be :all, :in, or :out 
    def neighborhood(x, direction = :all)
      adjacent(x, :direction => direction, :type => ((x.kind_of? GRATR::Edge) ? :edges : :vertices )) 
    end
    
    # Union of all neighborhoods of vertices (or edges) in the Enumerable x minus the contents of x
    # Definition taken from Jorgen Bang-Jensen, Gregory Gutin, _Digraphs: Theory, Algorithms and Applications_, pg 4
    def set_neighborhood(x, direction = :all)
      x.inject(Set.new) {|a,v| a.merge(neighborhood(v,direction))}.reject {|v2| x.include?(v2)}
    end  
    
    # Union of all set_neighborhoods reachable in p edges
    # Definition taken from Jorgen Bang-Jensen, Gregory Gutin, _Digraphs: Theory, Algorithms and Applications_, pg 46
    def closed_pth_neighborhood(w,p,direction=:all)
      if p <= 0
        w 
      elsif p == 1
        (w + set_neighborhood(w,direction)).uniq
      else
        n = set_neighborhood(w, direction)
        (w + n + closed_pth_neighborhood(n,p-1,direction)).uniq
      end
    end
    
    # Returns the neighboorhoods reachable in p steps from every vertex (or edge)
    # in the Enumerable x        
    # Definition taken from Jorgen Bang-Jensen, Gregory Gutin, _Digraphs: Theory, Algorithms and Applications_, pg 46
    def open_pth_neighborhood(x, p, direction=:all)
      if    p <= 0
        x
      elsif p == 1
        set_neighborhood(x,direction)
      else  
        set_neighborhood(open_pth_neighborhood(x, p-1, direction),direction) - closed_pth_neighborhood(x,p-1,direction)
      end    
    end
    
    # Returns the number of out-edges (for directed graphs) or the number of
    # incident edges (for undirected graphs) of vertex _v_.
    def out_degree(v) adjacent(v, :direction => :out).size; end

    # Returns the number of in-edges (for directed graphs) or the number of
    # incident edges (for undirected graphs) of vertex _v_.
    def in_degree(v)  adjacent(v, :direction => :in ).size; end

    # Returns the sum of the number in and out edges for a vertex
    def degree(v) in_degree(v) + out_degree(v); end

    # Minimum in-degree 
    def min_in_degree() to_a.map {|v| in_degree(v)}.min; end

    # Minimum out-degree
    def min_out_degree() to_a.map {|v| out_degree(v)}.min; end

    # Minimum degree of all vertexes
    def min_degree() [min_in_degree, min_out_degree].min; end

    # Maximum in-degree 
    def max_in_degree() vertices.map {|v| in_degree(v)}.max; end

    # Maximum out-degree
    def max_out_degree() vertices.map {|v| out_degree(v)}.max; end

    # Minimum degree of all vertexes
    def max_degree() [max_in_degree, max_out_degree].max; end

    # Regular
    def regular?() min_degree == max_degree; end

    # Returns the number of vertices.
    def size()         vertices.size; end

    # Synonym for size.
    def num_vertices() vertices.size; end

    # Returns the number of edges.
    def num_edges()    edges.size; end

    # Utility method to show a string representation of the edges of the graph.
    def to_s() edges.to_s; end

    # Equality is defined to be same set of edges and directed?
    def eql?(g) 
      return false unless g.kind_of? GRATR::Graph

      (g.directed?   == self.directed?)  and 
      (vertices.sort == g.vertices.sort) and
      (g.edges.sort  == edges.sort)
    end

    # Synonym for eql?
    def ==(rhs) eql?(rhs); end

    # Merge another graph into this one
    def merge(other)
      other.vertices.each {|v| add_vertex!(v)      }
      other.edges.each    {|e| add_edge!(e)         }
      other.edges.each    {|e| add_edge!(e.reverse) } if directed? and !other.directed? 
      self 
    end

    # A synonym for merge, that doesn't modify the current graph
    def +(other)
      result = self.class.new(self)
      case other
        when GRATR::Graph : result.merge(other)
        when GRATR::Edge  : result.add_edge!(other)
        else              result.add_vertex!(other)
      end
    end

    # Remove all vertices in the specified right hand side graph
    def -(other)
      case  other
        when GRATR::Graph : induced_subgraph(vertices - other.vertices)
        when GRATR::Edge  : self.class.new(self).remove_edge!(other)
        else              self.class.new(self).remove_vertex!(other)
      end
    end

    # A synonym for add_edge!
    def <<(edge) add_edge!(edge); end

    # Return the complement of the current graph
    def complement
      vertices.inject(self.class.new) do |a,v|
        a.add_vertex!(v)
        vertices.each {|v2| a.add_edge!(v,v2) unless edge?(v,v2) }; a
      end
    end

    # Given an array of vertices return the induced subgraph
    def induced_subgraph(v)
      edges.inject(self.class.new) do |a,e| 
        ( v.include?(e.source) and v.include?(e.target) ) ?  (a << e) : a
      end;
    end
    
    def inspect
      l = vertices.select {|v| self[v]}.map {|u| "vertex_label_set(#{u.inspect},#{self[u].inspect})"}.join('.')
      self.class.to_s + '[' + edges.map {|e| e.inspect}.join(', ') + ']' + (l ? '.'+l : '')
    end
    
   private
    def edge_convert(*args) args[0].kind_of?(GRATR::Edge) ? args[0] : edge_class[*args]; end
    

  end # Graph

end # GRATR


syntax highlighted by Code2HTML, v. 0.9.1