#![allow(clippy::transmute_ptr_to_ptr)]
#![allow(clippy::transmute_ptr_to_ref)]

use std::mem::transmute;
use onig_sys;
use super::{MetaCharType, RegexOptions, SyntaxBehavior, SyntaxOperator};

/// Meta Character State
///
/// Defines if a given meta character is enabled or not within a given
/// syntax. If the character is enabled it also contains the rust
/// `char` that it is set to.
#[derive(Copy, Clone)]
pub enum MetaChar {
    /// The meta character is set to the chosen `char`
    Character(char),
    /// The meta character is not enabled
    Ineffective,
}

/// Onig Syntax Wrapper
///
/// Each syntax dfines a flavour of regex syntax. This type allows
/// interaction with the built-in syntaxes through the static accessor
/// functions (`Syntax::emacs()`, `Syntax::default()` etc.) and the
/// creation of custom syntaxes.
///
/// For a demonstration of creating a custom syntax see
/// `examples/syntax.rs` in the main onig crate.
#[derive(Debug, Clone, Copy)]
pub struct Syntax {
    raw: onig_sys::OnigSyntaxType,
}

impl Syntax {
    /// Plain text syntax
    pub fn asis() -> &'static Syntax {
        unsafe { transmute(&onig_sys::OnigSyntaxASIS) }
    }

    /// POSIX Basic RE syntax
    pub fn posix_basic() -> &'static Syntax {
        unsafe { transmute(&onig_sys::OnigSyntaxPosixBasic) }
    }

    /// POSIX Extended RE syntax
    pub fn posix_extended() -> &'static Syntax {
        unsafe { transmute(&onig_sys::OnigSyntaxPosixExtended) }
    }

    /// Emacs syntax
    pub fn emacs() -> &'static Syntax {
        unsafe { transmute(&onig_sys::OnigSyntaxEmacs) }
    }

    /// Grep syntax
    pub fn grep() -> &'static Syntax {
        unsafe { transmute(&onig_sys::OnigSyntaxGrep) }
    }

    /// GNU regex syntax
    pub fn gnu_regex() -> &'static Syntax {
        unsafe { transmute(&onig_sys::OnigSyntaxGnuRegex) }
    }

    /// Java (Sun java.util.regex) syntax
    pub fn java() -> &'static Syntax {
        unsafe { transmute(&onig_sys::OnigSyntaxJava) }
    }

    /// Perl syntax
    pub fn perl() -> &'static Syntax {
        unsafe { transmute(&onig_sys::OnigSyntaxPerl) }
    }

    /// Perl + named group syntax
    pub fn perl_ng() -> &'static Syntax {
        unsafe { transmute(&onig_sys::OnigSyntaxPerl_NG) }
    }

    /// Ruby syntax
    pub fn ruby() -> &'static Syntax {
        unsafe { transmute(&onig_sys::OnigSyntaxRuby) }
    }

    /// Oniguruma Syntax
    pub fn oniguruma() -> &'static Syntax {
        unsafe { transmute(&onig_sys::OnigSyntaxOniguruma) }
    }

    /// Default syntax (Ruby syntax)
    pub fn default() -> &'static Syntax {
        unsafe { transmute(onig_sys::OnigDefaultSyntax) }
    }

    /// Retrieve the operators for this syntax
    pub fn operators(&self) -> SyntaxOperator {
        unsafe {
            let op = onig_sys::onig_get_syntax_op(&self.raw);
            let op2 = onig_sys::onig_get_syntax_op2(&self.raw);
            SyntaxOperator::from_bits_truncate(u64::from(op) + (u64::from(op2) << 32))
        }
    }

    /// Replace the operators for this syntax
    pub fn set_operators(&mut self, operators: SyntaxOperator) {
        let op = operators.bits() as onig_sys::OnigSyntaxOp;
        let op2 = (operators.bits() >> 32) as onig_sys::OnigSyntaxOp2;
        unsafe {
            onig_sys::onig_set_syntax_op(&mut self.raw, op);
            onig_sys::onig_set_syntax_op2(&mut self.raw, op2)
        }
    }

    /// Enable Operators for this Syntax
    ///
    /// Updates the operators for this syntax to enable the chosen
    /// ones.
    pub fn enable_operators(&mut self, operators: SyntaxOperator) {
        let operators = self.operators() | operators;
        self.set_operators(operators)
    }

    /// Disable Operators for this Syntax
    ///
    /// Updates the operators for this syntax to remove the specified
    /// operators.
    pub fn disable_operators(&mut self, operators: SyntaxOperator) {
        let operators = self.operators() & !operators;
        self.set_operators(operators)
    }

    /// Retrieves the syntax behaviours
    pub fn behavior(&self) -> SyntaxBehavior {
        SyntaxBehavior::from_bits_truncate(unsafe { onig_sys::onig_get_syntax_behavior(&self.raw) })
    }

    /// Overwrite the syntax behaviour for this syntax.
    pub fn set_behavior(&mut self, behavior: SyntaxBehavior) {
        let behavior = behavior.bits() as onig_sys::OnigSyntaxBehavior;
        unsafe {
            onig_sys::onig_set_syntax_behavior(&mut self.raw, behavior);
        }
    }

    /// Enable a given behaviour for this syntax
    pub fn enable_behavior(&mut self, behavior: SyntaxBehavior) {
        let behavior = self.behavior() | behavior;
        self.set_behavior(behavior)
    }

    /// Disable a given behaviour for this syntax
    pub fn disable_behavior(&mut self, behavior: SyntaxBehavior) {
        let behavior = self.behavior() & !behavior;
        self.set_behavior(behavior)
    }

    /// Retireve the syntax options for this syntax
    pub fn options(&self) -> RegexOptions {
        RegexOptions::from_bits_truncate(unsafe { onig_sys::onig_get_syntax_options(&self.raw) })
    }

    /// Replace the syntax options for this syntax
    pub fn set_options(&mut self, options: RegexOptions) {
        let options = options.bits() as onig_sys::OnigOptionType;
        unsafe {
            onig_sys::onig_set_syntax_options(&mut self.raw, options);
        }
    }

    /// Set a given meta character's state
    ///
    /// Arguments:
    ///  - `what`: The meta character to update
    ///  - `meta`: The value to set the meta character to
    pub fn set_meta_char(&mut self, what: MetaCharType, meta: MetaChar) {
        let what = what.bits();
        let code = match meta {
            MetaChar::Ineffective => onig_sys::ONIG_INEFFECTIVE_META_CHAR,
            MetaChar::Character(char) => char as u32,
        };
        unsafe {
            onig_sys::onig_set_meta_char(&mut self.raw, what, code);
        }
    }
}
