class Bundler::PubGrub::FailureWriter

def collapsible?(incompatibility)

def collapsible?(incompatibility)
  return false if @derivations[incompatibility] > 1
  cause = incompatibility.cause
  # If incompatibility is derived from two derived incompatibilities,
  # there are too many transitive causes to display concisely.
  return false if cause.conflict.conflict? && cause.other.conflict?
  # If incompatibility is derived from two external incompatibilities, it
  # tends to be confusing to collapse it.
  return false unless cause.conflict.conflict? || cause.other.conflict?
  # If incompatibility's internal cause is numbered, collapsing it would
  # get too noisy.
  complex = cause.conflict.conflict? ? cause.conflict : cause.other
  !@line_numbers.has_key?(complex)
end

def count_derivations(incompatibility)

def count_derivations(incompatibility)
  if @derivations.has_key?(incompatibility)
    @derivations[incompatibility] += 1
  else
    @derivations[incompatibility] = 1
    if incompatibility.conflict?
      cause = incompatibility.cause
      count_derivations(cause.conflict)
      count_derivations(cause.other)
    end
  end
end

def initialize(root)

def initialize(root)
  @root = root
  # { Incompatibility => Integer }
  @derivations = {}
  # [ [ String, Integer or nil ] ]
  @lines = []
  # { Incompatibility => Integer }
  @line_numbers = {}
  count_derivations(root)
end

def single_line?(cause)

def single_line?(cause)
  !cause.conflict.conflict? && !cause.other.conflict?
end

def visit(incompatibility, conclusion: false)

def visit(incompatibility, conclusion: false)
  raise unless incompatibility.conflict?
  numbered = conclusion || @derivations[incompatibility] > 1;
  conjunction = conclusion || incompatibility == @root ? "So," : "And"
  cause = incompatibility.cause
  if cause.conflict.conflict? && cause.other.conflict?
    conflict_line = @line_numbers[cause.conflict]
    other_line = @line_numbers[cause.other]
    if conflict_line && other_line
      write_line(
        incompatibility,
        "Because #{cause.conflict} (#{conflict_line})\nand #{cause.other} (#{other_line}),\n#{incompatibility}.",
        numbered: numbered
      )
    elsif conflict_line || other_line
      with_line    = conflict_line ? cause.conflict : cause.other
      without_line = conflict_line ? cause.other : cause.conflict
      line = @line_numbers[with_line]
      visit(without_line);
      write_line(
        incompatibility,
        "#{conjunction} because #{with_line} (#{line}),\n#{incompatibility}.",
        numbered: numbered
      )
    else
      single_line_conflict = single_line?(cause.conflict.cause)
      single_line_other    = single_line?(cause.other.cause)
      if single_line_conflict || single_line_other
        first  = single_line_other ? cause.conflict : cause.other
        second = single_line_other ? cause.other : cause.conflict
        visit(first)
        visit(second)
        write_line(
          incompatibility,
          "Thus, #{incompatibility}.",
          numbered: numbered
        )
      else
        visit(cause.conflict, conclusion: true)
        @lines << ["", nil]
        visit(cause.other)
        write_line(
          incompatibility,
          "#{conjunction} because #{cause.conflict} (#{@line_numbers[cause.conflict]}),\n#{incompatibility}.",
          numbered: numbered
        )
      end
    end
  elsif cause.conflict.conflict? || cause.other.conflict?
    derived = cause.conflict.conflict? ? cause.conflict : cause.other
    ext     = cause.conflict.conflict? ? cause.other : cause.conflict
    derived_line = @line_numbers[derived]
    if derived_line
      write_line(
        incompatibility,
        "Because #{ext}\nand #{derived} (#{derived_line}),\n#{incompatibility}.",
        numbered: numbered
      )
    elsif collapsible?(derived)
      derived_cause = derived.cause
      if derived_cause.conflict.conflict?
        collapsed_derived = derived_cause.conflict
        collapsed_ext = derived_cause.other
      else
        collapsed_derived = derived_cause.other
        collapsed_ext = derived_cause.conflict
      end
      visit(collapsed_derived)
      write_line(
        incompatibility,
        "#{conjunction} because #{collapsed_ext}\nand #{ext},\n#{incompatibility}.",
        numbered: numbered
      )
    else
      visit(derived)
      write_line(
        incompatibility,
        "#{conjunction} because #{ext},\n#{incompatibility}.",
        numbered: numbered
      )
    end
  else
    write_line(
      incompatibility,
      "Because #{cause.conflict}\nand #{cause.other},\n#{incompatibility}.",
      numbered: numbered
    )
  end
end

def write

def write
  return @root.to_s unless @root.conflict?
  visit(@root)
  padding = @line_numbers.empty? ? 0 : "(#{@line_numbers.values.last}) ".length
  @lines.map do |message, number|
    next "" if message.empty?
    lead = number ? "(#{number}) " : ""
    lead = lead.ljust(padding)
    message = message.gsub("\n", "\n" + " " * (padding + 2))
    "#{lead}#{message}"
  end.join("\n")
end

def write_line(incompatibility, message, numbered:)

def write_line(incompatibility, message, numbered:)
  if numbered
    number = @line_numbers.length + 1
    @line_numbers[incompatibility] = number
  end
  @lines << [message, number]
end