Skip to main content

lib/hex_solver/constraints/version.ex

defmodule HexSolver.Constraints.Version do
  @moduledoc false

  use HexSolver.Constraints.Impl, for: Version

  import Kernel, except: [match?: 2]

  alias HexSolver.Constraint
  alias HexSolver.Constraints.{Empty, Range, Union, Util}

  def any?(%Version{}), do: false

  def empty?(%Version{}), do: false

  def allows?(%Version{} = left, %Version{} = right), do: left == right

  def allows_any?(%Version{}, %Empty{}), do: true
  def allows_any?(%Version{} = left, right), do: Constraint.allows?(right, left)

  def allows_all?(%Version{}, %Empty{}) do
    true
  end

  def allows_all?(%Version{} = left, %Version{} = right) do
    left == right
  end

  def allows_all?(%Version{}, %Range{min: nil}) do
    false
  end

  def allows_all?(%Version{}, %Range{max: nil}) do
    false
  end

  def allows_all?(%Version{} = version, %Range{
        min: min,
        max: max,
        include_min: true,
        include_max: true
      }) do
    version == min and version == max
  end

  def allows_all?(%Version{}, %Range{}) do
    false
  end

  def allows_all?(%Version{} = version, %Union{ranges: ranges}) do
    Enum.all?(ranges, &allows_all?(version, &1))
  end

  def difference(%Version{} = version, constraint) do
    if Constraint.allows?(constraint, version), do: %Empty{}, else: version
  end

  def intersect(%Version{} = version, constraint) do
    if Constraint.allows?(constraint, version), do: version, else: %Empty{}
  end

  def union(%Version{} = version, constraint) do
    if Constraint.allows?(constraint, version) do
      constraint
    else
      case constraint do
        %Range{min: ^version} = range -> %{range | include_min: true}
        %Range{max: ^version} = range -> %{range | include_max: true}
        _ -> Util.union([version, constraint])
      end
    end
  end

  def compare(%Version{} = left, %Version{} = right) do
    Version.compare(left, right)
  end

  def compare(%Version{} = version, %Range{min: min, include_min: include_min}) do
    if min == nil do
      :gt
    else
      case Version.compare(version, min) do
        :eq when include_min -> :eq
        :eq -> :lt
        :lt -> :lt
        :gt -> :gt
      end
    end
  end

  def compare(%Version{} = version, %Union{ranges: [range | _]}) do
    compare(version, range)
  end

  def min(left, right) do
    case compare(left, right) do
      :lt -> left
      :eq -> left
      :gt -> right
    end
  end

  def max(left, right) do
    case compare(left, right) do
      :lt -> right
      :eq -> left
      :gt -> left
    end
  end

  def to_range(%Version{} = version) do
    %Range{min: version, max: version, include_min: true, include_max: true}
  end

  def to_range(%Range{} = range) do
    range
  end

  def prioritize(%Version{} = left, %Version{} = right) do
    do_prioritize(left, right) == :gt
  end

  defp do_prioritize(left, right) do
    cond do
      left.pre != [] and right.pre == [] -> :lt
      left.pre == [] and right.pre != [] -> :gt
      true -> Version.compare(left, right)
    end
  end
end