package wasmtime

// #include <wasm.h>
// #include <wasmtime.h>
// #include <stdlib.h>
import "C"
import (
	"runtime"
	"unsafe"
)

// Strategy is the compilation strategies for wasmtime
type Strategy C.wasmtime_strategy_t

const (
	// StrategyAuto will let wasmtime automatically pick an appropriate compilation strategy
	StrategyAuto Strategy = C.WASMTIME_STRATEGY_AUTO
	// StrategyCranelift will force wasmtime to use the Cranelift backend
	StrategyCranelift Strategy = C.WASMTIME_STRATEGY_CRANELIFT
)

// OptLevel decides what degree of optimization wasmtime will perform on generated machine code
type OptLevel C.wasmtime_opt_level_t

const (
	// OptLevelNone will perform no optimizations
	OptLevelNone OptLevel = C.WASMTIME_OPT_LEVEL_NONE
	// OptLevelSpeed will optimize machine code to be as fast as possible
	OptLevelSpeed OptLevel = C.WASMTIME_OPT_LEVEL_SPEED
	// OptLevelSpeedAndSize will optimize machine code for speed, but also optimize
	// to be small, sometimes at the cost of speed.
	OptLevelSpeedAndSize OptLevel = C.WASMTIME_OPT_LEVEL_SPEED_AND_SIZE
)

// ProfilingStrategy decides what sort of profiling to enable, if any.
type ProfilingStrategy C.wasmtime_profiling_strategy_t

const (
	// ProfilingStrategyNone means no profiler will be used
	ProfilingStrategyNone ProfilingStrategy = C.WASMTIME_PROFILING_STRATEGY_NONE
	// ProfilingStrategyJitdump will use the "jitdump" linux support
	ProfilingStrategyJitdump ProfilingStrategy = C.WASMTIME_PROFILING_STRATEGY_JITDUMP
)

// Config holds options used to create an Engine and customize its behavior.
type Config struct {
	_ptr *C.wasm_config_t
}

// NewConfig creates a new `Config` with all default options configured.
func NewConfig() *Config {
	config := &Config{_ptr: C.wasm_config_new()}
	runtime.SetFinalizer(config, func(config *Config) {
		config.Close()
	})
	return config
}

// SetDebugInfo configures whether dwarf debug information for JIT code is enabled
func (cfg *Config) SetDebugInfo(enabled bool) {
	C.wasmtime_config_debug_info_set(cfg.ptr(), C.bool(enabled))
	runtime.KeepAlive(cfg)
}

// SetMaxWasmStack configures the maximum stack size, in bytes, that JIT code can use.
// The amount of stack space that wasm takes is always relative to the first invocation of wasm on the stack.
// Recursive calls with host frames in the middle will all need to fit within this setting.
// Note that this setting is not interpreted with 100% precision.
func (cfg *Config) SetMaxWasmStack(size int) {
	C.wasmtime_config_max_wasm_stack_set(cfg.ptr(), C.size_t(size))
	runtime.KeepAlive(cfg)
}

// SetWasmReferenceTypes configures whether the wasm reference types proposal is enabled
func (cfg *Config) SetWasmReferenceTypes(enabled bool) {
	C.wasmtime_config_wasm_reference_types_set(cfg.ptr(), C.bool(enabled))
	runtime.KeepAlive(cfg)
}

// SetWasmSIMD configures whether the wasm SIMD proposal is enabled
func (cfg *Config) SetWasmSIMD(enabled bool) {
	C.wasmtime_config_wasm_simd_set(cfg.ptr(), C.bool(enabled))
	runtime.KeepAlive(cfg)
}

// SetWasmRelaxedSIMD configures whether the wasm relaxed SIMD proposal is enabled
func (cfg *Config) SetWasmRelaxedSIMD(enabled bool) {
	C.wasmtime_config_wasm_relaxed_simd_set(cfg.ptr(), C.bool(enabled))
	runtime.KeepAlive(cfg)
}

// SetWasmRelaxedSIMDDeterministic configures whether the wasm relaxed SIMD proposal is in deterministic mode
func (cfg *Config) SetWasmRelaxedSIMDDeterministic(enabled bool) {
	C.wasmtime_config_wasm_relaxed_simd_deterministic_set(cfg.ptr(), C.bool(enabled))
	runtime.KeepAlive(cfg)
}

// SetWasmBulkMemory configures whether the wasm bulk memory proposal is enabled
func (cfg *Config) SetWasmBulkMemory(enabled bool) {
	C.wasmtime_config_wasm_bulk_memory_set(cfg.ptr(), C.bool(enabled))
	runtime.KeepAlive(cfg)
}

// SetWasmMultiValue configures whether the wasm multi value proposal is enabled
func (cfg *Config) SetWasmMultiValue(enabled bool) {
	C.wasmtime_config_wasm_multi_value_set(cfg.ptr(), C.bool(enabled))
	runtime.KeepAlive(cfg)
}

// SetWasmMultiMemory configures whether the wasm multi memory proposal is enabled
func (cfg *Config) SetWasmMultiMemory(enabled bool) {
	C.wasmtime_config_wasm_multi_memory_set(cfg.ptr(), C.bool(enabled))
	runtime.KeepAlive(cfg)
}

// SetWasmMemory64 configures whether the wasm memory64 proposal is enabled
func (cfg *Config) SetWasmMemory64(enabled bool) {
	C.wasmtime_config_wasm_memory64_set(cfg.ptr(), C.bool(enabled))
	runtime.KeepAlive(cfg)
}

// SetWasmTailCall configures whether tail calls are enabled
func (cfg *Config) SetWasmTailCall(enabled bool) {
	C.wasmtime_config_wasm_tail_call_set(cfg.ptr(), C.bool(enabled))
	runtime.KeepAlive(cfg)
}

// SetWasmFunctionReferences configures whether function references are enabled
func (cfg *Config) SetWasmFunctionReferences(enabled bool) {
	C.wasmtime_config_wasm_function_references_set(cfg.ptr(), C.bool(enabled))
	runtime.KeepAlive(cfg)
}

// SetWasmGC configures whether garbage collection is enabled
func (cfg *Config) SetWasmGC(enabled bool) {
	C.wasmtime_config_wasm_gc_set(cfg.ptr(), C.bool(enabled))
	runtime.KeepAlive(cfg)
}

// SetGCSupport enables or disables GC support in Wasmtime entirely
func (cfg *Config) SetGCSupport(enabled bool) {
	C.wasmtime_config_gc_support_set(cfg.ptr(), C.bool(enabled))
	runtime.KeepAlive(cfg)
}

// SetWasmWideArithmetic configures whether wide arithmetic is enabled
func (cfg *Config) SetWasmWideArithmetic(enabled bool) {
	C.wasmtime_config_wasm_wide_arithmetic_set(cfg.ptr(), C.bool(enabled))
	runtime.KeepAlive(cfg)
}

// SetConsumeFuel configures whether fuel is enabled
func (cfg *Config) SetConsumeFuel(enabled bool) {
	C.wasmtime_config_consume_fuel_set(cfg.ptr(), C.bool(enabled))
	runtime.KeepAlive(cfg)
}

// SetNativeUnwindInfo whether to generate native unwind information (e.g. .eh_frame on Linux).
func (cfg *Config) SetNativeUnwindInfo(enabled bool) {
	C.wasmtime_config_native_unwind_info_set(cfg.ptr(), C.bool(enabled))
	runtime.KeepAlive(cfg)
}

// SetMacOSUseMachPorts configures whether, when on macOS, Mach ports are used for exception handling instead
// of traditional Unix-based signal handling.
func (cfg *Config) SetMacOSUseMachPorts(enabled bool) {
	C.wasmtime_config_macos_use_mach_ports_set(cfg.ptr(), C.bool(enabled))
	runtime.KeepAlive(cfg)
}

// SetMemoryInitCOWSet configures whether copy-on-write memory-mapped data is used to initialize a linear memory.
//
// Initializing linear memory via a copy-on-write mapping can drastically improve instantiation costs of a
// WebAssembly module because copying memory is deferred. Additionally if a page of memory is only ever read from
// WebAssembly and never written too then the same underlying page of data will be reused between all
// instantiations of a module meaning that if a module is instantiated many times this can lower the overall
// memory required needed to run that module.
func (cfg *Config) SetMemoryInitCOWSet(enabled bool) {
	C.wasmtime_config_memory_init_cow_set(cfg.ptr(), C.bool(enabled))
	runtime.KeepAlive(cfg)
}

// SetProfiler configures what profiler strategy to use for generated code
func (cfg *Config) SetProfiler(profiler ProfilingStrategy) {
	C.wasmtime_config_profiler_set(cfg.ptr(), C.wasmtime_profiling_strategy_t(profiler))
	runtime.KeepAlive(cfg)
}

// SetEpochInterruption enables epoch-based instrumentation of generated code to
// interrupt WebAssembly execution when the current engine epoch exceeds a
// defined threshold.
func (cfg *Config) SetEpochInterruption(enable bool) {
	C.wasmtime_config_epoch_interruption_set(cfg.ptr(), C.bool(enable))
	runtime.KeepAlive(cfg)
}

// SetTarget configures the target triple that this configuration will produce
// machine code for.
//
// This option defaults to the native host. Calling this method will
// additionally disable inference of the native features of the host (e.g.
// detection of SSE4.2 on x86_64 hosts). Native features can be reenabled with
// the `cranelift_flag_{set,enable}` properties.
//
// For more information see the Rust documentation at
// https://docs.wasmtime.dev/api/wasmtime/struct.Config.html#method.config
func (cfg *Config) SetTarget(target string) error {
	cstr := C.CString(target)
	err := C.wasmtime_config_target_set(cfg.ptr(), cstr)
	C.free(unsafe.Pointer(cstr))
	runtime.KeepAlive(cfg)
	if err != nil {
		return mkError(err)
	}
	return nil
}

// See comments in `ffi.go` for what's going on here
func (cfg *Config) ptr() *C.wasm_config_t {
	ret := cfg._ptr
	if ret == nil {
		panic("Config has already been used up")
	}
	maybeGC()
	return ret
}

// Close will deallocate this config's state explicitly.
//
// For more information see the documentation for engine.Close()
func (cfg *Config) Close() {
	if cfg._ptr == nil {
		return
	}
	runtime.SetFinalizer(cfg, nil)
	C.wasm_config_delete(cfg._ptr)
	cfg._ptr = nil
}
