module IntervalType
  UNKNOWN = 0
  LEFT_OPEN = 1
  LEFT_CLOSED = 2
  RIGHT_OPEN = 4
  RIGHT_CLOSED = 8
end

class VersionInterval
  attr_accessor :type, :start_cut, :end_cut

  def initialize(type, start_cut, end_cut)
    @type = type
    @start_cut = start_cut
    @end_cut = end_cut
  end

  def intersect(other_interval)
    return EmptyInterval.new if self.empty?

    # this look odd -- we have to use it here though, because it may be that placeholders are present inside
    # the version for which > and < would yield true
    return EmptyInterval.new if !(@start_cut <= other_interval.end_cut) || !(other_interval.start_cut <= @end_cut)

    start_cut_new = max(@start_cut, other_interval.start_cut)
    end_cut_new = min(@end_cut, other_interval.end_cut)

    # compute the boundaries for the intersection
    type = compute_intersection_boundary(self, other_interval, start_cut_new, end_cut_new)
    interval = VersionInterval.new(type, start_cut_new, end_cut_new)
    half_open = !(interval.bit_set?(IntervalType::RIGHT_CLOSED) && interval.bit_set?(IntervalType::LEFT_CLOSED))

    interval.singleton? && half_open ? EmptyInterval.new : interval
  end

  def union(other_interval)
    return EmptyInterval.new if self.intersect(other_interval).instance_of?(EmptyInterval)

    start_cut_new = min(@start_cut, other_interval.start_cut)
    end_cut_new = max(@end_cut, other_interval.end_cut)

    # compute the boundaries for the union
    type = compute_union_boundary(self, other_interval, start_cut_new, end_cut_new)
    VersionInterval.new(type, start_cut_new, end_cut_new)
  end

  def collapse(other_interval)
    return EmptyInterval.new if self.intersect(other_interval).instance_of?(EmptyInterval)

    frame = [@start_cut, other_interval.start_cut, @end_cut, other_interval.end_cut].reject { |cut| special(cut) }
    min_cut = frame.reduce { |smallest, current| smallest < current ? smallest : current }
    max_cut = frame.reduce { |biggest, current| biggest > current ? biggest : current }

    # compute the boundaries for the union
    type = compute_union_boundary(self, other_interval, min_cut, max_cut)
    VersionInterval.new(type, min_cut, max_cut)
  end

  def special(cut)
    cut.instance_of?(AboveAll) || cut.instance_of?(BelowAll)
  end

  def to_s
    s = ""
    s += bit_set?(IntervalType::LEFT_CLOSED) ? "[" : ""
    s += bit_set?(IntervalType::LEFT_OPEN) ? "(" : ""
    s += [@start_cut, @end_cut].join(",")
    s += bit_set?(IntervalType::RIGHT_CLOSED) ? "]" : ""
    s += bit_set?(IntervalType::RIGHT_OPEN) ? ")" : ""
    s
  end

# this function returns a human-readable descriptions of the version strings
  def to_description_s
    s = ""
    if self.distinct?
      s = "version #{@start_cut.to_s}"
    elsif self.universal?
      s = "all versions "
    else
      s = "all versions "
      s += start_cut.instance_of?(BelowAll) ? "" : bit_set?(IntervalType::LEFT_OPEN) ? "after #{@start_cut.to_s} " : bit_set?(IntervalType::LEFT_CLOSED) ? "starting from #{@start_cut.to_s} " : ""
      s += end_cut.instance_of?(AboveAll) ? "" : bit_set?(IntervalType::RIGHT_OPEN) ? "before #{@end_cut.to_s}" : bit_set?(IntervalType::RIGHT_CLOSED) ? "up to #{@end_cut.to_s}" : ""
    end
    s.strip
  end

  def to_nuget_s
    to_maven_s
  end

  def to_maven_s
    s = ""
    # special case -- distinct version
    if self.distinct?
      s += "[#{@start_cut.to_s}]"
    else
      s += start_cut.instance_of?(BelowAll) ? "(," : bit_set?(IntervalType::LEFT_OPEN) ? "[#{@start_cut.to_s}," : bit_set?(IntervalType::LEFT_CLOSED) ? "[#{@start_cut.to_s}," : ""
      s += end_cut.instance_of?(AboveAll) ? ")" : bit_set?(IntervalType::RIGHT_OPEN) ? "#{@end_cut.to_s})" : bit_set?(IntervalType::RIGHT_CLOSED) ? "#{@end_cut.to_s}]" : ""
    end
    s
  end

  def distinct?
    bit_set?(IntervalType::LEFT_CLOSED) && bit_set?(IntervalType::RIGHT_CLOSED) && @start_cut == @end_cut
  end

  def subsumes?(other)
    @start_cut <= other.start_cut && @end_cut >= other.end_cut
  end

  def universal?
    (bit_set?(IntervalType::LEFT_OPEN) && bit_set?(IntervalType::RIGHT_OPEN) && @start_cut.instance_of?(BelowAll) && @end_cut.instance_of?(AboveAll)) || @start_cut.is_initial_version? && @end_cut.instance_of?(AboveAll)
  end

  def to_gem_s
    get_canoncial_s
  end

  def to_ruby_s
    get_canoncial_s
  end

  def to_npm_s
    get_canoncial_s
  end

  def to_conan_s
    get_canoncial_s
  end

  def to_go_s
    get_canoncial_s
  end

  def to_pypi_s
    get_canoncial_s(',', '==')
  end

  def to_packagist_s
    get_canoncial_s(',')
  end

  def empty?
    self.instance_of?(EmptyInterval)
  end

  def singleton?
    @start_cut == @end_cut && @start_cut.value == @end_cut.value
  end

  def diff(other_interval, abs = true)
    if self.distinct? && other_interval.distinct?
      self.start_cut.diff(other_interval.start_cut, abs)
    else
      EmptyInterval.new()
    end
  end

  def joinable?(other_interval)
    other_interval.start_cut.is_successor_of?(self.end_cut) || universal?
  end

  def join(other_interval)
    if self.joinable?(other_interval)
      _join(self, other_interval)
    elsif other_interval.joinable?(self)
      _join(other_interval, self)
    else
      EmptyInterval.new()
    end
  end

  def cross_total
    if distinct?
      @start_cut.cross_total
    else
      @start_cut.cross_total + @end_cut.cross_total
    end
  end

  def ==(other_interval)
    @start_cut == other_interval.start_cut && @end_cut == other_interval.end_cut && @type == other_interval.type
  end

# inverts the given version interval -- note that this function amy return two version intervals
# e.g., (2,10] -> (-inf, 2], (10, +inf)
#                 left       right
  def invert
    intervals = []
    left_type = bit_set?(IntervalType::LEFT_OPEN) ? IntervalType::RIGHT_CLOSED : IntervalType::RIGHT_OPEN
    left_type = left_type | IntervalType::LEFT_OPEN
    right_type = bit_set?(IntervalType::RIGHT_OPEN) ? IntervalType::LEFT_CLOSED : IntervalType::LEFT_OPEN
    right_type = right_type | IntervalType::RIGHT_OPEN

    intervals << VersionInterval.new(left_type, BelowAll.new, @start_cut) unless @start_cut.instance_of?(BelowAll)
    intervals << VersionInterval.new(right_type, @end_cut, AboveAll.new) unless @end_cut.instance_of?(AboveAll)
    intervals
  end

  def bit_set?(interval_type)
    @type & interval_type != 0
  end

  protected

# computes the boundary type for union operation
  def compute_union_boundary(interval_a, interval_b, start_cut_new, end_cut_new)
    compute_boundary(interval_a, interval_b, start_cut_new, end_cut_new, IntervalType::LEFT_CLOSED, IntervalType::RIGHT_CLOSED)
  end

  def compute_intersection_boundary(interval_a, interval_b, start_cut_new, end_cut_new)
    compute_boundary(interval_a, interval_b,start_cut_new, end_cut_new, IntervalType::LEFT_OPEN, IntervalType::RIGHT_OPEN)
  end

  def compute_boundary(interval_a, interval_b, start_cut_new, end_cut_new, left_check, right_check)
    start_cut_a = interval_a.start_cut
    end_cut_a = interval_a.end_cut
    type_a = interval_a.type

    start_cut_b = interval_b.start_cut
    end_cut_b = interval_b.end_cut
    type_b = interval_b.type

    left_fill = left_check == IntervalType::LEFT_OPEN ? IntervalType::LEFT_CLOSED : IntervalType::LEFT_OPEN
    right_fill = right_check == IntervalType::RIGHT_OPEN ? IntervalType::RIGHT_CLOSED : IntervalType::RIGHT_OPEN

    # compute the boundaries for the union
    if start_cut_b == start_cut_a && start_cut_b == start_cut_b
      one_left_closed = left_type(type_a) == left_check || left_type(type_b) == left_check
      left_type = one_left_closed ? left_check : left_fill
    else
      left_type = start_cut_new == start_cut_a ? left_type(type_a) : left_type(type_b)
    end

    if end_cut_b == end_cut_a && end_cut_b == end_cut_b
      one_right_closed = right_type(type_a) == right_check || right_type(type_b) == right_check
      right_type = one_right_closed ? right_check : right_fill
    else
      right_type = end_cut_new == end_cut_a ? right_type(type_a) : right_type(type_b)
    end

    left_type | right_type
  end

  def _join(first_interval, second_interval)
    if first_interval.joinable?(second_interval)
      VersionInterval.new(first_interval.type, first_interval.start_cut.dup, second_interval.end_cut.dup)
    else
      EmptyInterval.new()
    end
  end

  def get_canoncial_s(delimiter = " ", eq = '=')
    if self.distinct?
      "#{eq}#{@start_cut.to_s}"
    else
      first = start_cut.instance_of?(BelowAll) ? "" : bit_set?(IntervalType::LEFT_OPEN) ? ">#{@start_cut.to_s}" : bit_set?(IntervalType::LEFT_CLOSED) ? ">=#{@start_cut.to_s}" : ""
      second = end_cut.instance_of?(AboveAll) ? "" : bit_set?(IntervalType::RIGHT_OPEN) ? "<#{@end_cut.to_s}" : bit_set?(IntervalType::RIGHT_CLOSED) ? "<=#{@end_cut.to_s}" : ""
      !first.empty? && !second.empty? ? "#{first}#{delimiter}#{second}" : first + second
    end
  end

  def max(cut_a, cut_b)
    cut_a > cut_b ? cut_a : cut_b
  end

  def min(cut_a, cut_b)
    cut_a < cut_b ? cut_a : cut_b
  end

  def right_type(type)
    (IntervalType::RIGHT_OPEN | IntervalType::RIGHT_CLOSED) & type
  end

  def left_type(type)
    (IntervalType::LEFT_OPEN | IntervalType::LEFT_CLOSED) & type
  end
end

class EmptyInterval < VersionInterval
  def initialize()
  end

  def to_s
    "empty"
  end
end
