require 'fileutils'
require 'open3'
require 'timeout'

RSpec::Matchers.define :become_true do |expected|

  match do |block|
    end_time = Time.now + Capybara.default_max_wait_time.seconds
    loop do
      break if Time.now > end_time
      value = block.call
      return true if value
    end
    false
  end

  def supports_block_expectations?
    true
  end

end

RSpec::Matchers.define :equal_time do |expected|
  match do |subject|
    expect(subject).to be_within(0.000_001.second).of(expected)
  end
end

SnapshotInfo = Struct.new(:name, :variation, :file)

class RSpec::Core::ExampleGroup
  def snapshot(name = :default, variation = :desktop)
    tmp = Tempfile.new([variation, '.png'])
    tmp.close

    document_width = page.driver.evaluate_script("document.documentElement.clientWidth")
    document_height = page.driver.evaluate_script("document.documentElement.clientHeight")
    page.save_screenshot "#{tmp.path}", width: document_width, height: document_height

    SnapshotInfo.new(name, variation, tmp)
  end
end

RSpec.configure do |config|
  config.after(:example) do |example|
    msg = example.metadata[:new_snapshot_paths].inject('') do |msg, path|
      <<-COMMANDS #{msg}
Show new screenshot:
  $ open #{dirname}/NEW_#{path}

Approve new screenshot:
  $ mv #{dirname}/NEW_#{path} #{dirname}/#{path}
COMMANDS
    end unless example.metadata[:new_snapshot_paths].nil?

    raise RSpec::Expectations::ExpectationNotMetError, msg, example.metadata[:snapshot_first_caller] unless msg.blank?
  end

  def dirname
    'spec/support/expected_snapshots'
  end

end

RSpec::Matchers.define :look_correct do
  match do |snapshot_info|

    next true if ENV['SKIP_SCREENSHOT_DIFF']

    path = snapshot_path(snapshot_info)

    existing_path = "#{dirname}/#{path}"
    new_path = snapshot_info.file.path

    existing_height = height_of(existing_path)
    new_height = height_of(new_path)

    tmpfile_path = Tempfile.new(['scaled','.png']).path if existing_height != new_height

    if existing_height < new_height
      `convert #{existing_path} -resize 1650x#{new_height} -background black -compose Copy -gravity north -extent 1650x#{new_height} #{tmpfile_path}`
      existing_path = tmpfile_path
    end

    if new_height < existing_height
      `convert #{new_path} -resize 1650x#{existing_height} -background black -compose Copy -gravity north -extent 1650x#{existing_height} #{tmpfile_path}`
      new_path = tmpfile_path
    end

    if File.exists?(existing_path)
      prog = 'compare'
      args = '-verbose -metric RMSE -highlight-color Red'
      diff_path = "#{dirname}/DIFF_#{path}"

      # do image comparison
      cmd = "#{prog} #{args} \"#{existing_path}\" \"#{new_path}\" \"#{diff_path}\""
      Open3.popen3(cmd) do |stdin, stdout, stderr, wait_thr|
        thr_status = wait_thr.value

        case thr_status.exitstatus
          when 0
            # remove the diff image since the images match
            FileUtils.rm(diff_path)
          when 1
            # images are different
            FileUtils.mv(snapshot_info.file.path, "#{dirname}/CHANGED_#{path}")
            false
          else
            puts stdout.read
            puts stderr.read
            raise "Diff command failed #{cmd} : (#{thr_status.exitstatus})"
        end
      end
    else

      # this is a new screenshot, so move it in place
      FileUtils.mkdir_p(dirname) unless File.directory?(dirname)
      FileUtils.mv(snapshot_info.file.path, "#{dirname}/NEW_#{path}")

      example.metadata[:snapshot_first_caller] = caller unless example.metadata[:snapshot_first_caller]
      (example.metadata[:new_snapshot_paths] ||= []) << path
    end
  end

  failure_message do |snapshot_info|
    path = snapshot_path(snapshot_info)

    <<~COMMANDS
      expected that '#{snapshot_info.name}' screenshot for #{snapshot_info.variation} would look correct

      Show screenshot diffs:
        $ open #{dirname}/DIFF_#{path} #{dirname}/CHANGED_#{path} #{dirname}/#{path}

      Approve changed screenshot:
        $ mv #{dirname}/CHANGED_#{path} #{dirname}/#{path}
    COMMANDS
  end

  def dirname
    'spec/support/expected_snapshots'
  end

  def snapshot_path(snapshot_info)
    "#{[example.metadata[:full_description].parameterize('-'), snapshot_info.name, snapshot_info.variation].join('_')}.png"
  end

  def height_of(path)
    `identify -format "%h" #{path}`.chomp.to_i
  end

  def example
    RSpec.current_example
  end
end
