//! Configuration settings for [`Engine`].

use crate::stdlib::{format, num::NonZeroU8, string::String};
use crate::token::Token;
use crate::Engine;

#[cfg(not(feature = "no_module"))]
use crate::stdlib::boxed::Box;

impl Engine {
    /// Control whether and how the [`Engine`] will optimize an [`AST`][crate::AST] after compilation.
    ///
    /// Not available under the `no_optimize` feature.
    #[cfg(not(feature = "no_optimize"))]
    #[inline(always)]
    pub fn set_optimization_level(
        &mut self,
        optimization_level: crate::OptimizationLevel,
    ) -> &mut Self {
        self.optimization_level = optimization_level;
        self
    }
    /// The current optimization level.
    /// It controls whether and how the [`Engine`] will optimize an [`AST`][crate::AST] after compilation.
    ///
    /// Not available under the `no_optimize` feature.
    #[cfg(not(feature = "no_optimize"))]
    #[inline(always)]
    pub fn optimization_level(&self) -> crate::OptimizationLevel {
        self.optimization_level
    }
    /// Enable/disable doc-comments.
    #[inline(always)]
    pub fn set_doc_comments(&mut self, enable: bool) -> &mut Self {
        self.disable_doc_comments = !enable;
        self
    }
    /// Set the maximum levels of function calls allowed for a script in order to avoid
    /// infinite recursion and stack overflows.
    #[cfg(not(feature = "unchecked"))]
    #[cfg(not(feature = "no_function"))]
    #[inline(always)]
    pub fn set_max_call_levels(&mut self, levels: usize) -> &mut Self {
        self.limits.max_call_stack_depth = levels;
        self
    }
    /// The maximum levels of function calls allowed for a script.
    #[cfg(not(feature = "unchecked"))]
    #[cfg(not(feature = "no_function"))]
    #[inline(always)]
    pub fn max_call_levels(&self) -> usize {
        self.limits.max_call_stack_depth
    }
    /// Set the maximum number of operations allowed for a script to run to avoid
    /// consuming too much resources (0 for unlimited).
    #[cfg(not(feature = "unchecked"))]
    #[inline(always)]
    pub fn set_max_operations(&mut self, operations: u64) -> &mut Self {
        self.limits.max_operations = if operations == u64::MAX {
            0
        } else {
            operations
        };
        self
    }
    /// The maximum number of operations allowed for a script to run (0 for unlimited).
    #[cfg(not(feature = "unchecked"))]
    #[inline(always)]
    pub fn max_operations(&self) -> u64 {
        self.limits.max_operations
    }
    /// Set the maximum number of imported [modules][crate::Module] allowed for a script.
    #[cfg(not(feature = "unchecked"))]
    #[cfg(not(feature = "no_module"))]
    #[inline(always)]
    pub fn set_max_modules(&mut self, modules: usize) -> &mut Self {
        self.limits.max_modules = modules;
        self
    }
    /// The maximum number of imported [modules][crate::Module] allowed for a script.
    #[cfg(not(feature = "unchecked"))]
    #[cfg(not(feature = "no_module"))]
    #[inline(always)]
    pub fn max_modules(&self) -> usize {
        self.limits.max_modules
    }
    /// Set the depth limits for expressions (0 for unlimited).
    #[cfg(not(feature = "unchecked"))]
    #[inline(always)]
    pub fn set_max_expr_depths(
        &mut self,
        max_expr_depth: usize,
        #[cfg(not(feature = "no_function"))] max_function_expr_depth: usize,
    ) -> &mut Self {
        self.limits.max_expr_depth = if max_expr_depth == usize::MAX {
            0
        } else {
            max_expr_depth
        };
        #[cfg(not(feature = "no_function"))]
        {
            self.limits.max_function_expr_depth = if max_function_expr_depth == usize::MAX {
                0
            } else {
                max_function_expr_depth
            };
        }
        self
    }
    /// The depth limit for expressions (0 for unlimited).
    #[cfg(not(feature = "unchecked"))]
    #[inline(always)]
    pub fn max_expr_depth(&self) -> usize {
        self.limits.max_expr_depth
    }
    /// The depth limit for expressions in functions (0 for unlimited).
    #[cfg(not(feature = "unchecked"))]
    #[cfg(not(feature = "no_function"))]
    #[inline(always)]
    pub fn max_function_expr_depth(&self) -> usize {
        self.limits.max_function_expr_depth
    }
    /// Set the maximum length of [strings][crate::ImmutableString] (0 for unlimited).
    #[cfg(not(feature = "unchecked"))]
    #[inline(always)]
    pub fn set_max_string_size(&mut self, max_size: usize) -> &mut Self {
        self.limits.max_string_size = if max_size == usize::MAX { 0 } else { max_size };
        self
    }
    /// The maximum length of [strings][crate::ImmutableString] (0 for unlimited).
    #[cfg(not(feature = "unchecked"))]
    #[inline(always)]
    pub fn max_string_size(&self) -> usize {
        self.limits.max_string_size
    }
    /// Set the maximum length of [arrays][crate::Array] (0 for unlimited).
    #[cfg(not(feature = "unchecked"))]
    #[cfg(not(feature = "no_index"))]
    #[inline(always)]
    pub fn set_max_array_size(&mut self, max_size: usize) -> &mut Self {
        self.limits.max_array_size = if max_size == usize::MAX { 0 } else { max_size };
        self
    }
    /// The maximum length of [arrays][crate::Array] (0 for unlimited).
    #[cfg(not(feature = "unchecked"))]
    #[cfg(not(feature = "no_index"))]
    #[inline(always)]
    pub fn max_array_size(&self) -> usize {
        self.limits.max_array_size
    }
    /// Set the maximum length of [object maps][crate::Map] (0 for unlimited).
    #[cfg(not(feature = "unchecked"))]
    #[cfg(not(feature = "no_object"))]
    #[inline(always)]
    pub fn set_max_map_size(&mut self, max_size: usize) -> &mut Self {
        self.limits.max_map_size = if max_size == usize::MAX { 0 } else { max_size };
        self
    }
    /// The maximum length of [object maps][crate::Map] (0 for unlimited).
    #[cfg(not(feature = "unchecked"))]
    #[cfg(not(feature = "no_object"))]
    #[inline(always)]
    pub fn max_map_size(&self) -> usize {
        self.limits.max_map_size
    }
    /// Set the module resolution service used by the [`Engine`].
    ///
    /// Not available under the `no_module` feature.
    #[cfg(not(feature = "no_module"))]
    #[inline(always)]
    pub fn set_module_resolver(
        &mut self,
        resolver: impl crate::ModuleResolver + 'static,
    ) -> &mut Self {
        self.module_resolver = Box::new(resolver);
        self
    }
    /// Disable a particular keyword or operator in the language.
    ///
    /// # Examples
    ///
    /// The following will raise an error during parsing because the `if` keyword is disabled
    /// and is recognized as a variable name!
    ///
    /// ```rust,should_panic
    /// # fn main() -> Result<(), rhai::ParseError> {
    /// use rhai::Engine;
    ///
    /// let mut engine = Engine::new();
    ///
    /// engine.disable_symbol("if");    // disable the 'if' keyword
    ///
    /// engine.compile("let x = if true { 42 } else { 0 };")?;
    /// //                      ^ 'if' is rejected as a reserved keyword
    /// # Ok(())
    /// # }
    /// ```
    ///
    /// The following will raise an error during parsing because the `+=` operator is disabled.
    ///
    /// ```rust,should_panic
    /// # fn main() -> Result<(), rhai::ParseError> {
    /// use rhai::Engine;
    ///
    /// let mut engine = Engine::new();
    ///
    /// engine.disable_symbol("+=");    // disable the '+=' operator
    ///
    /// engine.compile("let x = 42; x += 1;")?;
    /// //                            ^ unknown operator
    /// # Ok(())
    /// # }
    /// ```
    #[inline(always)]
    pub fn disable_symbol(&mut self, symbol: &str) -> &mut Self {
        self.disabled_symbols.insert(symbol.into());
        self
    }
    /// Register a custom operator with a precedence into the language.
    ///
    /// The operator must be a valid identifier (i.e. it cannot be a symbol).
    ///
    /// The precedence cannot be zero.
    ///
    /// # Example
    ///
    /// ```rust
    /// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
    /// use rhai::{Engine, RegisterFn};
    ///
    /// let mut engine = Engine::new();
    ///
    /// // Register a custom operator called 'foo' and give it
    /// // a precedence of 160 (i.e. between +|- and *|/).
    /// engine.register_custom_operator("foo", 160).unwrap();
    ///
    /// // Register a binary function named 'foo'
    /// engine.register_fn("foo", |x: i64, y: i64| (x * y) - (x + y));
    ///
    /// assert_eq!(
    ///     engine.eval_expression::<i64>("1 + 2 * 3 foo 4 - 5 / 6")?,
    ///     15
    /// );
    /// # Ok(())
    /// # }
    /// ```
    pub fn register_custom_operator(
        &mut self,
        keyword: &str,
        precedence: u8,
    ) -> Result<&mut Self, String> {
        let precedence = NonZeroU8::new(precedence);

        if precedence.is_none() {
            return Err("precedence cannot be zero".into());
        }

        match Token::lookup_from_syntax(keyword) {
            // Standard identifiers, reserved keywords and custom keywords are OK
            None | Some(Token::Reserved(_)) | Some(Token::Custom(_)) => (),
            // Active standard keywords cannot be made custom
            // Disabled keywords are OK
            Some(token) if token.is_keyword() => {
                if !self.disabled_symbols.contains(token.syntax().as_ref()) {
                    return Err(format!("'{}' is a reserved keyword", keyword).into());
                }
            }
            // Active standard operators cannot be made custom
            Some(token) if token.is_operator() => {
                if !self.disabled_symbols.contains(token.syntax().as_ref()) {
                    return Err(format!("'{}' is a reserved operator", keyword).into());
                }
            }
            // Active standard symbols cannot be made custom
            Some(token) if !self.disabled_symbols.contains(token.syntax().as_ref()) => {
                return Err(format!("'{}' is a reserved symbol", keyword).into())
            }
            // Disabled symbols are OK
            Some(_) => (),
        }

        // Add to custom keywords
        self.custom_keywords.insert(keyword.into(), precedence);

        Ok(self)
    }
}
