# Bottom-up mergesort of a lined list without an auxiliary space,
# see test/merge_sort_bu_test.rb 
class MergeSortBu
  Node = Struct.new(:value, :next)

  def sort(list)
    return list if list.nil? || list.next.nil?

    size = 1
    result = list

    loop do
      merges = 0
      first = result
      prev_last = nil

      while first
        second = find_second(first, size)
        break if first == second

        head, last = merge(first, second, size)
        prev_last.next = head if prev_last
        prev_last = last
        first = last.next

        result = head if merges == 0
        merges += 1
      end

      return result if merges == 1 && last.next.nil?

      size *= 2
    end
  end

  private

  def merge(first, second, size)
    fsize = size
    ssize = size
    head = nil
    tail = nil
    next_first = nil

    while (fsize != 0) || (second && ssize != 0)
      if ssize == 1 && next_first.nil?
        next_first = second.next
      end

      if fsize == 0
        tail = link(tail, second)
        second = second.next
        ssize -= 1
      elsif second.nil? || ssize == 0
        tail = link(tail, first)
        first = first.next
        fsize -= 1
      elsif first.value > second.value
        tail = link(tail, second)
        second = second.next
        ssize -= 1
      else
        tail = link(tail, first)
        first = first.next
        fsize -= 1
      end

      head = tail if head.nil?
    end

    tail.next = next_first
    [head, tail]
  end

  def link(tail, node)
    tail.next = node if tail
    node
  end

  def find_second(first, size)
    second = first
    size.times do
      break if second.next.nil?
      second = second.next
    end

    second
  end
end
