GoBoundless/dyph3

View on GitHub
lib/dyph/support/diff3.rb

Summary

Maintainability
A
1 hr
Test Coverage
module Dyph
  module Support

    class Diff3
      def self.execute_diff(left, base, right, diff2)
        Diff3.new(left, base, right, diff2).get_differences
      end

      def initialize(left, base, right, diff2)
        @left   = left
        @right  = right
        @base   = base
        @diff2 = diff2
      end

      def get_differences
        #[[action, base_lo, base_hi, side_lo, side_hi]...]
        left_diff  = @diff2.diff(@base, @left).map { |r| Diff2Command.new(*r) }
        right_diff = @diff2.diff(@base, @right).map { |r| Diff2Command.new(*r) }
        collapse_differences(DiffDoubleQueue.new(left_diff, right_diff))
      end

      Diff2Command = Struct.new(:code, :base_lo, :base_hi, :side_lo, :side_hi)

      private

        def collapse_differences(diffs_queue, differences=[])
          if diffs_queue.finished?
            differences
          else
            result_queue   = DiffDoubleQueue.new
            init_side =  diffs_queue.choose_side
            top_diff   =  diffs_queue.dequeue

            result_queue.enqueue(init_side, top_diff)

            diffs_queue.switch_sides
            build_result_queue(diffs_queue, top_diff.base_hi, result_queue)

            differences << determine_differnce(result_queue, init_side, diffs_queue.switch_sides)
            collapse_differences(diffs_queue, differences)
          end
        end

        def build_result_queue(diffs_queue, prev_base_hi, result_queue)
          #current side can be :left or :right
          if queue_finished?(diffs_queue.peek, prev_base_hi)
            result_queue
          else
            top_diff = diffs_queue.dequeue
            result_queue.enqueue(diffs_queue.current_side, top_diff)

            if prev_base_hi < top_diff.base_hi
              #switch the current side and adjust the base_hi
              diffs_queue.switch_sides
              build_result_queue(diffs_queue, top_diff.base_hi, result_queue)
            else
              build_result_queue(diffs_queue, prev_base_hi, result_queue)
            end
          end
        end

        def queue_finished?(queue, prev_base_hi)
          queue.empty? || queue.first.base_lo > prev_base_hi + 1
        end

        def determine_differnce(diff_diffs_queue, init_side, final_side)
          base_lo = diff_diffs_queue.get(init_side).first.base_lo
          base_hi = diff_diffs_queue.get(final_side).last.base_hi
#          puts "Beta base_lo #{base_lo} base_hi #{base_hi}"
          left_lo,  left_hi    = diffable_endpoints(diff_diffs_queue.get(:left), base_lo, base_hi)
          right_lo, right_hi   = diffable_endpoints(diff_diffs_queue.get(:right), base_lo, base_hi)

          #the endpoints are offset one, neet to account for that in getting subsets
          left_subset = @left[left_lo-1 .. left_hi]
          right_subset = @right[right_lo-1 .. right_hi]
          change_type = decide_action(diff_diffs_queue, left_subset, right_subset)
          [change_type, left_lo, left_hi, right_lo, right_hi, base_lo, base_hi]
        end

        def diffable_endpoints(command, base_lo, base_hi)
          if command.any?
            lo = command.first.side_lo - command.first.base_lo +  base_lo
            hi = command.last.side_hi  - command.last.base_hi  + base_hi
            [lo, hi]
          else
            [base_lo,  base_hi]
          end
        end

        def decide_action(diff_diffs_queue, left_subset, right_subset)
          #adjust because the ranges are 1 indexed

          if diff_diffs_queue.empty?(:left)
            :choose_right
          elsif diff_diffs_queue.empty?(:right)
            :choose_left
          else
            if left_subset != right_subset
              :possible_conflict
            else
              :no_conflict_found
            end
          end
        end
    end

    class DiffDoubleQueue
      attr_reader :current_side
      def initialize(left=[], right=[])
        @diffs = { left: left, right: right }
      end

      def dequeue(side=current_side)
        @diffs[side].shift
      end

      def peek(side=current_side)
        @diffs[side]
      end

      def finished?
        empty?(:left) && empty?(:right)
      end

      def enqueue(side=current_side, val)
        @diffs[side] << val
      end

      def get(side=current_side)
        @diffs[side]
      end

      def empty?(side=current_side)
        @diffs[side].empty?
      end

      def switch_sides(side=current_side)
        @current_side = side == :left ? :right : :left
      end

      def choose_side
        if empty? :left
          @current_side = :right
        elsif empty? :right
          @current_side = :left
        else
          #choose the lowest side relative to base
          @current_side = get(:left).first.base_lo <= get(:right).first.base_lo ? :left : :right
        end
      end
    end

  end
end