#!/usr/bin/env ruby -wW1
# frozen_string_literal: true

$LOAD_PATH << File.join(__dir__, '../lib')
$LOAD_PATH << File.join(__dir__, '../ext')

require 'optparse'
require 'yajl'
require 'json'
require 'json/pure'
require 'json/ext'
require 'msgpack'
require 'oj'
require 'ox'

class Jazz
  def initialize
    @boolean = true
    @number = 58
    @string = 'A string'
    @array = [true, false, nil]
    @hash = { 'one' => 1, 'two' => 2 }
  end

  def to_json(*_args)
    %{
    { "boolean":#{@boolean},
      "number":#{@number},
      "string":#{@string},
      "array":#{@array},
      "hash":#{@hash},
    }
}
  end

  def to_hash
    { 'boolean' => @boolean,
      'number' => @number,
      'string' => @string,
      'array' => @array,
      'hash' => @hash,
    }
  end

  def to_msgpack(out='')
    to_hash().to_msgpack(out)
  end
end

$indent = 2
$iter = 10_000
$with_object = true
$with_bignum = true
$with_nums = true

opts = OptionParser.new
opts.on('-c', '--count [Int]', Integer, 'iterations')       { |i| $iter = i }
opts.on('-i', '--indent [Int]', Integer, 'indentation')     { |i| $indent = i }
opts.on('-o', 'without objects')                            { $with_object = false }
opts.on('-b', 'without bignum')                             { $with_bignum = false }
opts.on('-n', 'without numbers')                            { $with_nums = false }
opts.on('-h', '--help', 'Show this display')                { puts opts; Process.exit!(0) }
opts.parse(ARGV)

if $with_nums
  obj = {
    'a' => 'Alpha',
    'b' => true,
    'c' => 12_345,
    'd' => [ true, [false, [12_345, nil], 3.967, ['something', false], nil]],
    'e' => { 'one' => 1, 'two' => 2 },
    'f' => nil,
  }
  obj['g'] = Jazz.new() if $with_object
  obj['h'] = 12_345_678_901_234_567_890_123_456_789 if $with_bignum
else
  obj = {
    'a' => 'Alpha',
    'b' => true,
    'c' => '12345',
    'd' => [ true, [false, ['12345', nil], '3.967', ['something', false], nil]],
    'e' => { 'one' => '1', 'two' => '2' },
    'f' => nil,
  }
end

Oj.default_options = { :indent => $indent, :mode => :object }

s = Oj.dump(obj)

xml = Ox.dump(obj, :indent => $indent)

puts

# Put Oj in strict mode so it only create JSON native types instead of the
# original Ruby Objects. None of the other packages other than Ox support
# Object recreation so no need for Oj to do it in the performance tests.
Oj.default_options = { :mode => :strict }
parse_results = { :oj => 0.0, :yajl => 0.0, :msgpack => 0.0, :pure => 0.0, :ext => 0.0, :ox => 0.0 }

start = Time.now
$iter.times do
  Oj.load(s)
end
dt = Time.now - start
base_dt = dt
parse_results[:oj] = dt
puts '%d Oj.load()s in %0.3f seconds or %0.1f loads/msec' % [$iter, dt, $iter/dt/1000.0]

start = Time.now
$iter.times do
  Yajl::Parser.parse(s)
end
dt = Time.now - start
if base_dt < dt
  base_dt = dt
  base_name = 'Yajl'
end
parse_results[:yajl] = dt
puts '%d Yajl::Parser.parse()s in %0.3f seconds or %0.1f parses/msec' % [$iter, dt, $iter/dt/1000.0]

begin
  JSON.parser = JSON::Ext::Parser
  start = Time.now
  $iter.times do
    JSON.parse(s)
  end
  dt = Time.now - start
  if base_dt < dt
    base_dt = dt
    base_name = 'JSON::Ext'
  end
  parse_results[:ext] = dt
  puts '%d JSON::Ext::Parser parse()s in %0.3f seconds or %0.1f parses/msec' % [$iter, dt, $iter/dt/1000.0]
rescue Exception => e
  puts "JSON::Ext failed: #{e.class}: #{e.message}"
end

begin
  JSON.parser = JSON::Pure::Parser
  start = Time.now
  $iter.times do
    JSON.parse(s)
  end
  dt = Time.now - start
  if base_dt < dt
    base_dt = dt
    base_name = 'JSON::Pure'
  end
  parse_results[:pure] = dt
  puts '%d JSON::Pure::Parser parse()s in %0.3f seconds or %0.1f parses/msec' % [$iter, dt, $iter/dt/1000.0]
rescue Exception => e
  puts "JSON::Pure failed: #{e.class}: #{e.message}"
end

begin
  mp = MessagePack.pack(obj)
  start = Time.now
  $iter.times do
    MessagePack.unpack(mp)
  end
  dt = Time.now - start
  if base_dt < dt
    base_dt = dt
    base_name = 'MessagePack'
  end
  parse_results[:msgpack] = dt
  puts '%d MessagePack.unpack()s in %0.3f seconds or %0.1f packs/msec' % [$iter, dt, $iter/dt/1000.0]
rescue Exception => e
  puts "MessagePack failed: #{e.class}: #{e.message}"
end

start = Time.now
$iter.times do
  Ox.load(xml)
end
dt = Time.now - start
parse_results[:ox] = dt
puts '%d Ox.load()s in %0.3f seconds or %0.1f loads/msec' % [$iter, dt, $iter/dt/1000.0]

puts 'Parser results:'
puts "gem       seconds  parses/msec  X faster than #{base_name} (higher is better)"
parse_results.each do |name, dt2|
  if dt2 <= 0.0
    puts "#{name} failed to generate JSON"
    next
  end
  puts '%-7s  %6.3f    %5.1f        %4.1f' % [name, dt2, $iter/dt/1000.0, base_dt/dt2]
end

puts

# Back to object mode for best performance when dumping.
Oj.default_options = { :indent => $indent, :mode => :object }

start = Time.now
$iter.times do
  Oj.dump(obj)
end
dt = Time.now - start
base_dt = dt
base_name = 'Oj'
parse_results[:oj] = dt
puts '%d Oj.dump()s in %0.3f seconds or %0.1f dumps/msec' % [$iter, dt, $iter/dt/1000.0]

start = Time.now
$iter.times do
  Yajl::Encoder.encode(obj)
end
dt = Time.now - start
if base_dt < dt
  base_dt = dt
  base_name = 'Yajl'
end
parse_results[:yajl] = dt
puts '%d Yajl::Encoder.encode()s in %0.3f seconds or %0.1f encodes/msec' % [$iter, dt, $iter/dt/1000.0]

begin
  JSON.parser = JSON::Ext::Parser
  start = Time.now
  $iter.times do
    JSON.generate(obj)
  end
  dt = Time.now - start
  if base_dt < dt
    base_dt = dt
    base_name = 'JSON::Ext'
  end
  parse_results[:pure] = dt
  puts '%d JSON::Ext generate()s in %0.3f seconds or %0.1f generates/msec' % [$iter, dt, $iter/dt/1000.0]
rescue Exception => e
  parse_results[:ext] = 0.0
  puts "JSON::Ext failed: #{e.class}: #{e.message}"
end

begin
  JSON.parser = JSON::Pure::Parser
  start = Time.now
  $iter.times do
    JSON.generate(obj)
  end
  dt = Time.now - start
  if base_dt < dt
    base_dt = dt
    base_name = 'JSON::Pure'
  end
  parse_results[:pure] = dt
  puts '%d JSON::Pure generate()s in %0.3f seconds or %0.1f generates/msec' % [$iter, dt, $iter/dt/1000.0]
rescue Exception => e
  parse_results[:pure] = 0.0
  puts "JSON::Pure failed: #{e.class}: #{e.message}"
end

begin
  start = Time.now
  $iter.times do
    MessagePack.pack(obj)
  end
  dt = Time.now - start
  if base_dt < dt
    base_dt = dt
    base_name = 'MessagePack'
  end
  parse_results[:msgpack] = dt
  puts '%d Msgpack()s in %0.3f seconds or %0.1f unpacks/msec' % [$iter, dt, $iter/dt/1000.0]
rescue Exception => e
  parse_results[:msgpack] = 0.0
  puts "MessagePack failed: #{e.class}: #{e.message}"
end

start = Time.now
$iter.times do
  Ox.dump(obj)
end
dt = Time.now - start
parse_results[:ox] = dt
puts '%d Ox.dump()s in %0.3f seconds or %0.1f dumps/msec' % [$iter, dt, $iter/dt/1000.0]

puts 'Parser results:'
puts "gem       seconds  dumps/msec  X faster than #{base_name} (higher is better)"
parse_results.each do |name, dt2|
  if dt2 <= 0.0
    puts "#{name} failed to generate JSON"
    next
  end
  puts '%-7s  %6.3f    %5.1f       %4.1f' % [name, dt2, $iter/dt/1000.0, base_dt/dt2]
end

puts
