require 'mkmf'

module LLRBExtconf
  class << self
    def configure
      remove_invalid_warnflags
      remove_warnflags_for_llvm
      add_cflags
      link_llvm
    end

    def compile_bitcodes
      Dir.chdir(extdir) { Dir.glob('*.c') }.each do |c_file|
        compile_bitcode(c_file, c_file.sub(/\.c\z/, '.bc'))
      end
    end

    private

    def remove_invalid_warnflags
      # cc1plus: warning: command line option ‘-Wimplicit-int’ is valid for C/ObjC but not for C++
      # cc1plus: warning: command line option ‘-Wdeclaration-after-statement’ is valid for C/ObjC but not for C++
      # cc1plus: warning: command line option ‘-Wimplicit-function-declaration’ is valid for C/ObjC but not for C++
      # cc1plus: warning: unrecognized command line option ‘-Wno-self-assign’
      # cc1plus: warning: unrecognized command line option ‘-Wno-constant-logical-operand’
      # cc1plus: warning: unrecognized command line option ‘-Wno-parentheses-equality’
      # cc1plus: warning: unrecognized command line option ‘-Wno-tautological-compare’
      %w[
        -Wimplicit-int
        -Wdeclaration-after-statement
        -Wimplicit-function-declaration
        -Wno-self-assign
        -Wno-constant-logical-operand
        -Wno-parentheses-equality
        -Wno-tautological-compare
      ].each do |flag|
        CONFIG['warnflags'].gsub!(flag, '')
      end
    end

    def remove_warnflags_for_llvm
      # /usr/local/Cellar/llvm/5.0.0/include/llvm/IR/DerivedTypes.h:410:40: error: implicit conversion loses integer precision: 'uint64_t'
      #       (aka 'unsigned long long') to 'unsigned int' [-Werror,-Wshorten-64-to-32]
      #     return VectorType::get(EltTy, VTy->getNumElements());
      #            ~~~~~~~~~~             ~~~~~^~~~~~~~~~~~~~~~
      %w[
        -Wshorten-64-to-32
      ].each do |flag|
        CONFIG['warnflags'].gsub!(flag, '')
      end
    end

    def add_cflags
      # To include ccan/*, add ext/llrb/cruby under include path. "cruby_extra" dir has CRuby's dynamic headers.
      $INCFLAGS = "#{$INCFLAGS} -I$(srcdir)/cruby -I$(srcdir)/cruby_extra"

      bitcode_dir = File.expand_path("#{__dir__}/..")
      $CFLAGS = "#{$CFLAGS} -DLLRB_BITCODE_DIR='\"#{bitcode_dir}\"' -Wall -Werror -W" # remove -Werror later
      $CXXFLAGS = "#{$CXXFLAGS} -Wall -Werror -W" # remove -Werror later
    end

    def link_llvm
      unless system('which llvm-config 2>&1 >/dev/null')
        raise "llvm-config(1) must be available!\nNot found in PATH='#{ENV['PATH']}'"
      end

      $CFLAGS = "#{$CFLAGS} #{`llvm-config --cflags`.rstrip}"
      $CXXFLAGS = "#{$CXXFLAGS} #{`llvm-config --cxxflags`.rstrip}"
      $LDFLAGS = "#{$LDFLAGS} #{`llvm-config --ldflags`.rstrip} #{`llvm-config --libs core engine passes`}"
    end

    def compile_bitcode(c_file, bc_file)
      unless bc_file.end_with?('.bc')
        raise ArgumentError, "bitcode file should end with .bc but got '#{bc_file}'"
      end
      ll_file = bc_file.sub(/\.bc\z/, '.ll')

      incflags = [
        __dir__,
        "#{__dir__}/cruby",
        "#{__dir__}/cruby_extra",
        RbConfig::CONFIG['rubyhdrdir'],
        RbConfig::CONFIG['rubyarchhdrdir'],
      ].map {|d| "-I#{d}" }.join(' ')

      ruby_cflags = RbConfig::CONFIG['cflags'].dup.tap do |flags|
        %w[
          -Wno-packed-bitfield-compat
          -Wsuggest-attribute=noreturn
          -Wsuggest-attribute=format
          -Wno-maybe-uninitialized
        ].each { |f| flags.sub!(f, '') } # maybe caused by compiling ruby with gcc and this with clang.
      end

      debug_flags = "-Xclang -print-stats" if false

      sh "clang #{incflags} #{ruby_cflags} #{debug_flags} -Werror -O2 -S -emit-llvm -o #{extdir}/#{ll_file} #{extdir}/#{c_file}" # remove -Werror later
      sh "llvm-as -o #{extdir}/#{bc_file} #{extdir}/#{ll_file}"
    end

    def sh(command)
      $stderr.puts command
      system(command) || raise("Failed to execute '#{command}'")
    end

    def extdir
      File.expand_path("#{__dir__}/..")
    end
  end
end

LLRBExtconf.compile_bitcodes
LLRBExtconf.configure

create_makefile('llrb/llrb')
