# frozen_string_literal: true

require_relative 'version_parser'
require 'set'

# VersionRange is a utility class that helps managing consecutive version ranges automatically
# given that they are added in-order
# Note that join_if_possible should be only activated in case the ranges are added in consecutive order!!
class VersionRange
  attr_reader :version_intervals, :join_if_possible

  def initialize(join_if_possible = true)
    @version_intervals = []
    @version_interval_set = Set.new
    @join_if_possible = join_if_possible
  end

  def add_all(version_range)
    version_range.version_intervals.each { |interval| add(interval) }
  end

  def add(version_interval)
    return if @version_interval_set.include?(version_interval)

    if @join_if_possible
      if @version_intervals.empty?
        @version_intervals << version_interval
        @version_interval_set.add(version_interval)
      else
        last = @version_intervals.last
        # nothing to do
        return if last.end_cut == version_interval.start_cut && last.end_cut.value == version_interval.start_cut.value

        if last.joinable?(version_interval)
          @version_intervals[@version_intervals.size - 1] = last.join(version_interval)
        else
          @version_intervals << version_interval
          @version_interval_set.add(version_interval)
        end
      end
    else
      @version_intervals << version_interval
      @version_interval_set.add(version_interval)
    end
  end

  def <<(item)
    add(item)
  end

  def size
    @version_intervals.size
  end

  def to_s
    @version_intervals.map(&:to_s).join(',')
  end

  def to_description_s
    @version_intervals.map(&:to_description_s).join(', ').capitalize
  end

  def to_npm_s
    @version_intervals.map(&:to_npm_s).join('||')
  end

  def to_conan_s
    to_npm_s
  end

  def to_nuget_s
    to_maven_s
  end

  def to_maven_s
    @version_intervals.map(&:to_maven_s).join(',')
  end

  def to_gem_s
    @version_intervals.map(&:to_gem_s).join('||')
  end

  def to_pypi_s
    @version_intervals.map(&:to_pypi_s).join('||')
  end

  def to_go_s
    @version_intervals.map(&:to_go_s).join('||')
  end

  def to_packagist_s
    @version_intervals.map(&:to_packagist_s).join('||')
  end

  def to_version_s(package_type)
    case package_type
    when 'npm'
      to_npm_s
    when 'nuget'
      to_nuget_s
    when 'maven'
      to_maven_s
    when 'gem'
      to_gem_s
    when 'pypi'
      to_pypi_s
    when 'packagist'
      to_packagist_s
    when 'go'
      to_go_s
    when 'conan'
      to_conan_s
    else
      ''
    end
  end

  # inverts the given version interval -- note that this function amy return two version intervals
  # e.g.,       (2,10], (12, 13], [15, +inf)
  # 1) invert:  (-inf, 2], (10, +inf), (-inf, 12], (13, +inf), (15)
  # 2) collapse (-inf, 2], (10, 12], (13, 15)
  #
  # the collapsed inverted ranges can potentially contain fixed versions
  def invert
    inverted = @version_intervals.map(&:invert).flatten
    version_intervals = collapse_intervals(inverted)
    version_intervals_to_range(version_intervals)
  end

  def collapse
    version_intervals = collapse_intervals(@version_intervals)
    version_intervals_to_range(version_intervals)
  end

  def includes?(version_interval)
    @version_interval_set.include?(version_interval)
  end

  def overlaps_with?(version_interval)
    @version_interval_set.each do |interval|
      return true unless interval.intersect(version_interval).instance_of? EmptyInterval
    end
    false
  end

  def first
    @version_intervals.first
  end

  def empty?
    @version_intervals.empty?
  end

  def any?
    @version_intervals.any?
  end

  def universal?
    @version_intervals.each do |version_interval|
      return true if version_interval.universal?
    end
    false
  end

  private

  def version_intervals_to_range(version_intervals)
    ret = VersionRange.new(false)
    version_intervals.each do |version_interval|
      ret << version_interval
    end
    ret
  end

  def collapse_intervals(version_intervals)
    interval_cp = []
    interval_cp += version_intervals
    ret = [interval_cp.shift]

    interval_cp.each do |item|
      last = ret.last
      if last.intersect(item).instance_of?(EmptyInterval)
        ret << item
      else
        ret.pop
        ret << last.collapse(item)
      end
    end
    ret
  end
end
