require "file_utils"
require "./interpreted"
{% if flag?(:msvc) %}
  require "crystal/system/win32/visual_studio"
{% end %}

SPEC_TEMPFILE_PATH    = File.join(Dir.tempdir, "cr-spec-#{Random.new.hex(4)}")
SPEC_TEMPFILE_CLEANUP = ENV["SPEC_TEMPFILE_CLEANUP"]? != "0"

# Expands *paths* in a unique temp folder and yield them to the block.
#
# The *paths* are interpreted relative to a unique folder for every spec run and
# prefixed by the name of the spec file that requests them.
#
# The constructed path is yielded to the block and cleaned up afterwards.
#
# Paths should still be uniquely chosen inside a spec file. This helper
# ensures they're placed in the temporary location (`Dir.tempdir`),
# avoids name clashes between parallel spec runs and cleans up afterwards.
#
# The unique directory for the spec run is removed `at_exit`.
#
# If the environment variable `SPEC_TEMPFILE_CLEANUP` is set to `0`, no paths
# will be cleaned up, enabling easier debugging.
def with_tempfile(*paths, file = __FILE__, &)
  calling_spec = File.basename(file).rchop("_spec.cr")
  paths = paths.map { |path| File.join(SPEC_TEMPFILE_PATH, calling_spec, path) }
  FileUtils.mkdir_p(File.join(SPEC_TEMPFILE_PATH, calling_spec))

  begin
    yield *paths
  ensure
    if SPEC_TEMPFILE_CLEANUP
      paths.each do |path|
        FileUtils.rm_rf(path) if File.exists?(path)
      end
    end
  end
end

# Creates a temporary directory and exposes it as working directory in the
# block.
#
# Use the `name` parameter to specify the name of the directory.
# It should be unique per spec file. If nil, a random name is chosen
# (`File.tempname`).
#
# The lifetime of the temporary directory is the same was for `.with_tempfile`.
def with_tempdir(name : String? = nil, *, file = __FILE__, &)
  name ||= File.tempname(dir: ".")
  with_tempfile(name, file: file) do |path|
    Dir.mkdir_p(path)
    Dir.cd(path) do
      yield
    end
  end
end

def with_temp_executable(name, file = __FILE__, &)
  {% if flag?(:win32) %}
    name += ".exe"
  {% end %}
  with_tempfile(name, file: file) do |tempname|
    yield tempname
  end
end

def with_temp_c_object_file(c_code, *, filename = "temp_c", file = __FILE__, &)
  # can't use backtick in interpreted code (#12241)
  pending_interpreted! "Unable to compile C code in interpreted code"

  obj_ext = {{ flag?(:msvc) ? ".obj" : ".o" }}
  with_tempfile("#{filename}.c", "#{filename}#{obj_ext}", file: file) do |c_filename, o_filename|
    File.write(c_filename, c_code)

    {% if flag?(:msvc) %}
      # following is based on `Crystal::Compiler#linker_command`
      unless cl = ENV["CC"]?
        cl = "cl.exe"
        if msvc_path = Crystal::System::VisualStudio.find_latest_msvc_path
          # we won't be cross-compiling the specs binaries, so host and target
          # bits are identical
          bits = {{ flag?(:bits64) ? "x64" : "x86" }}
          cl = Process.quote(msvc_path.join("bin", "Host#{bits}", bits, cl).to_s)
        end
      end

      `#{cl} /nologo /c /MD #{Process.quote(c_filename)} #{Process.quote("/Fo#{o_filename}")}`.should be_truthy
    {% else %}
      `#{ENV["CC"]? || "cc"} #{Process.quote(c_filename)} -c -o #{Process.quote(o_filename)}`.should be_truthy
    {% end %}

    yield o_filename
  end
end

if SPEC_TEMPFILE_CLEANUP
  at_exit do
    FileUtils.rm_rf(SPEC_TEMPFILE_PATH) if Dir.exists?(SPEC_TEMPFILE_PATH)
  end
end
