# frozen_string_literal: true

require "rubygems"
require "pathname"

require "psych" if RUBY_VERSION >= "1.9"

if ENV["COVERAGE"]
  require "simplecov"

  def require_do(resource)
    require resource
    yield if block_given?
  rescue LoadError
    nil
  end

  formatters = [SimpleCov::Formatter::HTMLFormatter]

  require_do("simplecov-rcov") {
    formatters << SimpleCov::Formatter::RcovFormatter
  }
  require_do("simplecov-vim/formatter") {
    formatters << SimpleCov::Formatter::VimFormatter
  }
  require_do("simplecov-sublime-ruby-coverage") {
    formatters << SimpleCov::Formatter::SublimeRubyCoverageFormatter
  }

  SimpleCov.start do
    formatter SimpleCov::Formatter::MultiFormatter.new(formatters)
  end
end

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

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

module CaptureSubprocessIO
  def _synchronize
    yield
  end

  def capture_subprocess_io
    _synchronize { _capture_subprocess_io { yield } }
  end

  def _capture_subprocess_io
    require "tempfile"

    captured_stdout, captured_stderr = Tempfile.new("out"), Tempfile.new("err")

    orig_stdout, orig_stderr = $stdout.dup, $stderr.dup
    $stdout.reopen captured_stdout
    $stderr.reopen captured_stderr

    yield

    $stdout.rewind
    $stderr.rewind

    [captured_stdout.read, captured_stderr.read]
  ensure
    captured_stdout.unlink
    captured_stderr.unlink
    $stdout.reopen orig_stdout
    $stderr.reopen orig_stderr
  end
  private :_capture_subprocess_io
end

require "diff-lcs"

module Diff::LCS::SpecHelper
  def hello
    "hello"
  end

  def hello_ary
    %w[h e l l o]
  end

  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

  # standard:disable Layout/ExtraSpacing
  def correct_forward_diff
    [
      [
        ["-",  0, "a"]
      ],
      [
        ["+",  2, "d"]
      ],
      [
        ["-",  4, "h"],
        ["+",  4, "f"]
      ],
      [
        ["+",  6, "k"]
      ],
      [
        ["-",  8, "n"],
        ["+",  9, "r"],
        ["-",  9, "p"],
        ["+", 10, "s"],
        ["+", 11, "t"]
      ]
    ]
  end

  def correct_backward_diff
    [
      [
        ["+",  0, "a"]
      ],
      [
        ["-",  2, "d"]
      ],
      [
        ["-",  4, "f"],
        ["+",  4, "h"]
      ],
      [
        ["-",  6, "k"]
      ],
      [
        ["-",  9, "r"],
        ["+",  8, "n"],
        ["-", 10, "s"],
        ["+",  9, "p"],
        ["-", 11, "t"]
      ]
    ]
  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
  # standard:enable Layout/ExtraSpacing

  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 { |e|
      if e.is_a?(Array)
        e.map { |f| f.to_a.join }.join(", ")
      else
        e.to_a.join
      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 do |line|
      line = [line[0], line[2], line[1]]
      case line[0]
      when "<"
        line[0] = ">"
      when ">"
        line[0] = "<"
      end
      new_result << line
    end
    new_result.sort_by { |line| [line[1], line[2]] }
  end

  def map_to_no_change(change_result)
    new_result = []
    change_result.each do |line|
      case line[0]
      when "!"
        new_result << ["<", line[1], line[2]]
        new_result << [">", line[1] + 1, line[2]]
      else
        new_result << line
      end
    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)
        @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|
        expect(ee).to(satisfy { |vee| vee.nil? || s1[ii] == s2[ee] })
      end
    end

    matcher :correctly_map_sequence do |s1|
      match do |actual|
        actual.each_index { |ii| expect(actual[ii]).to 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:"
  # standard:disable Style/HashSyntax
  conf.filter_run_excluding :broken => true
  # standard:enable Style/HashSyntax
end
