# encoding: utf-8

require 'mkmf'

dir_config('tunemygc')

# Only defined for Ruby 2.1+
gc_events = have_const('RUBY_INTERNAL_EVENT_GC_END_SWEEP')

if gc_events
  # From ko1/gc_tracer
  # Piggy backs off the new methods to retrieve GC stats and latest cycle info without doing
  # allocations - thus safe to call within a GC cycle and properly snapshot what we need without
  # the results being skewed by postponed jobs, which isn't deterministic.
  open("tunemygc_env.h", 'w'){|f|
    gc_stat = GC.stat
    gc_latest_info = GC.latest_gc_info
    f.puts '#include "ruby/ruby.h"'
    f.puts "static VALUE sym_gc_stat[#{gc_stat.keys.size}];"
    f.puts "static VALUE sym_latest_gc_info[#{gc_latest_info.keys.size}];"

    f.puts 'typedef struct {'
    f.puts '    VALUE thread_id;'
    f.puts '    double ts;'
    f.puts '    size_t peak_rss;'
    f.puts '    size_t current_rss;'
    f.puts '    VALUE stage;'
    gc_stat.keys.each do |key|
      f.puts "    size_t #{key};"
    end
    gc_latest_info.each do |key, val|
      f.puts "    VALUE #{key};"
    end
    f.puts '} tunemygc_stat_record;'

    f.puts "static void"
    f.puts "tunemygc_set_stat_record(tunemygc_stat_record *record)"
    f.puts "{"
      #
      gc_stat.keys.each.with_index{|k, i|
        f.puts "    record->#{k} = rb_gc_stat(sym_gc_stat[#{i}]);"
      }
      gc_latest_info.keys.each.with_index{|k, i|
        f.puts "    record->#{k} = rb_gc_latest_gc_info(sym_latest_gc_info[#{i}]);"
      }
      #
    f.puts "}"

    f.puts "static VALUE"
    f.puts "tunemygc_get_stat_record(tunemygc_stat_record *record)"
    f.puts "{"
      #
      f.puts  "    VALUE stat = rb_ary_new2(#{gc_stat.keys.size});"
      f.puts  "    VALUE latest_info = rb_hash_new();"
      f.puts  "    VALUE snapshot = rb_ary_new2(8);"
      gc_stat.keys.each.with_index{|k, i|
        f.puts "    rb_ary_store(stat, #{i}, SIZET2NUM(record->#{k}));"
      }
      gc_latest_info.keys.each.with_index{|k, i|
        f.puts "    rb_hash_aset(latest_info, sym_latest_gc_info[#{i}], record->#{k});"
      }
      f.puts "    rb_ary_store(snapshot, 0, DBL2NUM(record->ts));"
      f.puts "    rb_ary_store(snapshot, 1, SIZET2NUM(record->peak_rss));"
      f.puts "    rb_ary_store(snapshot, 2, SIZET2NUM(record->current_rss));"
      f.puts "    rb_ary_store(snapshot, 3, record->stage);"
      f.puts "    rb_ary_store(snapshot, 4, stat);"
      f.puts "    rb_ary_store(snapshot, 5, latest_info);"
      f.puts "    rb_ary_store(snapshot, 6, Qnil);"
      f.puts "    rb_ary_store(snapshot, 7, record->thread_id);"
      f.puts "    return snapshot;"
      #
    f.puts "}"

    f.puts "static void"
    f.puts "tunemygc_setup_trace_symbols(void)"
    f.puts "{"
      #
      gc_stat.keys.each.with_index{|k, i|
        f.puts "    sym_gc_stat[#{i}] = ID2SYM(rb_intern(\"#{k}\"));"
      }
      gc_latest_info.keys.each.with_index{|k, i|
        f.puts "    sym_latest_gc_info[#{i}] = ID2SYM(rb_intern(\"#{k}\"));"
      }
      #
    f.puts "}"
  }

  create_makefile('tunemygc/tunemygc_ext')
else
  # do nothing if we're not running within Ruby 2.1.x or 2.2.x
  File.open('Makefile', 'w') do |f|
    f.puts "install:\n\t\n"
  end
end
