{% if flag?(:win32) %}
  require "c/process"
{% end %}
require "crystal/tracing"

module GC
  def self.init
    Crystal::System::Thread.init_suspend_resume
  end

  # :nodoc:
  def self.malloc(size : LibC::SizeT) : Void*
    Crystal.trace :gc, "malloc", size: size
    # libc malloc is not guaranteed to return cleared memory, so we need to
    # explicitly clear it. Ref: https://github.com/crystal-lang/crystal/issues/14678
    LibC.malloc(size).tap(&.clear)
  end

  # :nodoc:
  def self.malloc_atomic(size : LibC::SizeT) : Void*
    Crystal.trace :gc, "malloc", size: size, atomic: 1
    LibC.malloc(size)
  end

  # :nodoc:
  def self.realloc(pointer : Void*, size : LibC::SizeT) : Void*
    Crystal.trace :gc, "realloc", size: size
    LibC.realloc(pointer, size)
  end

  def self.collect
  end

  def self.enable
  end

  def self.disable
  end

  def self.free(pointer : Void*) : Nil
    Crystal.trace :gc, "free"
    LibC.free(pointer)
  end

  def self.is_heap_ptr(pointer : Void*) : Bool
    false
  end

  def self.add_finalizer(object)
  end

  def self.register_disappearing_link(pointer : Void**)
  end

  def self.stats : GC::Stats
    Stats.new(
      # collections: 0,
      # bytes_found: 0,
      heap_size: 0,
      free_bytes: 0,
      unmapped_bytes: 0,
      bytes_since_gc: 0,
      total_bytes: 0)
  end

  def self.prof_stats
    ProfStats.new(
      heap_size: 0,
      free_bytes: 0,
      unmapped_bytes: 0,
      bytes_since_gc: 0,
      bytes_before_gc: 0,
      non_gc_bytes: 0,
      gc_no: 0,
      markers_m1: 0,
      bytes_reclaimed_since_gc: 0,
      reclaimed_bytes_before_gc: 0,
      expl_freed_bytes_since_gc: 0,
      obtained_from_os_bytes: 0)
  end

  {% if flag?(:win32) %}
    # :nodoc:
    def self.beginthreadex(security : Void*, stack_size : LibC::UInt, start_address : Void* -> LibC::UInt, arglist : Void*, initflag : LibC::UInt, thrdaddr : LibC::UInt*) : LibC::HANDLE
      ret = LibC._beginthreadex(security, stack_size, start_address, arglist, initflag, thrdaddr)
      raise RuntimeError.from_errno("_beginthreadex") if ret.null?
      ret.as(LibC::HANDLE)
    end
  {% elsif !flag?(:wasm32) %}
    # :nodoc:
    def self.pthread_create(thread : LibC::PthreadT*, attr : LibC::PthreadAttrT*, start : Void* -> Void*, arg : Void*)
      LibC.pthread_create(thread, attr, start, arg)
    end

    # :nodoc:
    def self.pthread_join(thread : LibC::PthreadT)
      LibC.pthread_join(thread, nil)
    end

    # :nodoc:
    def self.pthread_detach(thread : LibC::PthreadT)
      LibC.pthread_detach(thread)
    end
  {% end %}

  # :nodoc:
  def self.current_thread_stack_bottom : {Void*, Void*}
    {Pointer(Void).null, Pointer(Void).null}
  end

  # :nodoc:
  {% if flag?(:preview_mt) %}
    def self.set_stackbottom(thread : Thread, stack_bottom : Void*)
      # NOTE we could store stack_bottom per thread,
      #      and return it in `#current_thread_stack_bottom`,
      #      but there is no actual use for that.
    end
  {% else %}
    def self.set_stackbottom(stack_bottom : Void*)
    end
  {% end %}

  # :nodoc:
  def self.lock_read
  end

  # :nodoc:
  def self.unlock_read
  end

  # :nodoc:
  def self.lock_write
  end

  # :nodoc:
  def self.unlock_write
  end

  # :nodoc:
  def self.push_stack(stack_top, stack_bottom)
  end

  # Stop and start the world.
  #
  # This isn't a GC-safe stop-the-world implementation (it may allocate objects
  # while stopping the world), but the guarantees are enough for the purpose of
  # gc_none. It could be GC-safe if Thread::LinkedList(T) became a struct, and
  # Thread::Mutex either became a struct or provide low level abstraction
  # methods that directly interact with syscalls (without allocating).
  #
  # Thread safety is guaranteed by the mutex in Thread::LinkedList: either a
  # thread is starting and hasn't added itself to the list (it will block until
  # it can acquire the lock), or is currently adding itself (the current thread
  # will block until it can acquire the lock).
  #
  # In both cases there can't be a deadlock since we won't suspend another
  # thread until it has successfuly added (or removed) itself to (from) the
  # linked list and released the lock, and the other thread won't progress until
  # it can add (or remove) itself from the list.
  #
  # Finally, we lock the mutex and keep it locked until we resume the world, so
  # any thread waiting on the mutex will only be resumed when the world is
  # resumed.

  # :nodoc:
  def self.stop_world : Nil
    current_thread = Thread.current

    # grab the lock (and keep it until the world is restarted)
    Thread.lock

    # tell all threads to stop (async)
    Thread.unsafe_each do |thread|
      thread.suspend unless thread == current_thread
    end

    # wait for all threads to have stopped
    Thread.unsafe_each do |thread|
      thread.wait_suspended unless thread == current_thread
    end
  end

  # :nodoc:
  def self.start_world : Nil
    current_thread = Thread.current

    # tell all threads to resume
    Thread.unsafe_each do |thread|
      thread.resume unless thread == current_thread
    end

    # finally, we can release the lock
    Thread.unlock
  end
end
