# -*- ruby encoding: utf-8 -*-

require 'rubygems'
require 'pathname'

file   = Pathname.new(__FILE__).expand_path
path   = file.parent
parent = path.parent

$:.unshift parent.join('lib')

require 'diff-lcs'

module Diff::LCS::SpecHelper
  def seq1
    %w(a b c e h j l m n p)
  end

  def skipped_seq1
    %w(a h n p)
  end

  def seq2
    %w(b c d e f j k l m r s t)
  end

  def skipped_seq2
    %w(d f k r s t)
  end

  def word_sequence
    %w(abcd efgh ijkl mnopqrstuvwxyz)
  end

  def correct_lcs
    %w(b c e j l m)
  end

  def correct_forward_diff
    [
      [ [ '-',  0, 'a' ] ],
      [ [ '+',  2, 'd' ] ],
      [ [ '-',  4, 'h' ],
        [ '+',  4, 'f' ] ],
      [ [ '+',  6, 'k' ] ],
      [ [ '-',  8, 'n' ],
        [ '-',  9, 'p' ],
        [ '+',  9, 'r' ],
        [ '+', 10, 's' ],
        [ '+', 11, 't' ] ]
    ]
  end

  def correct_backward_diff
    [
      [ [ '+',  0, 'a' ] ],
      [ [ '-',  2, 'd' ] ],
      [ [ '-',  4, 'f' ],
        [ '+',  4, 'h' ] ],
      [ [ '-',  6, 'k' ] ],
      [
        [ '-',  9, 'r' ],
        [ '-', 10, 's' ],
        [ '+',  8, 'n' ],
        [ '-', 11, 't' ],
        [ '+',  9, 'p' ] ]
    ]
  end

  def correct_forward_sdiff
    [
      [ '-', [  0, 'a' ], [  0, nil ] ],
      [ '=', [  1, 'b' ], [  0, 'b' ] ],
      [ '=', [  2, 'c' ], [  1, 'c' ] ],
      [ '+', [  3, nil ], [  2, 'd' ] ],
      [ '=', [  3, 'e' ], [  3, 'e' ] ],
      [ '!', [  4, 'h' ], [  4, 'f' ] ],
      [ '=', [  5, 'j' ], [  5, 'j' ] ],
      [ '+', [  6, nil ], [  6, 'k' ] ],
      [ '=', [  6, 'l' ], [  7, 'l' ] ],
      [ '=', [  7, 'm' ], [  8, 'm' ] ],
      [ '!', [  8, 'n' ], [  9, 'r' ] ],
      [ '!', [  9, 'p' ], [ 10, 's' ] ],
      [ '+', [ 10, nil ], [ 11, 't' ] ]
    ]
  end

  def reverse_sdiff(forward_sdiff)
    forward_sdiff.map { |line|
      line[1], line[2] = line[2], line[1]
      case line[0]
      when '-' then line[0] = '+'
      when '+' then line[0] = '-'
      end
      line
    }
  end

  def change_diff(diff)
    map_diffs(diff, Diff::LCS::Change)
  end

  def context_diff(diff)
    map_diffs(diff, Diff::LCS::ContextChange)
  end

  def format_diffs(diffs)
    diffs.map do |e|
      if e.kind_of?(Array)
        e.map { |f| f.to_a.join }.join(", ")
      else
        e.to_a.join
      end
    end.join("\n")
  end

  def map_diffs(diffs, klass = Diff::LCS::ContextChange)
    diffs.map do |chunks|
      if klass == Diff::LCS::ContextChange
        klass.from_a(chunks)
      else
        chunks.map { |changes| klass.from_a(changes) }
      end
    end
  end

  def balanced_traversal(s1, s2, callback_type)
    callback = __send__(callback_type)
    Diff::LCS.traverse_balanced(s1, s2, callback)
    callback
  end

  def balanced_reverse(change_result)
    new_result = []
    change_result.each { |line|
      line = [ line[0], line[2], line[1] ]
      case line[0]
      when '<'
        line[0] = '>'
      when '>'
        line[0] = '<'
      end
      new_result << line
    }
    new_result.sort_by { |line| [ line[1], line[2] ] }
  end

  def map_to_no_change(change_result)
    new_result = []
    change_result.each { |line|
      case line[0]
      when '!'
        new_result << [ '<', line[1], line[2] ]
        new_result << [ '>', line[1] + 1, line[2] ]
      else
        new_result << line
      end
    }
    new_result
  end


  def simple_callback
    callbacks = Object.new
    class << callbacks
      attr_reader :matched_a
      attr_reader :matched_b
      attr_reader :discards_a
      attr_reader :discards_b
      attr_reader :done_a
      attr_reader :done_b

      def reset
        @matched_a = []
        @matched_b = []
        @discards_a = []
        @discards_b = []
        @done_a = []
        @done_b = []
      end

      def match(event)
        @matched_a << event.old_element
        @matched_b << event.new_element
      end

      def discard_b(event)
        @discards_b << event.new_element
      end

      def discard_a(event)
        @discards_a << event.old_element
      end

      def finished_a(event)
        @done_a << [event.old_element, event.old_position,
          event.new_element, event.new_position]
      end

      def finished_b(event)
        p "called #finished_b"
        @done_b << [event.old_element, event.old_position,
          event.new_element, event.new_position]
      end
    end
    callbacks.reset
    callbacks
  end

  def simple_callback_no_finishers
    simple = simple_callback
    class << simple
      undef :finished_a
      undef :finished_b
    end
    simple
  end

  def balanced_callback
    cb = Object.new
    class << cb
      attr_reader :result

      def reset
        @result = []
      end

      def match(event)
        @result << [ "=", event.old_position, event.new_position ]
      end

      def discard_a(event)
        @result << [ "<", event.old_position, event.new_position ]
      end

      def discard_b(event)
        @result << [ ">", event.old_position, event.new_position ]
      end

      def change(event)
        @result << [ "!", event.old_position, event.new_position ]
      end
    end
    cb.reset
    cb
  end

  def balanced_callback_no_change
    balanced = balanced_callback
    class << balanced
      undef :change
    end
    balanced
  end

  module Matchers
    extend RSpec::Matchers::DSL

    matcher :be_nil_or_match_values do |ii, s1, s2|
      match do |ee|
        ee.should satisfy { |vee| vee.nil? || s1[ii] == s2[ee] }
      end
    end

    matcher :correctly_map_sequence do |s1|
      match do |actual|
        actual.each_with_index { |ee, ii|
          ee.should be_nil_or_match_values(ii, s1, @s2)
        }
      end

      chain :to_other_sequence do |s2|
        @s2 = s2
      end
    end
  end
end

RSpec.configure do |conf|
  conf.include Diff::LCS::SpecHelper
  conf.alias_it_should_behave_like_to :it_has_behavior, 'has behavior:'
end

# vim: ft=ruby
