defmodule Wasmex.Engine do
  @moduledoc ~S"""
  An `Engine` which is a global context for compilation and management of Wasm
  modules.

  Engines store global configuration preferences such as compilation settings,
  enabled features, etc. You'll likely only need at most one of these for a
  program.

  You can create an engine with default configuration settings using
  `EngineConfig::default()`. Be sure to consult the documentation of
  `Wasmex.EngineConfig` for default settings.

  ## Example

      iex> {:ok, _engine} = Wasmex.Engine.new(%Wasmex.EngineConfig{})
  """

  alias Wasmex.EngineConfig

  @type t :: %__MODULE__{
          resource: binary(),
          reference: reference()
        }

  defstruct resource: nil,
            # The actual NIF store resource.
            # Normally the compiler will happily do stuff like inlining the
            # resource in attributes. This will convert the resource into an
            # empty binary with no warning. This will make that harder to
            # accidentally do.
            reference: nil

  def __wrap_resource__(resource) do
    %__MODULE__{
      resource: resource,
      reference: make_ref()
    }
  end

  @doc ~S"""
  Creates a new `Wasmex.Engine` with the specified options.

  ## Example

      iex> {:ok, _engine} = Wasmex.Engine.new(%Wasmex.EngineConfig{})
  """
  @spec new(EngineConfig.t()) :: {:ok, __MODULE__.t()} | {:error, binary()}
  def new(%EngineConfig{} = config) do
    case Wasmex.Native.engine_new(config) do
      {:error, err} -> {:error, err}
      resource -> {:ok, __wrap_resource__(resource)}
    end
  end

  @doc ~S"""
  Creates a new `Wasmex.Engine` with default settings.

  ## Example

      iex> _engine = Wasmex.Engine.default()
  """
  @spec default() :: __MODULE__.t()
  def default() do
    {:ok, engine} = new(%EngineConfig{})
    engine
  end

  @doc ~S"""
  Ahead-of-time (AOT) compiles a WebAssembly module.

  The `bytes` provided must be in one of two formats:

  * A [binary-encoded][binary] WebAssembly module
  * A [text-encoded][text] instance of the WebAssembly text format

  This method may be used to compile a module for use with a
  different target host. The output of this method may be used with
  `Wasmex.Module.unsafe_deserialize/2` on hosts compatible with the
  `Wasmex.EngineConfig` associated with this `Wasmex.Engine`.

  The output of this method is safe to send to another host machine
  for later execution. As the output is already a compiled module,
  translation and code generation will be skipped and this will
  improve the performance of constructing a `Wasmex.Module` from
  the output of this method.

  [binary]: https://webassembly.github.io/spec/core/binary/index.html
  [text]: https://webassembly.github.io/spec/core/text/index.html

  ## Example

      iex> {:ok, engine} = Wasmex.Engine.new(%Wasmex.EngineConfig{})
      iex> bytes = File.read!(TestHelper.wasm_test_file_path())
      iex> {:ok, _serialized_module} = Wasmex.Engine.precompile_module(engine, bytes)
  """
  @spec precompile_module(__MODULE__.t(), binary()) :: {:ok, binary()} | {:error, binary()}
  def precompile_module(%__MODULE__{resource: resource}, bytes) do
    case Wasmex.Native.engine_precompile_module(resource, bytes) do
      {:error, err} -> {:error, err}
      serialized_module -> {:ok, serialized_module}
    end
  end
end

defimpl Inspect, for: Wasmex.Engine do
  import Inspect.Algebra

  def inspect(dict, opts) do
    concat(["#Wasmex.Engine<", to_doc(dict.reference, opts), ">"])
  end
end
