use super::{EnforcedLimits, StackConfig};
use crate::core::FuelCostsProvider;
use wasmparser::WasmFeatures;

/// Configuration for an [`Engine`].
///
/// [`Engine`]: [`crate::Engine`]
#[derive(Debug, Clone)]
pub struct Config {
    /// The limits set on the value stack and call stack.
    pub(crate) stack: StackConfig,
    /// The Wasm features used when validating or translating functions.
    features: WasmFeatures,
    /// Is `true` if Wasmi executions shall consume fuel.
    consume_fuel: bool,
    /// Is `true` if Wasmi shall ignore Wasm custom sections when parsing Wasm modules.
    ignore_custom_sections: bool,
    /// The configured fuel costs of all Wasmi bytecode instructions.
    fuel_costs: FuelCostsProvider,
    /// The mode of Wasm to Wasmi bytecode compilation.
    compilation_mode: CompilationMode,
    /// Enforced limits for Wasm module parsing and compilation.
    limits: EnforcedLimits,
}

/// The chosen mode of Wasm to Wasmi bytecode compilation.
#[derive(Debug, Default, Copy, Clone)]
pub enum CompilationMode {
    /// The Wasm code is compiled eagerly to Wasmi bytecode.
    Eager,
    /// The Wasm code is validated eagerly and translated lazily on first use.
    #[default]
    LazyTranslation,
    /// The Wasm code is validated and translated lazily on first use.
    ///
    /// # Note
    ///
    /// This mode must not be used if the result of Wasm execution
    /// must be deterministic amongst multiple Wasm implementations.
    Lazy,
}

impl Default for Config {
    fn default() -> Self {
        Self {
            stack: StackConfig::default(),
            features: Self::default_features(),
            consume_fuel: false,
            ignore_custom_sections: false,
            fuel_costs: FuelCostsProvider::default(),
            compilation_mode: CompilationMode::default(),
            limits: EnforcedLimits::default(),
        }
    }
}

impl Config {
    /// Returns the default [`WasmFeatures`].
    fn default_features() -> WasmFeatures {
        let mut features = WasmFeatures::empty();
        features.set(WasmFeatures::MUTABLE_GLOBAL, true);
        features.set(WasmFeatures::MULTI_VALUE, true);
        features.set(WasmFeatures::MULTI_MEMORY, true);
        features.set(WasmFeatures::SATURATING_FLOAT_TO_INT, true);
        features.set(WasmFeatures::SIGN_EXTENSION, true);
        features.set(WasmFeatures::BULK_MEMORY, true);
        features.set(WasmFeatures::REFERENCE_TYPES, true);
        features.set(WasmFeatures::GC_TYPES, true); // required by reference-types
        features.set(WasmFeatures::TAIL_CALL, true);
        features.set(WasmFeatures::EXTENDED_CONST, true);
        features.set(WasmFeatures::FLOATS, true);
        features.set(WasmFeatures::CUSTOM_PAGE_SIZES, false);
        features.set(WasmFeatures::MEMORY64, true);
        features.set(WasmFeatures::WIDE_ARITHMETIC, false);
        features.set(WasmFeatures::SIMD, cfg!(feature = "simd"));
        features.set(WasmFeatures::RELAXED_SIMD, cfg!(feature = "simd"));
        features
    }

    /// Sets the maximum recursion depth of the [`Engine`]'s stack during execution.
    ///
    /// # Note
    ///
    /// An execution traps if it exceeds this limits.
    ///
    /// [`Engine`]: [`crate::Engine`]
    pub fn set_max_recursion_depth(&mut self, value: usize) -> &mut Self {
        self.stack.set_max_recursion_depth(value);
        self
    }

    /// Sets the minimum (or initial) height of the [`Engine`]'s value stack in bytes.
    ///
    /// # Note
    ///
    /// - Lower initial heights may improve memory consumption.
    /// - Higher initial heights may improve cold start times.
    ///
    /// # Panics
    ///
    /// If `value` is greater than the current maximum height of the value stack.
    ///
    /// [`Engine`]: [`crate::Engine`]
    pub fn set_min_stack_height(&mut self, value: usize) -> &mut Self {
        if self.stack.set_min_stack_height(value).is_err() {
            let max = self.stack.max_stack_height();
            panic!("minimum stack height exceeds maximum: min={value}, max={max}");
        }
        self
    }

    /// Sets the maximum height of the [`Engine`]'s value stack in bytes.
    ///
    /// # Note
    ///
    /// An execution traps if it exceeds this limits.
    ///
    /// # Panics
    ///
    /// If `value` is less than the current minimum height of the value stack.
    ///
    /// [`Engine`]: [`crate::Engine`]
    pub fn set_max_stack_height(&mut self, value: usize) -> &mut Self {
        if self.stack.set_max_stack_height(value).is_err() {
            let min = self.stack.min_stack_height();
            panic!("maximum stack height is lower than minimum: min={min}, max={value}");
        }
        self
    }

    /// Sets the maximum number of cached stacks for reuse for the [`Config`].
    ///
    /// # Note
    ///
    /// - A higher value may improve execution performance.
    /// - A lower value may improve memory consumption.
    pub fn set_max_cached_stacks(&mut self, value: usize) -> &mut Self {
        self.stack.set_max_cached_stacks(value);
        self
    }

    /// Enable or disable the [`mutable-global`] Wasm proposal for the [`Config`].
    ///
    /// # Note
    ///
    /// Enabled by default.
    ///
    /// [`mutable-global`]: https://github.com/WebAssembly/mutable-global
    pub fn wasm_mutable_global(&mut self, enable: bool) -> &mut Self {
        self.features.set(WasmFeatures::MUTABLE_GLOBAL, enable);
        self
    }

    /// Enable or disable the [`sign-extension`] Wasm proposal for the [`Config`].
    ///
    /// # Note
    ///
    /// Enabled by default.
    ///
    /// [`sign-extension`]: https://github.com/WebAssembly/sign-extension-ops
    pub fn wasm_sign_extension(&mut self, enable: bool) -> &mut Self {
        self.features.set(WasmFeatures::SIGN_EXTENSION, enable);
        self
    }

    /// Enable or disable the [`saturating-float-to-int`] Wasm proposal for the [`Config`].
    ///
    /// # Note
    ///
    /// Enabled by default.
    ///
    /// [`saturating-float-to-int`]:
    /// https://github.com/WebAssembly/nontrapping-float-to-int-conversions
    pub fn wasm_saturating_float_to_int(&mut self, enable: bool) -> &mut Self {
        self.features
            .set(WasmFeatures::SATURATING_FLOAT_TO_INT, enable);
        self
    }

    /// Enable or disable the [`multi-value`] Wasm proposal for the [`Config`].
    ///
    /// # Note
    ///
    /// Enabled by default.
    ///
    /// [`multi-value`]: https://github.com/WebAssembly/multi-value
    pub fn wasm_multi_value(&mut self, enable: bool) -> &mut Self {
        self.features.set(WasmFeatures::MULTI_VALUE, enable);
        self
    }

    /// Enable or disable the [`multi-memory`] Wasm proposal for the [`Config`].
    ///
    /// # Note
    ///
    /// Enabled by default.
    ///
    /// [`multi-memory`]: https://github.com/WebAssembly/multi-memory
    pub fn wasm_multi_memory(&mut self, enable: bool) -> &mut Self {
        self.features.set(WasmFeatures::MULTI_MEMORY, enable);
        self
    }

    /// Enable or disable the [`bulk-memory`] Wasm proposal for the [`Config`].
    ///
    /// # Note
    ///
    /// Enabled by default.
    ///
    /// [`bulk-memory`]: https://github.com/WebAssembly/bulk-memory-operations
    pub fn wasm_bulk_memory(&mut self, enable: bool) -> &mut Self {
        self.features.set(WasmFeatures::BULK_MEMORY, enable);
        self
    }

    /// Enable or disable the [`reference-types`] Wasm proposal for the [`Config`].
    ///
    /// # Note
    ///
    /// Enabled by default.
    ///
    /// [`reference-types`]: https://github.com/WebAssembly/reference-types
    pub fn wasm_reference_types(&mut self, enable: bool) -> &mut Self {
        self.features.set(WasmFeatures::REFERENCE_TYPES, enable);
        self.features.set(WasmFeatures::GC_TYPES, enable);
        self
    }

    /// Enable or disable the [`tail-call`] Wasm proposal for the [`Config`].
    ///
    /// # Note
    ///
    /// Enabled by default.
    ///
    /// [`tail-call`]: https://github.com/WebAssembly/tail-call
    pub fn wasm_tail_call(&mut self, enable: bool) -> &mut Self {
        self.features.set(WasmFeatures::TAIL_CALL, enable);
        self
    }

    /// Enable or disable the [`extended-const`] Wasm proposal for the [`Config`].
    ///
    /// # Note
    ///
    /// Enabled by default.
    ///
    /// [`extended-const`]: https://github.com/WebAssembly/extended-const
    pub fn wasm_extended_const(&mut self, enable: bool) -> &mut Self {
        self.features.set(WasmFeatures::EXTENDED_CONST, enable);
        self
    }

    /// Enable or disable the [`custom-page-sizes`] Wasm proposal for the [`Config`].
    ///
    /// # Note
    ///
    /// Disabled by default.
    ///
    /// [`custom-page-sizes`]: https://github.com/WebAssembly/custom-page-sizes
    pub fn wasm_custom_page_sizes(&mut self, enable: bool) -> &mut Self {
        self.features.set(WasmFeatures::CUSTOM_PAGE_SIZES, enable);
        self
    }

    /// Enable or disable the [`memory64`] Wasm proposal for the [`Config`].
    ///
    /// # Note
    ///
    /// Disabled by default.
    ///
    /// [`memory64`]: https://github.com/WebAssembly/memory64
    pub fn wasm_memory64(&mut self, enable: bool) -> &mut Self {
        self.features.set(WasmFeatures::MEMORY64, enable);
        self
    }

    /// Enable or disable the [`wide-arithmetic`] Wasm proposal for the [`Config`].
    ///
    /// Disabled by default.
    ///
    /// [`wide-arithmetic`]: https://github.com/WebAssembly/wide-arithmetic
    pub fn wasm_wide_arithmetic(&mut self, enable: bool) -> &mut Self {
        self.features.set(WasmFeatures::WIDE_ARITHMETIC, enable);
        self
    }

    /// Enable or disable the [`simd`] Wasm proposal for the [`Config`].
    ///
    /// Enabled by default.
    ///
    /// [`simd`]: https://github.com/WebAssembly/simd
    #[cfg(feature = "simd")]
    pub fn wasm_simd(&mut self, enable: bool) -> &mut Self {
        self.features.set(WasmFeatures::SIMD, enable);
        self
    }

    /// Enable or disable the [`relaxed-simd`] Wasm proposal for the [`Config`].
    ///
    /// Enabled by default.
    ///
    /// [`relaxed-simd`]: https://github.com/WebAssembly/relaxed-simd
    #[cfg(feature = "simd")]
    pub fn wasm_relaxed_simd(&mut self, enable: bool) -> &mut Self {
        self.features.set(WasmFeatures::RELAXED_SIMD, enable);
        self
    }

    /// Enable or disable Wasm floating point (`f32` and `f64`) instructions and types.
    ///
    /// Enabled by default.
    pub fn floats(&mut self, enable: bool) -> &mut Self {
        self.features.set(WasmFeatures::FLOATS, enable);
        self
    }

    /// Configures whether Wasmi will consume fuel during execution to either halt execution as desired.
    ///
    /// # Note
    ///
    /// This configuration can be used to make Wasmi instrument its internal bytecode
    /// so that it consumes fuel as it executes. Once an execution runs out of fuel
    /// a [`TrapCode::OutOfFuel`](crate::TrapCode::OutOfFuel) trap is raised.
    /// This way users can deterministically halt or yield the execution of WebAssembly code.
    ///
    /// - Use [`Store::set_fuel`](crate::Store::set_fuel) to set the remaining fuel of the [`Store`] before
    ///   executing some code as the [`Store`] start with no fuel.
    /// - Use [`Caller::set_fuel`](crate::Caller::set_fuel) to update the remaining fuel when executing host functions.
    ///
    /// Disabled by default.
    ///
    /// [`Store`]: crate::Store
    /// [`Engine`]: crate::Engine
    pub fn consume_fuel(&mut self, enable: bool) -> &mut Self {
        self.consume_fuel = enable;
        self
    }

    /// Returns `true` if the [`Config`] enables fuel consumption by the [`Engine`].
    ///
    /// [`Engine`]: crate::Engine
    pub(crate) fn get_consume_fuel(&self) -> bool {
        self.consume_fuel
    }

    /// Configures whether Wasmi will ignore custom sections when parsing Wasm modules.
    ///
    /// Default value: `false`
    pub fn ignore_custom_sections(&mut self, enable: bool) -> &mut Self {
        self.ignore_custom_sections = enable;
        self
    }

    /// Returns `true` if the [`Config`] mandates to ignore Wasm custom sections when parsing Wasm modules.
    pub(crate) fn get_ignore_custom_sections(&self) -> bool {
        self.ignore_custom_sections
    }

    /// Returns the configured [`FuelCostsProvider`].
    pub(crate) fn fuel_costs(&self) -> &FuelCostsProvider {
        &self.fuel_costs
    }

    /// Sets the [`CompilationMode`] used for the [`Engine`].
    ///
    /// By default [`CompilationMode::LazyTranslation`] is used.
    ///
    /// [`Engine`]: crate::Engine
    pub fn compilation_mode(&mut self, mode: CompilationMode) -> &mut Self {
        self.compilation_mode = mode;
        self
    }

    /// Returns the [`CompilationMode`] used for the [`Engine`].
    ///
    /// [`Engine`]: crate::Engine
    pub(super) fn get_compilation_mode(&self) -> CompilationMode {
        self.compilation_mode
    }

    /// Sets the [`EnforcedLimits`] enforced by the [`Engine`] for Wasm module parsing and compilation.
    ///
    /// By default no limits are enforced.
    ///
    /// [`Engine`]: crate::Engine
    pub fn enforced_limits(&mut self, limits: EnforcedLimits) -> &mut Self {
        self.limits = limits;
        self
    }

    /// Returns the [`EnforcedLimits`] used for the [`Engine`].
    ///
    /// [`Engine`]: crate::Engine
    pub(crate) fn get_enforced_limits(&self) -> &EnforcedLimits {
        &self.limits
    }

    /// Returns the [`WasmFeatures`] represented by the [`Config`].
    pub(crate) fn wasm_features(&self) -> WasmFeatures {
        self.features
    }
}
