# frozen_string_literal: true

require_relative '../../utils.rb'

class SemanticVersion
  attr_reader :version_string, :prefix_delimiter, :prefix_segments, :suffix_segments, :segments

  # String to build a regexp that matches a version.
  #
  # A version might start with a leading "v", then it must have a digit,
  # then it might have any sequence made of alphanumerical characters,
  # underscores, dots, dashes, and wildcards.
  VERSION_PATTERN = "v?[0-9][a-zA-Z0-9_.*+-]*"

  # Regexp for a string that only contains a single version string.
  VERSION_ONLY_REGEXP = Regexp.new("\\A#{VERSION_PATTERN}\\z").freeze

  def initialize(version_string, segments = nil)
    unless VERSION_ONLY_REGEXP.match version_string
      raise SemverDialects::InvalidVersionError, version_string
    end

    @version_string = version_string
    @prefix_segments = []
    @suffix_segments = []
    @prefix_delimiter = 0
    version, _ = version_string.delete_prefix('v').split('+')
    if segments.nil?
      @segments = split_version_string!(version)
    else
      @prefix_segments = segments
      @prefix_delimiter = segments.size - 1
      @segments = segments
    end
  end

  def split_version_string!(version_string)
    delim_pattern = /[\.\-]/
    split_array = version_string.split(delim_pattern).map {
      |grp|
      grp.split(/(\d+)/).reject { |cell| cell.nil? || cell.empty? }
    }.flatten

    # go as far to the right as possible considering numbers and placeholders
    (0..split_array.size - 1).each do
      |i|
      if split_array[i].number? || split_array[i] == "X" || split_array[i] == "x"
        @prefix_delimiter = i
      else
        break
      end
    end

    # remove redundant trailing zeros
    @prefix_delimiter.downto(0).each do
      |i|
      if split_array[i] == "0"
        split_array.delete_at(i)
        @prefix_delimiter -= 1
      else
        break
      end
    end

    @prefix_segments = split_array[0..@prefix_delimiter].map { |group_string| SemanticVersionSegment.new(group_string) } unless @prefix_delimiter < 0
    if split_array.size - 1 >= @prefix_delimiter + 1
      @suffix_segments = split_array[@prefix_delimiter + 1, split_array.size].map { |group_string| SemanticVersionSegment.new(group_string) }
    end

    @prefix_segments.clone().concat(@suffix_segments)
  end

  def _get_equalized_arrays_for(array_a, array_b)
    first_array = array_a.clone()
    second_array = array_b.clone()
    if first_array.size < second_array.size
      (second_array.size - first_array.size).times do
        first_array << SemanticVersionSegment.new("0")
      end
    elsif first_array.size > second_array.size
      (first_array.size - second_array.size).times do
        second_array << SemanticVersionSegment.new("0")
      end
    end
    [first_array, second_array]
  end

  def get_equalized_arrays_for(semver_a, semver_b)
    first_array_prefix = semver_a.prefix_segments.clone()
    second_array_prefix = semver_b.prefix_segments.clone()
    first_array_suffix = semver_a.suffix_segments.clone()
    second_array_suffix = semver_b.suffix_segments.clone()
    first_array_prefix, second_array_prefix = _get_equalized_arrays_for(first_array_prefix, second_array_prefix)
    first_array_suffix, second_array_suffix = _get_equalized_arrays_for(first_array_suffix, second_array_suffix)
    [first_array_prefix.concat(first_array_suffix), second_array_prefix.concat(second_array_suffix)]
  end

  def get_equalized_prefix_arrays_for(semver_a, semver_b)
    first_array_prefix = semver_a.prefix_segments.clone()
    second_array_prefix = semver_b.prefix_segments.clone()
    first_array_prefix, second_array_prefix = _get_equalized_arrays_for(first_array_prefix, second_array_prefix)
    [first_array_prefix, second_array_prefix]
  end

  def <(other_semver)
    self_array, other_array = get_equalized_arrays_for(self, other_semver)
    (0..self_array.size - 1).each {
      |i|
      if self_array[i] < other_array[i]
        return true
      elsif self_array[i] > other_array[i]
        return false
      end
    }
    false
  end

  def is_zero?
    @prefix_segments.empty? || @prefix_segments.all? { |segment| segment.is_zero? }
  end

  def pre_release?
    @suffix_segments.any?(&:is_pre_release)
  end

  def post_release?
    @suffix_segments.any?(&:is_post_release)
  end

  def >(other_semver)
    self_array, other_array = get_equalized_arrays_for(self, other_semver)
    (0..self_array.size - 1).each {
      |i|
      if self_array[i] > other_array[i]
        return true
      elsif self_array[i] < other_array[i]
        return false
      end
    }
    false
  end

  def >=(other_semver)
    self == other_semver || self > other_semver || self == other_semver
  end

  def <=(other_semver)
    self == other_semver || self < other_semver
  end

  def ==(other_semver)
    segments_a = []
    segments_b = []

    segments_a += other_semver.segments
    segments_b += @segments

    if other_semver.segments.size < @segments.size
      (@segments.size - other_semver.segments.size).times {|_| segments_a << SemanticVersionSegment.new("0")}
    elsif other_semver.segments.size > @segments.size
      (other_semver.segments.size - @segments.size).times {|_| segments_b << SemanticVersionSegment.new("0") }
    end

    (0..segments_a.size - 1).each {
      |i|
      if segments_a[i] != segments_b[i]
        return false
      end
    }
    true
  end

  def !=(other_semver)
    return !(self == other_semver)
  end

  def diff(other_semver, abs = true)
    diffs = []

    self_array, other_array = get_equalized_prefix_arrays_for(self, other_semver)
    (0..self_array.size - 1).each {
      |i|
      # diff semantic groups
      diffs << (self_array[i].diff(other_array[i], abs))
    }
    diffs
  end

  def to_normalized_s
    @segments.map { |group| group.to_normalized_s }.join(":").to_s
  end

  def to_s
    @segments.map { |group| group.to_s }.join(":").to_s
  end

  def minor
    @prefix_segments.size >= 1 ? @prefix_segments[1].to_s : "0"
  end

  def major
    @prefix_segments.size >= 2 ? @prefix_segments[0].to_s : "0"
  end

  def patch
    @prefix_segments.size >= 3 ? @prefix_segments[2].to_s : "0"
  end

  def is_successor_of?(other_semver)
    filtered_segments = self.diff(other_semver, false).reject { |i| i.to_i == 0 }
    filtered_segments.size == 1 && filtered_segments.last.to_i == 1
  end

  def cross_total()
    [self.major, self.minor, self.patch].reject { |i| i.empty? }.map { |i| i.to_i }.inject(0) { |sum, x| sum + x }
  end
end

class SemanticVersionSegment
  attr_accessor :normalized_group_string, :original_group_string, :is_post_release, :is_pre_release

  @@group_suffixes = {
    # pre-releases
    "PRE" => -16,
    "PREVIEW" => -16,
    "DEV" => -15,
    "A" => -14,
    "ALPHA" => -13,
    "B" => -12,
    "BETA" => -12,
    "RC" => -11,
    "M" => -10,

    "RELEASE" => 0,
    "FINAL" => 0,
    # PHP specific
    "STABLE" => 0,

    # post-releases
    "SP" => 1,
  }

  def initialize(group_string)
    @is_post_release = false
    @is_pre_release = false

    @version_string = group_string
    @original_group_string = group_string
    # use x as unique placeholder
    group_string_ucase = group_string.to_s.gsub(/\*/, "x").upcase

    if @@group_suffixes.key?(group_string_ucase)
      value = @@group_suffixes[group_string_ucase]
      @is_post_release = value > 0
      @is_pre_release = value < 0
      @normalized_group_string = @@group_suffixes[group_string_ucase].to_s
    else
      @normalized_group_string = group_string_ucase
    end
  end

  def compare(semver_a, semver_b, ret_anr_bnonr, ret_anonr_bnr, comparator)
    if semver_a.number? && semver_b.number?
      semver_a.to_i.send(comparator, semver_b.to_i)
    elsif semver_a.number? && !semver_b.number?
      if semver_b == "X"
        true
      else
        ret_anr_bnonr
      end
    elsif !semver_a.number? && semver_b.number?
      if semver_a == "X"
        true
      else
        ret_anonr_bnr
      end
    else # !semantic_version_b.group_string.is_number? && !semantic_version_agrous_string.is_number?
      if semver_a == "X" || semver_b == "X"
        true
      else
        semver_a.send(comparator, semver_b)
      end
    end
  end

  def <(other_semver)
    compare(self.normalized_group_string, other_semver.normalized_group_string, true, false, :<)
  end

  def >(other_semver)
    compare(self.normalized_group_string, other_semver.normalized_group_string, false, true, :>)
  end

  def >=(other_semver)
    self == other_semver || compare(self.normalized_group_string, other_semver.normalized_group_string, false, true, :>)
  end

  def <=(other_semver)
    self == other_semver || compare(self.normalized_group_string, other_semver.normalized_group_string, true, false, :<)
  end

  def ==(other_semver)
    self.normalized_group_string == other_semver.normalized_group_string
  end

  def !=(other_semver)
    return !(self == other_semver)
  end

  def to_normalized_s
    @normalized_group_string
  end

  def to_s
    @version_string
  end

  def is_number?
    self.normalized_group_string.number?
  end

  def is_zero?
    is_number? ? self.normalized_group_string.to_i == 0 : false
  end

  def diff(other_semver, abs = true)

    if other_semver.normalized_group_string == "X" || self.normalized_group_string == "X"
      "0"
    elsif self.normalized_group_string.number? && other_semver.normalized_group_string.number?
      ret = self.normalized_group_string.to_i - other_semver.normalized_group_string.to_i
      (abs && ret < 0) ? -ret : ret
    else
      nil
    end
  end
end
