#!/usr/bin/env ruby

# frozen_string_literal: true

require 'bundler/setup'
require 'benchmark/ips'
require 'tempfile'
require 'stringio'

# from benchmark/ips readme
class GCSuite
  def warming(*) # leftovers:allow
    run_gc
  end

  def running(*) # leftovers:allow
    run_gc
  end

  def warmup_stats(*); end # leftovers:allow

  def add_report(*); end # leftovers:allow

  private

  def run_gc
    GC.enable
    GC.start
    GC.disable
  end
end

GC.disable

config = { suite: GCSuite.new, time: 3, warmup: 0.1 }

def benchmark(label)
  return unless ARGV.empty? || ARGV.include?(label)

  puts "\n#{label}:"

  yield
  puts ''
end

module AnythingMatcher
  def self.===(_value)
    true
  end
end
benchmark('method-vs-ancestors') do
  a_proc = proc { true }
  method = AnythingMatcher.method(:===)

  raise unless AnythingMatcher === rand
  raise unless BasicObject === rand
  raise unless a_proc === rand
  raise unless method === rand

  Benchmark.ips do |x|
    x.config(config)

    x.report(:method_object) { method === rand }
    x.report(:ancestor) { Object === rand }
    x.report(:'=== method') { AnythingMatcher === rand }
    x.report(:proc) { a_proc === rand }

    x.compare!
  end
end

benchmark('length-and-out-of-bounds-access') do
  Benchmark.ips do |x|
    x.config(config)
    x.report(:length_gte_one, '[1,2,3].length >= 4')
    x.report(:length_gt_one, '[1,2,3].length > 3')
    x.report(:access_one, '[1,2,3][3]')
    x.report(:length_gte_thousand, '[1,2,3].length >= 1000')
    x.report(:length_gt_thousand, '[1,2,3].length > 999')
    x.report(:access_thousand, '[1,2,3][999]')
    x.compare!
  end
end

benchmark('or-or-any-2') do
  Benchmark.ips do |x|
    x.config(config)
    @array = [1, 2]
    @a = 1
    @b = 2
    @matcher_first = 0..1
    @matcher_second = 2..3
    @matcher_miss = 3..4

    x.report(:any_first) { @array.any?(@matcher_first) }
    x.report(:any_second) { @array.any?(@matcher_second) }
    x.report(:any_none) { @array.any?(@matcher_miss) }
    x.report(:or_first) { @matcher_first === @a || @matcher_first === @b }
    x.report(:or_second) { @matcher_second === @a || @matcher_second === @b }
    x.report(:or_none) { @matcher_miss === @a || @matcher_miss === @b }
    x.compare!
  end
end

benchmark('not-empty') do
  Benchmark.ips do |x|
    x.config(config)
    x.report(:length_gte_1, '[].length >= 1')
    x.report(:length_gt_0, '[].length > 0')
    x.report(:bang_first, '![].first')
    x.report(:not_first, 'not [].first')
    x.report(:not_empty, 'not [].empty?')
    x.report(:bang_empty, '![].empty?')
    x.compare!
  end
end

benchmark('remove-and-return-2-from-array') do
  Benchmark.ips do |x|
    x.config(config)

    x.report(:pop) do
      a = [1, 2, 3]
      a.pop
      a.pop
    end

    x.report(:shift) do
      a = [1, 2, 3]
      a.shift
      a.shift
    end

    x.report(:first_drop) do
      a = [1, 2, 3]
      a.first
      a[1]
      a.drop(2)
    end

    x.compare!
  end
end

benchmark('concat-when-empty') do
  Benchmark.ips do |x|
    x.config(config)

    x.report(:empty?, 'a = []; [1,2,3,4,5,6,7,8,9,0].concat(a) unless a.empty?')
    x.report(:concat, 'a = []; [1,2,3,4,5,6,7,8,9,0].concat(a)')
    x.report(:control, 'a = []; [1,2,3,4,5,6,6,7,8,9,0]')
    x.compare!
  end
end

benchmark('concat-when-not-empty') do
  Benchmark.ips do |x|
    x.config(config)

    x.report(:empty?, 'a = [1]; [1,2,3,4,5,6,7,8,9,0].concat(a) unless a.empty?')
    x.report(:concat, 'a = [1]; [1,2,3,4,5,6,7,8,9,0].concat(a)')
    x.report(:control, 'a = [1]; [1,2,3,4,5,6,6,7,8,9,0]')
    x.compare!
  end
end

benchmark('match-vs-count') do
  # [<>&"']
  Benchmark.ips do |x|
    x.config(config)
    x.report(:count_miss, '"my-string".count(%{<>&"\'}).positive?')
    x.report(:match_miss, '"my-string".match?(/[<>&"\']/)')
    x.report(:count_hit_start, '"<my-string".count(%{<>&"\'}).positive?')
    x.report(:match_hit_start, '"<my-string".match?(/[<>&"\']/)')
    x.report(:count_hit_end, '"my-string>".count(%{<>&"\'}).positive?')
    x.report(:match_hit_end, '"my-string>".match?(/[<>&"\']/)')
    x.report(:count_hit_long_start, "'<#{'my-string ' * 20_000}'.count(%{<>&\"'}).positive?")
    x.report(:match_hit_long_start, "'<#{'my-string ' * 20_000}'.match?(/[<>&\"']/)")

    x.report(:count_hit_long_end, "'#{'my-string ' * 20_000}>'.count(%{<>&\"'}).positive?")
    x.report(:match_hit_long_end, "'#{'my-string ' * 20_000}>'.match?(/[<>&\"']/)")
    x.compare!
  end
end

benchmark('delete-before') do
  Benchmark.ips do |x|
    x.config(config)
    x.report(:split_miss, 's="my-own-string"; (s.split("+", 2)[1] || s)')
    x.report(:index_miss, 's="my-own-string"; (index = s.index("+")) ? s[(index + 1)..-1] : s')
    x.report(:sub_miss, 's="my-own-string"; s.sub(/\\A.*?\\+/m, "")')

    x.report(:split_hit, 's="my-own-string"; (s.split("-", 2)[1] || s)')
    x.report(:index_hit, 's="my-own-string"; (index = s.index("-")) ? s[(index + 1)..-1] : s')
    x.report(:sub_hit, 's="my-own-string"; s.sub(/\\A.*?-/m, "")')
    x.compare!
  end
end

benchmark('delete-after') do
  Benchmark.ips do |x|
    x.config(config)
    x.report(:split_miss, 's="my-own-string"; s.split("+", 2).first')
    x.report(:index_miss, 's="my-own-string"; (index = s.index("+")) ? s[0...index] : s')
    x.report(:sub_miss, 's="my-own-string"; s.sub(/\\+.*\\z/m, "")')
    x.report(:match_miss, 's="my-own-string"; s.match(/\\A(.*?)\\+/m)&.[](1) || s')

    x.report(:split_hit, 's="my-own-string"; s.split("-", 2).first')
    x.report(:index_hit, 's="my-own-string"; (index = s.index("-")) ? s[0...index] : s')
    x.report(:sub_hit, 's="my-own-string"; s.sub(/-.*\\z/m, "")')
    x.report(:match_hit, 's="my-own-string"; s.match(/\\A(.*?)-/m)&.[](1) || s')
    x.compare!
  end
end

benchmark('delete-after-no-eval') do
  @sub_re = /-.*\z/m.freeze
  @match_re = /\A(.*?)-/m.freeze
  @str = 'my-own-string'
  @miss_str = 'my+own+string'
  @pat = '-'

  Benchmark.ips do |x|
    x.config(config)
    x.report(:split_miss) { @miss_str.split(@pat, 2).first }
    x.report(:index_miss) { (index = @miss_str.index(@pat)) ? @miss_str[0...index] : @miss_str }
    x.report(:sub_miss) { @miss_str.sub(@sub_re, '') }
    x.report(:match_miss) { @miss_str.match(@match_re)&.[](1) || @miss_str }

    x.report(:split_hit) { @str.split(@pat, 2).first }
    x.report(:index_hit) { (index = @str.index(@pat)) ? @str[0...index] : @str }
    x.report(:sub_hit) { @str.sub(@sub_re, '') }
    x.report(:match_hit) { @str.match(@match_re)&.[](1) || @str }
    x.compare!
  end
end

benchmark('delete-after-no-eval-re-pattern') do
  @sub_re = /[-_].*\z/m.freeze
  @match_re = /\A(.*?)[-_]/m.freeze
  @str = 'my-own-string'
  @miss_str = 'my+own+string'
  @pat_re = /[-_]/

  Benchmark.ips do |x|
    x.config(config)
    x.report(:split_miss) { @miss_str.split(@pat_re, 2).first }
    x.report(:index_miss) { (index = @miss_str.index(@pat_re)) ? @miss_str[0...index] : @miss_str }
    x.report(:sub_miss) { @miss_str.sub(@sub_re, '') }
    x.report(:match_miss) { @miss_str.match(@match_re)&.[](1) || @miss_str }

    x.report(:split_hit) { @str.split(@pat_re, 2).first }
    x.report(:index_hit) { (index = @str.index(@pat_re)) ? @str[0...index] : @str }
    x.report(:sub_hit) { @str.sub(@sub_re, '') }
    x.report(:match_hit) { @str.match(@match_re)&.[](1) || @str }
    x.compare!
  end
end

benchmark('match-vs-include') do
  Benchmark.ips do |x|
    x.config(config)
    x.report(:include_miss, '"my-string".include?("<")')
    x.report(:match_miss, '"my-string".match?(/</)')
    x.report(:include_hit_start, '"<my-string".include?("<")')
    x.report(:match_hit_start, '"<my-string".match?(/</)')
    x.report(:include_hit_end, '"my-string>".include?(">")')
    x.report(:match_hit_end, '"my-string>".match?(/>/)')
    x.report(:include_hit_long_start, "'<#{'my-string ' * 20_000}'.include?('<')")
    x.report(:match_hit_long_start, "'<#{'my-string ' * 20_000}'.match?(/</)")

    x.report(:include_hit_long_end, "'#{'my-string ' * 20_000}>'.include?('>')")
    x.report(:match_hit_long_end, "'#{'my-string ' * 20_000}>'.match?(/>/)")
    x.compare!
  end
end
