require 'ipaddress/prefix'

module IPAddress; 
  # 
  # =Name
  # 
  # IPAddress::IPv4 - IP version 4 address manipulation library
  #
  # =Synopsis
  #
  #    require 'ipaddress'
  #
  # =Description
  # 
  # Class IPAddress::IPv4 is used to handle IPv4 type addresses. 
  #
  class IPv4
    
    include IPAddress
    include Enumerable  
    include Comparable                  
    
    #
    # This Hash contains the prefix values for Classful networks
    #
    # Note that classes C, D and E will all have a default 
    # prefix of /24 or 255.255.255.0
    #
    CLASSFUL = {
      /^0../ => 8,  # Class A, from 0.0.0.0 to 127.255.255.255
      /^10./ => 16, # Class B, from 128.0.0.0 to 191.255.255.255
      /^110/ => 24  # Class C, D and E, from 192.0.0.0 to 255.255.255.254
    }

    #
    # Regular expression to match an IPv4 address
    #
    REGEXP = Regexp.new(/((25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)\.){3}(25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)/)
    
    #
    # Creates a new IPv4 address object.
    #
    # An IPv4 address can be expressed in any of the following forms:
    # 
    # * "10.1.1.1/24": ip +address+ and +prefix+. This is the common and
    # suggested way to create an object                  .
    # * "10.1.1.1/255.255.255.0": ip +address+ and +netmask+. Although
    # convenient sometimes, this format is less clear than the previous
    # one.             
    # * "10.1.1.1": if the address alone is specified, the prefix will be
    # set as default 32, also known as the host prefix    
    #
    # Examples:
    #
    #   # These two are the same
    #   ip = IPAddress::IPv4.new("10.0.0.1/24")
    #   ip = IPAddress("10.0.0.1/24")
    #   
    #   # These two are the same
    #   IPAddress::IPv4.new "10.0.0.1/8"
    #   IPAddress::IPv4.new "10.0.0.1/255.0.0.0"
    #
    def initialize(str)
      ip, netmask = str.split("/")
      
      # Check the ip and remove white space
      if IPAddress.valid_ipv4?(ip)
        @address = ip.strip
      else
        raise ArgumentError, "Invalid IP #{ip.inspect}"
      end
      
      # Check the netmask
      if netmask  # netmask is defined
        netmask.strip!
        if netmask =~ /^\d{1,2}$/  # netmask in cidr format 
          @prefix = Prefix32.new(netmask.to_i)
        elsif IPAddress.valid_ipv4_netmask?(netmask)  # netmask in IP format
          @prefix = Prefix32.parse_netmask(netmask)
        else  # invalid netmask
          raise ArgumentError, "Invalid netmask #{netmask}"
        end
      else  # netmask is nil, reverting to defaul classful mask
        @prefix = Prefix32.new(32)
      end

      # Array formed with the IP octets
      @octets = @address.split(".").map{|i| i.to_i}
      # 32 bits interger containing the address
      @u32 = (@octets[0]<< 24) + (@octets[1]<< 16) + (@octets[2]<< 8) + (@octets[3])
      
    end # def initialize

    #
    # Returns the address portion of the IPv4 object
    # as a string.
    #
    #   ip = IPAddress("172.16.100.4/22")
    #
    #   ip.address
    #     #=> "172.16.100.4"
    #
    def address
      @address
    end

    #
    # Returns the prefix portion of the IPv4 object
    # as a IPAddress::Prefix32 object
    #
    #   ip = IPAddress("172.16.100.4/22")
    #
    #   ip.prefix
    #     #=> 22
    #
    #   ip.prefix.class
    #     #=> IPAddress::Prefix32
    #
    def prefix
      @prefix
    end

    #
    # Set a new prefix number for the object
    #
    # This is useful if you want to change the prefix
    # to an object created with IPv4::parse_u32 or
    # if the object was created using the classful
    # mask.
    #
    #   ip = IPAddress("172.16.100.4")
    #
    #   puts ip
    #     #=> 172.16.100.4/16
    #   
    #   ip.prefix = 22
    #
    #   puts ip
    #     #=> 172.16.100.4/22
    #
    def prefix=(num)
      @prefix = Prefix32.new(num)
    end

    # 
    # Returns the address as an array of decimal values
    #
    #   ip = IPAddress("172.16.100.4")
    #
    #   ip.octets
    #     #=> [172, 16, 100, 4]
    #
    def octets
      @octets
    end
    
    #
    # Returns a string with the address portion of 
    # the IPv4 object
    #
    #   ip = IPAddress("172.16.100.4/22")
    #
    #   ip.to_s
    #     #=> "172.16.100.4"
    #
    def to_s
      @address
    end

    #
    # Returns a string with the IP address in canonical
    # form.
    #
    #   ip = IPAddress("172.16.100.4/22")
    #
    #   ip.to_string
    #     #=> "172.16.100.4/22"
    #
    def to_string
      "#@address/#@prefix"
    end

    # 
    # Returns the prefix as a string in IP format
    #
    #   ip = IPAddress("172.16.100.4/22")
    #
    #   ip.netmask
    #     #=> "255.255.252.0"
    #
    def netmask
      @prefix.to_ip
    end

    #
    # Like IPv4#prefix=, this method allow you to 
    # change the prefix / netmask of an IP address
    # object.
    #
    #   ip = IPAddress("172.16.100.4")
    #
    #   puts ip
    #     #=> 172.16.100.4/16
    #
    #   ip.netmask = "255.255.252.0"
    #
    #   puts ip
    #     #=> 172.16.100.4/22
    #
    def netmask=(addr)
      @prefix = Prefix32.parse_netmask(addr)
    end

    #
    # Returns the address portion in unsigned
    # 32 bits integer format.
    #
    # This method is identical to the C function
    # inet_pton to create a 32 bits address family 
    # structure. 
    #
    #   ip = IPAddress("10.0.0.0/8")
    #
    #   ip.to_i
    #     #=> 167772160
    #
    def u32
      @u32
    end
    alias_method :to_i, :u32
    alias_method :to_u32, :u32
    
    #
    # Returns the address portion of an IPv4 object
    # in a network byte order format.
    #
    #   ip = IPAddress("172.16.10.1/24")
    #
    #   ip.data
    #     #=> "\254\020\n\001"
    #
    # It is usually used to include an IP address
    # in a data packet to be sent over a socket
    #
    #   a = Socket.open(params) # socket details here
    #   ip = IPAddress("10.1.1.0/24")
    #   binary_data = ["Address: "].pack("a*") + ip.data 
    #   
    #   # Send binary data
    #   a.puts binary_data
    #
    def data
      [@u32].pack("N")
    end

    #
    # Returns the octet specified by index
    #
    #   ip = IPAddress("172.16.100.50/24")
    #
    #   ip[0]
    #     #=> 172
    #   ip[1]
    #     #=> 16
    #   ip[2]
    #     #=> 100
    #   ip[3]
    #     #=> 50
    #
    def [](index)
      @octets[index]
    end
    alias_method :octet, :[]
    
    #
    # Returns the address portion of an IP in binary format,
    # as a string containing a sequence of 0 and 1
    #
    #   ip = IPAddress("127.0.0.1")
    #
    #   ip.bits
    #     #=> "01111111000000000000000000000001"
    #
    def bits
      data.unpack("B*").first
    end

    #
    # Returns the broadcast address for the given IP.
    #
    #   ip = IPAddress("172.16.10.64/24")
    #
    #   ip.broadcast.to_s
    #     #=> "172.16.10.255"
    #
    def broadcast
      self.class.parse_u32(broadcast_u32, @prefix)
    end
    
    #
    # Checks if the IP address is actually a network
    #
    #   ip = IPAddress("172.16.10.64/24")
    #
    #   ip.network?
    #     #=> false
    # 
    #   ip = IPAddress("172.16.10.64/26")
    #
    #   ip.network?
    #     #=> true
    #
    def network?
      @u32 | @prefix.to_u32 == @prefix.to_u32
    end

    #
    # Returns a new IPv4 object with the network number 
    # for the given IP.
    #
    #   ip = IPAddress("172.16.10.64/24")
    #
    #   ip.network.to_s
    #     #=> "172.16.10.0"
    #
    def network
      self.class.parse_u32(network_u32, @prefix)
    end

    #
    # Returns a new IPv4 object with the
    # first host IP address in the range.
    # 
    # Example: given the 192.168.100.0/24 network, the first
    # host IP address is 192.168.100.1.
    #
    #   ip = IPAddress("192.168.100.0/24")
    #
    #   ip.first.to_s
    #     #=> "192.168.100.1"
    #
    # The object IP doesn't need to be a network: the method
    # automatically gets the network number from it
    #
    #   ip = IPAddress("192.168.100.50/24")
    #
    #   ip.first.to_s
    #     #=> "192.168.100.1"
    #
    def first
      self.class.parse_u32(network_u32+1, @prefix)
    end

    #
    # Like its sibling method IPv4#first, this method 
    # returns a new IPv4 object with the 
    # last host IP address in the range.
    # 
    # Example: given the 192.168.100.0/24 network, the last
    # host IP address is 192.168.100.254
    #
    #   ip = IPAddress("192.168.100.0/24")
    #
    #   ip.last.to_s
    #     #=> "192.168.100.254"
    #
    # The object IP doesn't need to be a network: the method
    # automatically gets the network number from it
    #
    #   ip = IPAddress("192.168.100.50/24")
    #
    #   ip.last.to_s
    #     #=> "192.168.100.254"
    #
    def last
      self.class.parse_u32(broadcast_u32-1, @prefix)
    end

    #
    # Iterates over all the hosts IP addresses for the given
    # network (or IP address).
    #
    #   ip = IPAddress("10.0.0.1/29")
    #
    #   ip.each_host do |i|
    #     p i.to_s
    #   end
    #     #=> "10.0.0.1"
    #     #=> "10.0.0.2"
    #     #=> "10.0.0.3"
    #     #=> "10.0.0.4"
    #     #=> "10.0.0.5"
    #     #=> "10.0.0.6"
    #
    def each_host
      (network_u32+1..broadcast_u32-1).each do |i|
        yield self.class.parse_u32(i, @prefix)
      end
    end

    #
    # Iterates over all the IP addresses for the given
    # network (or IP address).
    #
    # The object yielded is a new IPv4 object created
    # from the iteration.
    #
    #   ip = IPAddress("10.0.0.1/29")
    #
    #   ip.each do |i|
    #     p i.address
    #   end
    #     #=> "10.0.0.0"
    #     #=> "10.0.0.1"
    #     #=> "10.0.0.2"
    #     #=> "10.0.0.3"
    #     #=> "10.0.0.4"
    #     #=> "10.0.0.5"
    #     #=> "10.0.0.6"
    #     #=> "10.0.0.7"
    #
    def each
      (network_u32..broadcast_u32).each do |i|
        yield self.class.parse_u32(i, @prefix)
      end
    end

    #
    # Spaceship operator to compare IPv4 objects
    #
    # Comparing IPv4 addresses is useful to ordinate
    # them into lists that match our intuitive 
    # perception of ordered IP addresses.
    # 
    # The first comparison criteria is the u32 value.
    # For example, 10.100.100.1 will be considered 
    # to be less than 172.16.0.1, because, in a ordered list,
    # we expect 10.100.100.1 to come before 172.16.0.1.
    #
    # The second criteria, in case two IPv4 objects 
    # have identical addresses, is the prefix. An higher
    # prefix will be considered greater than a lower
    # prefix. This is because we expect to see
    # 10.100.100.0/24 come before 10.100.100.0/25.
    #
    # Example:
    #
    #   ip1 = IPAddress "10.100.100.1/8"
    #   ip2 = IPAddress "172.16.0.1/16"
    #   ip3 = IPAddress "10.100.100.1/16"
    #
    #   ip1 < ip2
    #     #=> true
    #   ip1 > ip3
    #     #=> false
    #
    #   [ip1,ip2,ip3].sort.map{|i| i.to_string}
    #     #=> ["10.100.100.1/8","10.100.100.1/16","172.16.0.1/16"]
    #
    def <=>(oth)
      return prefix <=> oth.prefix if to_u32 == oth.to_u32  
      to_u32 <=> oth.to_u32
    end
    
    #
    # Returns the number of IP addresses included
    # in the network. It also counts the network
    # address and the broadcast address.
    #
    #   ip = IPAddress("10.0.0.1/29")
    #
    #   ip.size
    #     #=> 8
    #
    def size
      2 ** @prefix.host_prefix
    end

    #
    # Returns an array with the IP addresses of
    # all the hosts in the network.
    # 
    #   ip = IPAddress("10.0.0.1/29")
    #
    #   ip.hosts.map {|i| i.address}
    #     #=> ["10.0.0.1",
    #     #=>  "10.0.0.2",
    #     #=>  "10.0.0.3",
    #     #=>  "10.0.0.4",
    #     #=>  "10.0.0.5",
    #     #=>  "10.0.0.6"]
    #
    def hosts
      to_a[1..-2]
    end
    
    #
    # Returns the network number in Unsigned 32bits format
    #
    #   ip = IPAddress("10.0.0.1/29")
    #
    #   ip.network_u32
    #     #=> 167772160
    #
    def network_u32
      @u32 & @prefix.to_u32
    end

    #
    # Returns the broadcast address in Unsigned 32bits format
    #
    #   ip = IPaddress("10.0.0.1/29")
    #
    #   ip.broadcast_u32
    #     #=> 167772167
    #
    def broadcast_u32
      network_u32 + size - 1
    end

    #
    # Checks whether a subnet includes the given IP address.
    #
    # Accepts an IPAddress::IPv4 object.
    #
    #   ip = IPAddress("192.168.10.100/24")
    #
    #   addr = IPAddress("192.168.10.102/24")
    #
    #   ip.include? addr
    #     #=> true
    #
    #   ip.include? IPAddress("172.16.0.48/16")
    #     #=> false
    #
    def include?(oth)
      @prefix <= oth.prefix and network_u32 == (oth.to_u32 & @prefix.to_u32)
    end

    #
    # Checks whether a subnet includes all the 
    # given IPv4 objects.
    #
    #   ip = IPAddress("192.168.10.100/24")
    #
    #   addr1 = IPAddress("192.168.10.102/24")
    #   addr2 = IPAddress("192.168.10.103/24")
    #
    #   ip.include_all?(addr1,addr2)
    #     #=> true
    #
    def include_all?(*others)
      others.all? {|oth| include?(oth)}
    end
    
    #
    # Checks if an IPv4 address objects belongs
    # to a private network RFC1918
    #
    # Example:
    #
    #   ip = IPAddress "10.1.1.1/24"
    #   ip.private?
    #     #=> true
    #
    def private?
      [self.class.new("10.0.0.0/8"),
       self.class.new("172.16.0.0/12"),
       self.class.new("192.168.0.0/16")].any? {|i| i.include? self}
    end

    #
    # Returns the IP address in in-addr.arpa format
    # for DNS lookups
    #
    #   ip = IPAddress("172.16.100.50/24")
    #
    #   ip.reverse
    #     #=> "50.100.16.172.in-addr.arpa"
    #
    def reverse
      @octets.reverse.join(".") + ".in-addr.arpa"
    end
    alias_method :arpa, :reverse
    
    #
    # Splits a network into different subnets
    #
    # If the IP Address is a network, it can be divided into
    # multiple networks. If +self+ is not a network, this
    # method will calculate the network from the IP and then
    # subnet it.
    #
    # If +subnets+ is an power of two number, the resulting 
    # networks will be divided evenly from the supernet.
    #
    #   network = IPAddress("172.16.10.0/24")
    #
    #   network / 4   # implies map{|i| i.to_string}
    #     #=> ["172.16.10.0/26",
    #          "172.16.10.64/26",
    #          "172.16.10.128/26",
    #          "172.16.10.192/26"]
    #
    # If +num+ is any other number, the supernet will be 
    # divided into some networks with a even number of hosts and
    # other networks with the remaining addresses.
    #
    #   network = IPAddress("172.16.10.0/24")
    #
    #   network / 3   # implies map{|i| i.to_string}
    #     #=> ["172.16.10.0/26",
    #          "172.16.10.64/26",
    #          "172.16.10.128/25"]
    #
    # Returns an array of IPv4 objects
    #
    def split(subnets=2)
      unless (1..(2**@prefix.host_prefix)).include? subnets
        raise ArgumentError, "Value #{subnets} out of range" 
      end
      networks = subnet(newprefix(subnets))
      until networks.size == subnets
        networks = sum_first_found(networks)
      end
      return networks
    end
    alias_method :/, :split

    #
    # Returns a new IPv4 object from the supernetting
    # of the instance network.
    #
    # Supernetting is similar to subnetting, except
    # that you getting as a result a network with a
    # smaller prefix (bigger host space). For example,
    # given the network
    #
    #   ip = IPAddress("172.16.10.0/24")
    #
    # you can supernet it with a new /23 prefix
    #
    #   ip.supernet(23).to_string
    #     #=> "172.16.10.0/23"
    #
    # However if you supernet it with a /22 prefix, the
    # network address will change:
    #
    #   ip.supernet(22).to_string
    #     #=> "172.16.8.0/22"
    #
    # If +new_prefix+ is less than 1, returns 0.0.0.0/0
    #
    def supernet(new_prefix)
      raise ArgumentError, "New prefix must be smaller than existing prefix" if new_prefix >= @prefix.to_i
      return self.class.new("0.0.0.0/0") if new_prefix < 1
      return self.class.new(@address+"/#{new_prefix}").network
    end

    #
    # This method implements the subnetting function 
    # similar to the one described in RFC3531.
    #
    # By specifying a new prefix, the method calculates
    # the network number for the given IPv4 object
    # and calculates the subnets associated to the new
    # prefix.
    #
    # For example, given the following network:
    #
    #   ip = IPAddress "172.16.10.0/24"
    #
    # we can calculate the subnets with a /26 prefix
    #
    #   ip.subnets(26).map{&:to_string)
    #     #=> ["172.16.10.0/26", "172.16.10.64/26", 
    #          "172.16.10.128/26", "172.16.10.192/26"]
    #
    # The resulting number of subnets will of course always be
    # a power of two.
    #
    def subnet(subprefix)
      unless ((@prefix.to_i)..32).include? subprefix
        raise ArgumentError, "New prefix must be between #@prefix and 32"
      end
      Array.new(2**(subprefix-@prefix.to_i)) do |i|
        self.class.parse_u32(network_u32+(i*(2**(32-subprefix))), subprefix)
      end
    end

    #
    # Returns the difference between two IP addresses
    # in unsigned int 32 bits format
    #  
    # Example:
    #
    #   ip1 = IPAddress("172.16.10.0/24")
    #   ip2 = IPAddress("172.16.11.0/24")
    #
    #   puts ip1 - ip2
    #     #=> 256
    #
    def -(oth)
      return (to_u32 - oth.to_u32).abs
    end

    #
    # Returns a new IPv4 object which is the result 
    # of the summarization, if possible, of the two 
    # objects
    #
    # Example:
    #
    #   ip1 = IPAddress("172.16.10.1/24")
    #   ip2 = IPAddress("172.16.11.2/24")
    #
    #   p (ip1 + ip2).map {|i| i.to_string}
    #     #=> ["172.16.10.0/23"]
    #
    # If the networks are not contiguous, returns
    # the two network numbers from the objects
    #
    #   ip1 = IPAddress("10.0.0.1/24")
    #   ip2 = IPAddress("10.0.2.1/24")
    #
    #   p (ip1 + ip2).map {|i| i.to_string}
    #     #=> ["10.0.0.0/24","10.0.2.0/24"]
    #
    def +(oth)
      aggregate(*[self,oth].sort.map{|i| i.network})
    end

    #
    # Checks whether the ip address belongs to a 
    # RFC 791 CLASS A network, no matter
    # what the subnet mask is.
    #
    # Example:
    # 
    #   ip = IPAddress("10.0.0.1/24")
    #
    #   ip.a?
    #     #=> true
    #
    def a?
      CLASSFUL.key(8) === bits
    end
    
    #
    # Checks whether the ip address belongs to a
    # RFC 791 CLASS B network, no matter
    # what the subnet mask is.
    #
    # Example:
    #
    #   ip = IPAddress("172.16.10.1/24")
    #
    #   ip.b?
    #     #=> true
    #
    def b?
      CLASSFUL.key(16) === bits
    end

    #
    # Checks whether the ip address belongs to a
    # RFC 791 CLASS C network, no matter
    # what the subnet mask is.
    #
    # Example:
    #
    #   ip = IPAddress("192.168.1.1/30")
    #
    #   ip.c?
    #     #=> true
    #
    def c?
      CLASSFUL.key(24) === bits
    end

    #
    # Return the ip address in a format compatible
    # with the IPv6 Mapped IPv4 addresses
    # 
    # Example:
    #
    #   ip = IPAddress("172.16.10.1/24")
    #
    #   ip.to_ipv6
    #     #=> "ac10:0a01"
    #
    def to_ipv6
      "%.4x:%.4x" % [to_u32].pack("N").unpack("nn")
    end

    #
    # Creates a new IPv4 object from an
    # unsigned 32bits integer.
    #
    #   ip = IPAddress::IPv4::parse_u32(167772160)
    #
    #   ip.prefix = 8
    #   ip.to_string
    #     #=> "10.0.0.0/8"
    #
    # The +prefix+ parameter is optional:
    #
    #   ip = IPAddress::IPv4::parse_u32(167772160, 8)
    #
    #   ip.to_string
    #     #=> "10.0.0.0/8"
    #
    def self.parse_u32(u32, prefix=32)
      self.new([u32].pack("N").unpack("C4").join(".")+"/#{prefix}")
    end

    #
    # Creates a new IPv4 object from binary data,
    # like the one you get from a network stream.
    # 
    # For example, on a network stream the IP 172.16.0.1
    # is represented with the binary "\254\020\n\001".
    # 
    #   ip = IPAddress::IPv4::parse_data "\254\020\n\001"
    #   ip.prefix = 24
    #
    #   ip.to_string
    #     #=> "172.16.10.1/24"
    #
    def self.parse_data(str, prefix=32)
      self.new(str.unpack("C4").join(".")+"/#{prefix}")
    end

    #
    # Extract an IPv4 address from a string and 
    # returns a new object
    #
    # Example:
    #
    #   str = "foobar172.16.10.1barbaz"
    #   ip = IPAddress::IPv4::extract str
    #
    #   ip.to_s
    #     #=> "172.16.10.1"
    #
    def self.extract(str)
      self.new REGEXP.match(str).to_s
    end
    
    #
    # Summarization (or aggregation) is the process when two or more
    # networks are taken together to check if a supernet, including all
    # and only these networks, exists. If it exists then this supernet
    # is called the summarized (or aggregated) network.
    #
    # It is very important to understand that summarization can only
    # occur if there are no holes in the aggregated network, or, in other
    # words, if the given networks fill completely the address space
    # of the supernet. So the two rules are:
    #
    # 1) The aggregate network must contain +all+ the IP addresses of the
    #    original networks;
    # 2) The aggregate network must contain +only+ the IP addresses of the
    #    original networks;
    #
    # A few examples will help clarify the above. Let's consider for
    # instance the following two networks:
    #
    #   ip1 = IPAddress("172.16.10.0/24")
    #   ip2 = IPAddress("172.16.11.0/24")
    #
    # These two networks can be expressed using only one IP address
    # network if we change the prefix. Let Ruby do the work:
    #
    #   IPAddress::IPv4::summarize(ip1,ip2).to_s
    #     #=> "172.16.10.0/23"
    #
    # We note how the network "172.16.10.0/23" includes all the addresses
    # specified in the above networks, and (more important) includes
    # ONLY those addresses. 
    #
    # If we summarized +ip1+ and +ip2+ with the following network:
    #
    #   "172.16.0.0/16"
    #
    # we would have satisfied rule #1 above, but not rule #2. So "172.16.0.0/16"
    # is not an aggregate network for +ip1+ and +ip2+.
    #
    # If it's not possible to compute a single aggregated network for all the
    # original networks, the method returns an array with all the aggregate
    # networks found. For example, the following four networks can be
    # aggregated in a single /22:
    #
    #   ip1 = IPAddress("10.0.0.1/24")
    #   ip2 = IPAddress("10.0.1.1/24")
    #   ip3 = IPAddress("10.0.2.1/24")
    #   ip4 = IPAddress("10.0.3.1/24")
    #
    #   IPAddress::IPv4::summarize(ip1,ip2,ip3,ip4).to_string
    #     #=> "10.0.0.0/22", 
    #
    # But the following networks can't be summarized in a single network:
    #
    #   ip1 = IPAddress("10.0.1.1/24")
    #   ip2 = IPAddress("10.0.2.1/24")
    #   ip3 = IPAddress("10.0.3.1/24")
    #   ip4 = IPAddress("10.0.4.1/24")
    #
    #   IPAddress::IPv4::summarize(ip1,ip2,ip3,ip4).map{|i| i.to_string}
    #     #=> ["10.0.1.0/24","10.0.2.0/23","10.0.4.0/24"]
    #
    def self.summarize(*args)
      # one network? no need to summarize
      return [args.first.network] if args.size == 1
      
      i = 0
      result = args.dup.sort.map{|ip| ip.network}
      while i < result.size-1
        sum = result[i] + result[i+1]
        result[i..i+1] = sum.first if sum.size == 1
        i += 1
      end
      
      result.flatten!
      if result.size == args.size
        # nothing more to summarize
        return result
      else
        # keep on summarizing
        return self.summarize(*result)
      end
    end

    #
    # Creates a new IPv4 address object by parsing the 
    # address in a classful way.
    #
    # Classful addresses have a fixed netmask based on the 
    # class they belong to:
    #
    # * Class A, from 0.0.0.0 to 127.255.255.255
    # * Class B, from 128.0.0.0 to 191.255.255.255
    # * Class C, D and E, from 192.0.0.0 to 255.255.255.254
    #
    # Example:
    #
    #   ip = IPAddress::IPv4.parse_classful "10.0.0.1"
    #
    #   ip.netmask 
    #     #=> "255.0.0.0"
    #   ip.a?
    #     #=> true
    #
    # Note that classes C, D and E will all have a default
    # prefix of /24 or 255.255.255.0
    #
    def self.parse_classful(ip)
      if IPAddress.valid_ipv4?(ip)
        address = ip.strip
      else
        raise ArgumentError, "Invalid IP #{ip.inspect}"
      end
      prefix = CLASSFUL.find{|h,k| h === ("%.8b" % address.to_i)}.last
      self.new "#{address}/#{prefix}"
    end

    #
    # private methods
    #
    private

    def newprefix(num)
      num.upto(32) do |i|
        if (a = Math::log2(i).to_i) == Math::log2(i)
          return @prefix + a 
        end
      end
    end
    
    def sum_first_found(arr)
      dup = arr.dup.reverse
      dup.each_with_index do |obj,i|
        a = [self.class.summarize(obj,dup[i+1])].flatten
        if a.size == 1
          dup[i..i+1] = a
          return dup.reverse
        end
      end
      return dup.reverse
    end

    def aggregate(ip1,ip2)
      return [ip1] if ip1.include? ip2

      snet = ip1.supernet(ip1.prefix-1)
      if snet.include_all?(ip1, ip2) && ((ip1.size + ip2.size) == snet.size)
        return [snet]
      else
        return [ip1, ip2]
      end
    end
  end # class IPv4
end # module IPAddress

