# $Idaemons: /home/cvs/pkgtools/lib/pkgversion.rb,v 1.7 2001/09/18 16:04:44 knu Exp $

class PkgVersion
  include Comparable

  attr_accessor :version, :revision, :epoch

  def initialize(pkgversion)
    if /[\s-]/ =~ pkgversion    
      raise ArgumentError, "#{pkgversion}: Must not contain a '-' or whitespace."
    end

    if /^([^_,]+)(?:_(\d+))?(?:,(\d+))?$/ !~ pkgversion
      raise ArgumentError, "#{pkgversion}: Not in due form: '<version>[_<revision>][,<epoch>]'."
    end

    @version = $1
    @revision = $2 ? $2.to_i : 0
    @epoch = $3 ? $3.to_i : 0
  end

  def to_s
    s = @version
    s += '_' + @revision.to_s if @revision.nonzero?
    s += ',' + @epoch.to_s if @epoch.nonzero?

    s
  end

  def coerce(other)
    case other
    when PkgVersion
      return other, self
    when String
      return PkgVersion.new(other), self
    else
      raise TypeError, "Coercion between #{other.type} and #{self.type} is not supported."
    end
  end

  def <=>(other)
    case other
    when PkgVersion
      # ok
    when String
      other = PkgVersion.new(other)
    else
      a, b = other.coerce(self)

      return a <=> b
    end

    (@epoch <=> other.epoch).nonzero? ||
      PkgVersion.compare_numbers(@version, other.version).nonzero? ||
      @revision <=> other.revision
  end

  def PkgVersion::compare_numbers(n1, n2)
    n1 ||= ''
    n2 ||= ''

    # Short-cut in case of equality
    if n1 == n2
      return 0
    end

    # Split into subnumbers
    a1 = n1.split('.')
    a2 = n2.split('.')

    s1 = nil
    s2 = nil

    begin
      break if a1.empty? && a2.empty?

      s1 = a1.shift
      s2 = a2.shift
    end while s1 == s2

    s1 ||= ''
    s2 ||= ''

    # Short-cut in case of equality
    if s1 == s2
      return 0
    end

    # Split into sub-subnumbers
    a1 = s1.split(/(\D+)/)
    a2 = s2.split(/(\D+)/)

    a1.shift if /^\D/ =~ s1
    a2.shift if /^\D/ =~ s2

    # Sub-subnumbers
    x1 = a1.shift
    x2 = a2.shift

    # Check for alpha, beta, or pre
    if /^\D/ =~ x1		# x1: non-number
      if /^\D/ !~ x2		#	        vs. x2: number or null
	return -1		# -> x2 wins
      end

      if x1 != x2		#               vs. x2: non-number
	return x1 <=> x2	# -> Compare in dictionary order
      end
    elsif /^\D/ =~ x2		# x1: number or null vs. x2: non-number
      return 1			# -> x1 wins
    end

    # Seek for the difference
    while x1 == x2
      break if a1.empty? && a2.empty?

      x1 = a1.shift
      x2 = a2.shift
    end

    x1 ||= ''
    x2 ||= ''

    # Short-cut in case of equality
    if x1 == x2
      return 0
    end

    if /^\d/ =~ x1		# x1: number
      if /^\d/ =~ x2		#               vs. x2: number
	return x1.to_i <=> x2.to_i	# -> Compare numerically
      end
				#               vs. x2: non-number or null
      return 1			# -> x1 wins
    end
				# x1: non-number or null
    if /^\d/ =~ x2		#               vs. x2: number
      return -1			# -> x2 wins
    end
				#               vs. x2: non-number or null
    return x1 <=> x2		# -> Compare in dictionary order
  end
end
