require "./llvm/**"
require "c/string"

module LLVM
  # Returns the runtime version of LLVM.
  #
  # Starting with LLVM 16, this method returns the version as reported by
  # `LLVMGetVersion` at runtime. Older versions of LLVM do not expose this
  # information, so the value falls back to `LibLLVM::VERSION` which is
  # determined at compile time and might slightly be out of sync to the
  # dynamic library loaded at runtime.
  def self.version
    {% if LibLLVM.has_method?(:get_version) %}
      LibLLVM.get_version(out major, out minor, out patch)
      "#{major}.#{minor}.#{patch}"
    {% else %}
      LibLLVM::VERSION
    {% end %}
  end

  {% for target in LibLLVM::ALL_TARGETS %}
    {% name = target.downcase.id %}
    @@initialized_{{name}} = Atomic(Bool).new(false)

    def self.init_{{name}} : Nil
      return if @@initialized_{{name}}.swap(true)

      \{% if LibLLVM::BUILT_TARGETS.includes?({{name.symbolize}}) %}
        LibLLVM.initialize_{{name}}_target_info
        LibLLVM.initialize_{{name}}_target
        LibLLVM.initialize_{{name}}_target_mc
        LibLLVM.initialize_{{name}}_asm_printer
        LibLLVM.initialize_{{name}}_asm_parser
        LibLLVM.link_in_mc_jit
      \{% else %}
        raise "ERROR: LLVM was built without {{target.id}} target"
      \{% end %}
    end
  {% end %}

  def self.init_native_target : Nil
    {% if flag?(:i386) || flag?(:x86_64) %}
      init_x86
    {% elsif flag?(:aarch64) %}
      init_aarch64
    {% elsif flag?(:arm) %}
      init_arm
    {% elsif flag?(:wasm32) %}
      init_webassembly
    {% elsif flag?(:avr) %}
      init_avr
    {% else %}
      {% raise "Unsupported platform" %}
    {% end %}
  end

  def self.init_all_targets : Nil
    {% for target in LibLLVM::ALL_TARGETS %}
      {% name = target.downcase.id %}
      \{% if LibLLVM::BUILT_TARGETS.includes?({{name.symbolize}}) %}
        init_{{name}}
      \{% end %}
    {% end %}
  end

  @[Deprecated("This method has no effect")]
  def self.start_multithreaded : Bool
    if multithreaded?
      true
    else
      LibLLVM.start_multithreaded != 0
    end
  end

  @[Deprecated("This method has no effect")]
  def self.stop_multithreaded
    if multithreaded?
      LibLLVM.stop_multithreaded
    end
  end

  def self.multithreaded? : Bool
    LibLLVM.is_multithreaded != 0
  end

  def self.default_target_triple : String
    chars = LibLLVM.get_default_target_triple
    case triple = string_and_dispose(chars)
    when .starts_with?("aarch64-unknown-linux-android")
      # remove API version
      "aarch64-unknown-linux-android"
    when .starts_with?("x86_64-pc-solaris")
      # remove API version
      "x86_64-pc-solaris"
    else
      triple
    end
  end

  def self.host_cpu_name : String
    String.new LibLLVM.get_host_cpu_name
  end

  def self.normalize_triple(triple : String) : String
    normalized = LibLLVM.normalize_target_triple(triple)
    normalized = LLVM.string_and_dispose(normalized)

    normalized
  end

  def self.to_io(chars, io) : Nil
    io.write_string Slice.new(chars, LibC.strlen(chars))
    LibLLVM.dispose_message(chars)
  end

  def self.string_and_dispose(chars) : String
    string = String.new(chars)
    LibLLVM.dispose_message(chars)
    string
  end

  def self.parse_command_line_options(options : Enumerable(String), overview : String = "") : Nil
    c_strs = options.to_a(&.to_unsafe)
    LibLLVM.parse_command_line_options(c_strs.size, c_strs, overview)
  end

  protected def self.assert(error : LibLLVM::ErrorRef)
    if error
      chars = LibLLVM.get_error_message(error)
      raise String.new(chars).tap { LibLLVM.dispose_error_message(chars) }
    end
  end

  {% unless LibLLVM::IS_LT_130 %}
    def self.run_passes(module mod : Module, passes : String, target_machine : TargetMachine, options : PassBuilderOptions)
      LibLLVM.run_passes(mod, passes, target_machine, options)
    end
  {% end %}

  DEBUG_METADATA_VERSION = 3
end
