/*-
 * syslog-rs - a syslog client translated from libc to rust
 * 
 * Copyright 2025 Aleksandr Morozov
 * 
 * The syslog-rs crate can be redistributed and/or modified
 * under the terms of either of the following licenses:
 *
 *   1. the Mozilla Public License Version 2.0 (the “MPL”) OR
 *
 *   2. The MIT License (MIT)
 *                     
 *   3. EUROPEAN UNION PUBLIC LICENCE v. 1.2 EUPL © the European Union 2007, 2016
 */




use std::{borrow::Cow, fmt, ops::{BitAnd, Shl}, path::Path, sync::LazyLock};

use crate::{error::{SyRes, SyslogError}, map_error_code, portable, throw_error};

#[cfg(target_family = "windows")]
pub use self::common_eventlog_items::*;

#[cfg(target_family = "unix")]
pub use self::common_syslog_items::*;

#[cfg(target_family = "windows")]
pub mod common_eventlog_items
{
    use std::fmt;

    use windows::Win32::System::EventLog::{EVENTLOG_ERROR_TYPE, EVENTLOG_INFORMATION_TYPE, EVENTLOG_SUCCESS, EVENTLOG_WARNING_TYPE, REPORT_EVENT_TYPE};

    use crate::{error::SyRes, throw_error};

    #[allow(nonstandard_style)]
    #[repr(i32)]
    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
    pub enum Priority
    {
         /// system is unusable
        LOG_EMERG = 0,

        /// action must be taken immediately
        LOG_ALERT = 1,

        /// critical conditions
        LOG_CRIT =  2,

        /// error conditions
        LOG_ERR =  3,

        /// warning conditions
        LOG_WARNING = 4,

        /// normal, but significant, condition
        LOG_NOTICE = 5,
        
        /// informational message
        LOG_INFO = 6,

        /// debug-level message
        LOG_DEBUG = 7,
    }
  
    impl fmt::Display for Priority
    {
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result 
        {
            //let pri = self.bits & LogMask::LOG_PRIMASK;

            match self
            {
                Self::LOG_EMERG => 
                    write!(f, "[EMERG]"),
                Self::LOG_ALERT => 
                    write!(f, "[ALERT]"),
                Self::LOG_CRIT => 
                    write!(f, "[CRIT]"),
                Self::LOG_ERR => 
                    write!(f, "[ERR]"),
                Self::LOG_WARNING => 
                    write!(f, "[WARNING]"),
                Self::LOG_NOTICE => 
                    write!(f, "[NOTICE]"),
                Self::LOG_INFO => 
                    write!(f, "[INFO]"),
                Self::LOG_DEBUG => 
                    write!(f, "[DEBUG]"),
            }
        }
    }

    impl From<Priority> for REPORT_EVENT_TYPE 
    {
        fn from(value: Priority) -> Self 
        {
            match value
            {
                Priority::LOG_EMERG | Priority::LOG_ALERT | Priority::LOG_CRIT =>
                    return EVENTLOG_WARNING_TYPE,
                Priority::LOG_ERR => 
                    return EVENTLOG_ERROR_TYPE,
                Priority::LOG_WARNING | Priority::LOG_NOTICE => 
                    return EVENTLOG_INFORMATION_TYPE,
                Priority::LOG_INFO | Priority::LOG_DEBUG => 
                    return EVENTLOG_SUCCESS
            }
        }
    }

    impl Priority
    {
        /*
        /// This function validates the `pri` for the incorrects bits set.
        /// If bits are set incorrectly, resets the invalid bits with:
        /// *pri & (LogMask::LOG_PRIMASK | LogMask::LOG_FACMASK).
        ///
        /// # Arguments
        ///
        /// * `pri` - a priority bits
        ///
        /// # Returns
        /// 
        /// * A [SyRes]. Ok() when valid or Err with error message
        pub(crate) 
        fn check_invalid_bits(&mut self) -> SyRes<()>
        {
        
            if (self.bits() & !(LogMask::LOG_PRIMASK | LogMask::LOG_FACMASK )) != 0
            {
                let pri_old = self.clone();
                
                *self = Self::from_bits_retain(self.bits() & (LogMask::LOG_PRIMASK | LogMask::LOG_FACMASK).bits() );

                throw_error!("unknwon facility/priority: {:x}", pri_old);
            }

            return Ok(());
        }*/
    }

    

    bitflags! {
        /// Controls  the  operation  of openlog() and subsequent calls to syslog.
        #[derive(Clone, Copy, Debug, PartialEq, Eq)]
        pub struct LogStat: i32 
        {
            /// Log the process ID with each message. (!todo)
            const LOG_PID = 1;
            
            /// Write directly to the system console if there is an error 
            /// while sending to the system logger.
            const LOG_CONS = 2;

            /// The converse of LOG_NDELAY; opening of the connection is delayed 
            /// until syslog() is called. (This is the default behaviour,and need 
            /// not be specified.)
            const LOG_ODELAY = 0;

            /// Open the connection immediately
            const LOG_NDELAY = 0;

            /// Don't wait for child processes that may have been created 
            /// while logging the message
            const LOG_NOWAIT = 0;
            
            /// Also log the message to stderr
            const LOG_PERROR = 0x20;
        }
    }

    #[cfg(feature = "build_sync")]
    impl LogStat
    {
        #[inline]
        pub(crate)
        fn send_to_stderr(&self, msg: &str)
        {
            if self.intersects(LogStat::LOG_PERROR) == true
            {
                eprintln!("{}", msg);
            }
        }

        #[inline]
        pub(crate)
        fn send_to_syscons(&self, msg_payload: &str)
        {
            if self.intersects(LogStat::LOG_CONS)
            {
                eprintln!("{}", msg_payload);
            }
        }
    }

    bitflags! {
        /// The facility argument is used to specify what type of program 
        /// is logging the message.
        #[derive(Clone, Copy, Debug, PartialEq, Eq)]
        pub struct LogFacility: i32 
        {
            /// kernel messages (these can't be generated from user processes)
            const LOG_KERN = 0;

            /// (default) generic user-level messages
            const LOG_USER = 8;

            /// mail subsystem
            const LOG_MAIL = 16;

            /// system daemons without separate facility value
            const LOG_DAEMON = 24;
            
            /// security/authorization messages
            const LOG_AUTH = 32;

            /// messages generated internally by syslogd(8)
            const LOG_SYSLOG = 40;

            /// line printer subsystem
            const LOG_LPR = 48;

            /// USENET news subsystem
            const LOG_NEWS = 56;

            /// UUCP subsystem
            const LOG_UUCP = 64;

            /// reserved for local use
            const LOG_LOCAL0 = 128;

            /// reserved for local use
            const LOG_LOCAL1 = 136;

            /// reserved for local use
            const LOG_LOCAL2 = 144;
            
            /// reserved for local use
            const LOG_LOCAL3 = 152;

            /// reserved for local use
            const LOG_LOCAL4 = 160;

            /// reserved for local use
            const LOG_LOCAL5 = 168;

            /// reserved for local use
            const LOG_LOCAL6 = 176;
            
            /// reserved for local use
            const LOG_LOCAL7 = 184;
        }
    }

    impl LogFacility
    {
        pub 
        fn into_win_facility(self) -> u32
        {
            return self.bits() as u32 >> 3;
        }
    }

    #[cfg(test)]
    mod tests
    {
        use windows::Win32::System::EventLog::{EVENTLOG_ERROR_TYPE, EVENTLOG_INFORMATION_TYPE, EVENTLOG_SUCCESS, EVENTLOG_WARNING_TYPE, REPORT_EVENT_TYPE};

        use crate::Priority;

        #[test]
        fn test_conversion_prio_to_ret()
        {
            assert_eq!(REPORT_EVENT_TYPE::from(Priority::LOG_EMERG), EVENTLOG_WARNING_TYPE);
            assert_eq!(REPORT_EVENT_TYPE::from(Priority::LOG_ALERT), EVENTLOG_WARNING_TYPE);
            assert_eq!(REPORT_EVENT_TYPE::from(Priority::LOG_CRIT), EVENTLOG_WARNING_TYPE);
            assert_eq!(REPORT_EVENT_TYPE::from(Priority::LOG_ERR), EVENTLOG_ERROR_TYPE);
            assert_eq!(REPORT_EVENT_TYPE::from(Priority::LOG_WARNING), EVENTLOG_INFORMATION_TYPE);
            assert_eq!(REPORT_EVENT_TYPE::from(Priority::LOG_NOTICE), EVENTLOG_INFORMATION_TYPE);
            assert_eq!(REPORT_EVENT_TYPE::from(Priority::LOG_INFO), EVENTLOG_SUCCESS);
            assert_eq!(REPORT_EVENT_TYPE::from(Priority::LOG_DEBUG), EVENTLOG_SUCCESS);
        }
    }
}

#[cfg(target_family = "unix")]
pub mod common_syslog_items
{
    use nix::libc;

    use std::fmt;


    bitflags! {
        /// Controls  the  operation  of openlog() and subsequent calls to syslog.
        #[derive(Clone, Copy, Debug, PartialEq, Eq)]
        pub struct LogStat: libc::c_int 
        {
            /// Log the process ID with each message. (!todo)
            const LOG_PID = libc::LOG_PID;
            
            /// Write directly to the system console if there is an error 
            /// while sending to the system logger.
            const LOG_CONS = libc::LOG_CONS;

            /// The converse of LOG_NDELAY; opening of the connection is delayed 
            /// until syslog() is called. (This is the default behaviour,and need 
            /// not be specified.)
            const LOG_ODELAY = libc::LOG_ODELAY;

            /// Open the connection immediately
            const LOG_NDELAY = libc::LOG_NDELAY;

            /// Don't wait for child processes that may have been created 
            /// while logging the message
            const LOG_NOWAIT = libc::LOG_NOWAIT;
            
            /// Also log the message to stderr
            const LOG_PERROR = 0x20;
        }
    }

    #[cfg(feature = "build_sync")]
    impl LogStat
    {
        #[inline]
        pub(crate)
        fn send_to_stderr(&self, msg: &str)
        {
            if self.intersects(LogStat::LOG_PERROR) == true
            {
                let stderr_lock = std::io::stderr().lock();
                let newline = "\n";

                let _ = send_to_fd(stderr_lock, msg, &newline);
            }
        }

        #[inline]
        pub(crate)
        fn send_to_syscons(&self, msg_payload: &str)
        {
            use std::fs::File;
            use std::os::unix::fs::OpenOptionsExt;

            if self.intersects(LogStat::LOG_CONS)
            {
                use crate::PATH_CONSOLE;

                let syscons = 
                    File
                        ::options()
                            .create(false)
                            .read(false)
                            .write(true)
                            .custom_flags(libc::O_NONBLOCK | libc::O_CLOEXEC)
                            .open(*PATH_CONSOLE);

                if let Ok(file) = syscons
                {
                    let newline = "\n";
                    let _ = send_to_fd(file, msg_payload, newline);
                }
            }
        }
    }

    #[allow(nonstandard_style)]
    #[repr(i32)]
    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
    pub enum Priority
    {
         /// system is unusable
        LOG_EMERG = libc::LOG_EMERG,

        /// action must be taken immediately
        LOG_ALERT = libc::LOG_ALERT,

        /// critical conditions
        LOG_CRIT =  libc::LOG_CRIT,

        /// error conditions
        LOG_ERR =  libc::LOG_ERR,

        /// warning conditions
        LOG_WARNING = libc::LOG_WARNING,

        /// normal, but significant, condition
        LOG_NOTICE = libc::LOG_NOTICE,
        
        /// informational message
        LOG_INFO = libc::LOG_INFO,

        /// debug-level message
        LOG_DEBUG = libc::LOG_DEBUG,
    }
  
    impl fmt::Display for Priority
    {
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result 
        {
            //let pri = self.bits & LogMask::LOG_PRIMASK;

            match self
            {
                Self::LOG_EMERG => 
                    write!(f, "[EMERG]"),
                Self::LOG_ALERT => 
                    write!(f, "[ALERT]"),
                Self::LOG_CRIT => 
                    write!(f, "[CRIT]"),
                Self::LOG_ERR => 
                    write!(f, "[ERR]"),
                Self::LOG_WARNING => 
                    write!(f, "[WARNING]"),
                Self::LOG_NOTICE => 
                    write!(f, "[NOTICE]"),
                Self::LOG_INFO => 
                    write!(f, "[INFO]"),
                Self::LOG_DEBUG => 
                    write!(f, "[DEBUG]"),
            }
        }
    }


    impl Priority
    {
    
    }

    bitflags! {
        /// The facility argument is used to specify what type of program 
        /// is logging the message.
        #[derive(Clone, Copy, Debug, PartialEq, Eq)]
        pub struct LogFacility: libc::c_int 
        {
            /// kernel messages (these can't be generated from user processes)
            const LOG_KERN = libc::LOG_KERN;

            /// (default) generic user-level messages
            const LOG_USER = libc::LOG_USER;

            /// mail subsystem
            const LOG_MAIL = libc::LOG_MAIL;

            /// system daemons without separate facility value
            const LOG_DAEMON = libc::LOG_DAEMON;
            
            /// security/authorization messages
            const LOG_AUTH = libc::LOG_AUTH;

            /// messages generated internally by syslogd(8)
            const LOG_SYSLOG = libc::LOG_SYSLOG;

            /// line printer subsystem
            const LOG_LPR = libc::LOG_LPR;

            /// USENET news subsystem
            const LOG_NEWS = libc::LOG_NEWS;

            /// UUCP subsystem
            const LOG_UUCP = libc::LOG_UUCP;

            /// reserved for local use
            const LOG_LOCAL0 = libc::LOG_LOCAL0;

            /// reserved for local use
            const LOG_LOCAL1 = libc::LOG_LOCAL1;

            /// reserved for local use
            const LOG_LOCAL2 = libc::LOG_LOCAL2;
            
            /// reserved for local use
            const LOG_LOCAL3 = libc::LOG_LOCAL3;

            /// reserved for local use
            const LOG_LOCAL4 = libc::LOG_LOCAL4;

            /// reserved for local use
            const LOG_LOCAL5 = libc::LOG_LOCAL5;

            /// reserved for local use
            const LOG_LOCAL6 = libc::LOG_LOCAL6;
            
            /// reserved for local use
            const LOG_LOCAL7 = libc::LOG_LOCAL7;
        }
    }


    

    /// Unpriv socket
    pub const PATH_LOG: &'static str = "/var/run/log";

    /// Priviledged socket
    pub const PATH_LOG_PRIV: &'static str = "/var/run/logpriv";

    /// backward compatibility
    pub const PATH_OLDLOG: &'static str = "/dev/log";

    /// OSX compat
    pub const PATH_OSX: &'static str = "/var/run/syslog";

    /*
    pub static PATH_CONSOLE: LazyLock<CString> = LazyLock::new(|| 
        {
            CString::new("/dev/console").unwrap()
        }
    );
    */

    #[cfg(feature = "build_sync")]
    pub(crate) mod sync_portion
    {
        use std::io::Write;
        use std::io::IoSlice;
        use crate::error::SyRes;
        use crate::map_error_os;

        /// Sends to the FD i.e file of stderr, stdout or any which 
        /// implements [Write] `write_vectored`.
        ///
        /// # Arguments
        /// 
        /// * `file_fd` - mutable consume of the container FD.
        ///
        /// * `msg` - a reference on array of data
        ///
        /// * `newline` - a new line string ref i.e "\n" or "\r\n"
        pub(crate) 
        fn send_to_fd<W>(mut file_fd: W, msg: &str, newline: &str) -> SyRes<usize>
        where W: Write
        {
            return 
                file_fd
                    .write_vectored(
                        &[IoSlice::new(msg.as_bytes()), IoSlice::new(newline.as_bytes())]
                    )
                    .map_err(|e|
                        map_error_os!(e, "send_to_fd() writev() failed")
                    );
        }
    }

    #[cfg(feature = "build_sync")]
    pub(crate) use self::sync_portion::*;

    #[cfg(test)]
    mod tests
    {
        use super::*;

        #[cfg(feature = "build_sync")]
        #[test]
        fn test_error_message()
        {
            /*use std::sync::Arc;
            use std::thread;
            use std::time::Duration;
            use super::{LOG_MASK};*/

            let testmsg = "this is test message!";
            let newline = "\n";
            let stderr_lock = std::io::stderr().lock();
            let res = send_to_fd(stderr_lock, testmsg, &newline);

            println!("res: {:?}", res);

            assert_eq!(res.is_ok(), true, "err: {}", res.err().unwrap());
        
            return;
        }

        #[test]
        fn test_priority_shl()
        {
            assert_eq!((1 << 5), (1 << Priority::LOG_NOTICE));
        }
    }
}

/// Path to console.
pub static PATH_CONSOLE: LazyLock<&Path> = LazyLock::new(|| 
    {
        Path::new("/dev/console")
    }
);

/// A max dgram init.
pub static RFC5424_MAX_DGRAM: LazyLock<usize> = LazyLock::new(|| 
    {
        portable::get_local_dgram_maxdgram() as usize
    }
);


/// max hostname size
pub const MAXHOSTNAMELEN: usize = 256;

/// mask to extract facility part
pub const LOG_FACMASK: i32 = 0x03f8;

/// Maximum number of characters of syslog message.
/// According to RFC5424. However syslog-protocol also may state that 
/// the max message will be defined by the transport layer.
pub const MAXLINE: usize = 8192;

/// RFC3164 limit
pub const RFC3164_MAX_PAYLOAD_LEN: usize = 1024;

/// A maximum message which could be passed to Windows Event Log
pub const WINDOWS_EVENT_REPORT_MAX_PAYLOAD_LEN: usize = 31839;

#[cfg(all(feature = "udp_truncate_1024_bytes", feature = "udp_truncate_1440_bytes"))]
compile_error!("either 'udp_truncate_1024_bytes' or 'udp_truncate_1440_bytes' should be enabled");

// RFC5424 480 octets or limited by the (transport) MAX_DGRAM_LEN or other.
#[cfg(feature = "udp_truncate_1024_bytes")]
pub const RFC5424_UDP_MAX_PKT_LEN: usize  = 1024;

#[cfg(any(feature = "udp_truncate_1440_bytes", all(not(feature = "udp_truncate_1440_bytes"), not(feature = "udp_truncate_1024_bytes"))))]
pub const RFC5424_UDP_MAX_PKT_LEN: usize  = 2048;

#[cfg(feature = "tcp_truncate_1024_bytes")]
pub const RFC5424_TCP_MAX_PKT_LEN: usize  = 1024;

#[cfg(feature = "tcp_truncate_2048_bytes")]
pub const RFC5424_TCP_MAX_PKT_LEN: usize  = 2048;

#[cfg(feature = "tcp_truncate_4096_bytes")]
pub const RFC5424_TCP_MAX_PKT_LEN: usize  = 4096;

#[cfg(feature = "tcp_truncate_max_bytes")]
pub const RFC5424_TCP_MAX_PKT_LEN: usize  = MAXLINE;

/// A max byte lenth of APPNAME (NILVALUE / 1*48PRINTUSASCII)
pub const RFC_MAX_APP_NAME: usize = 48;

/// A private enterprise number.
pub const IANA_PRIV_ENT_NUM: u64 = 32473;

/// RFC5424 defined value.
pub const NILVALUE: &'static str = "-";

/// RFC5424 escape character.
pub const ESC_CHAR_REPL: &'static str = "#000";

/// RFC5424 defined value (bytes).
pub const NILVALUE_B: &'static [u8] = b"-";

/// White space
pub const WSPACE: &'static str = " ";

/// Opening brace ('[', ABNF )
pub const OBRACE: &'static str = "[";

/// Closing brace (']', ABNF %d93)
pub const CBRACE: &'static str = "]";

/// Closing brace RFC3...
pub const CBRACE_SEM: &'static str = "]:";

/// Quote-character ('"', ABNF %d34)
pub const QCHAR: &'static str = "\"";

/// At-sign ("@", ABNF %d64)
pub const ATSIGN: &'static str = "@";

/// Eq-sign ("=", ABNF %d61)
pub const EQSIGN: &'static str = "=";

/// A cursor return.
pub const NEXTLINE: &'static str = "\n";

bitflags! {
    /// Masks
    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
    pub(crate) struct LogMask: i32 
    {
        /// A mask for the [LogFacility]
        const LOG_FACMASK = 0x3f8;

        /// A mask for the [Priority]
        const LOG_PRIMASK = 7;
    }
}

/// A struct which contains syslog encoded Facility and Priority 
/// is the following order:
/// 
/// - log_facility mask 0x3f8
/// - priority mask 0x7
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SyslogMsgPriFac(i32);

impl fmt::Display for SyslogMsgPriFac
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result 
    {
        write!(f, "{}", self.0)
    }
}

impl TryFrom<i32> for SyslogMsgPriFac
{
    type Error = SyslogError;

    fn try_from(value: i32) -> Result<Self, Self::Error> 
    {
        if (value & !(LogMask::LOG_PRIMASK | LogMask::LOG_FACMASK )) != 0
        {
            throw_error!("unknwon facility/priority: {:x}", value);
        }

        return Ok(Self(value));
    }
}

impl TryFrom<&[u8]> for SyslogMsgPriFac
{
    type Error = SyslogError;

    fn try_from(value: &[u8]) -> Result<Self, Self::Error> 
    {
        let val = 
            str::from_utf8(value)
                .map_or(
                    None, 
                    |pri_str|
                    {
                        i32::from_str_radix(pri_str, 10)
                            .map_or(
                                None,
                                |val|
                                Some(val & LogMask::LOG_PRIMASK | val & LogMask::LOG_FACMASK.bits()) 
                            )
                    }
                )
                .ok_or(
                    map_error_code!(InternalError, "cannot convert to prio|facility")
                )?;

        if (val & !(LogMask::LOG_PRIMASK | LogMask::LOG_FACMASK )) != 0
        {
            throw_error!("unknwon facility/priority: {:x}", val);
        }

        return Ok(Self(val));
    }
}

impl SyslogMsgPriFac
{
    /// Initializes the instance by adding a priority and facility together
    /// to form a message header.
    pub(crate) 
    fn set_facility(p: Priority, f: LogFacility) -> Self
    {
        return Self( p as i32 | f.bits() );
    }

    /// Returns the raw value.
    pub 
    fn get_val(&self) -> i32
    {
        return self.0;
    }

    /// Reads from the inner a [Priority].
    pub 
    fn get_priority(&self) -> Priority
    {
        return Priority::from(self.0 & LogMask::LOG_PRIMASK);
    }

    /// Reads from inner a [LogFacility].
    pub 
    fn get_log_facility(&self) -> LogFacility
    {
        return LogFacility::from_bits_retain(self.0 & LogMask::LOG_FACMASK);
    }
}

impl From<i32> for Priority
{
    fn from(value: i32) -> Self 
    {
        if value == Priority::LOG_ALERT as i32
        {
            return Priority::LOG_ALERT;
        }
        else if value == Priority::LOG_CRIT as i32
        {
            return Priority::LOG_CRIT;
        }
        else if value == Priority::LOG_DEBUG as i32
        {
            return Priority::LOG_DEBUG;
        }
        else if value == Priority::LOG_EMERG as i32
        {
            return Priority::LOG_EMERG;
        }
        else if value == Priority::LOG_ERR as i32
        {
            return Priority::LOG_ERR;
        }
        else if value == Priority::LOG_INFO as i32
        {
            return Priority::LOG_INFO;
        }
        else if value == Priority::LOG_NOTICE as i32
        {
            return Priority::LOG_NOTICE;
        }
        else
        {
            return Priority::LOG_WARNING;
        }
    }
}

/// LOG_MASK is used to create the priority mask in setlogmask. 
/// For a single Priority mask
/// used with [Priority]
/// can be used with | & ! bit operations LOG_MASK()
///
/// # Examples
/// 
/// ```ignore
///     LOG_MASK!(Priority::LOG_ALERT) | LOG_MASK!(Priority::LOG_INFO)
/// ```
#[macro_export]
macro_rules! LOG_MASK 
{
    ($arg:expr) => (
        (1 << ($arg))
    )
}

/// LOG_MASK is used to create the priority mask in setlogmask
/// For a mask UPTO specified
/// used with [Priority]
///
/// # Examples
/// 
/// ```ignore
///     LOG_UPTO!(Priority::LOG_ALERT)
/// ```
#[macro_export]
macro_rules! LOG_UPTO 
{
    ($arg:expr) => (
        ((1 << (($arg) + 1)) - 1)
    )
}

impl Shl<Priority> for i32
{
    type Output = i32;

    fn shl(self, rhs: Priority) -> i32 
    {
        let lhs = self;
        return lhs << rhs as i32;
    }
}

impl BitAnd<Priority> for i32
{
    type Output = i32;

    #[inline]
    fn bitand(self, rhs: Priority) -> i32
    {
        return self & rhs as i32;
    }
}

impl BitAnd<LogMask> for Priority
{
    type Output = i32;

    #[inline]
    fn bitand(self, rhs: LogMask) -> i32
    {
        return self as i32 & rhs.bits();
    }
}

impl BitAnd<LogMask> for LogFacility 
{
    type Output = LogFacility;

    #[inline]
    fn bitand(self, rhs: LogMask) -> Self::Output
    {
        return Self::from_bits_retain(self.bits() & rhs.bits());
    }
}

impl BitAnd<LogMask> for i32 
{
    type Output = i32;

    #[inline]
    fn bitand(self, rhs: LogMask) -> i32
    {
        return self & rhs.bits();
    }
}

/// This function trancated 1 last UTF8 character from the string.
///
/// # Arguments
///
/// * `lt` - a string which is trucated
/// 
/// # Returns
/// 
/// * A reference to the ctruncated string
pub 
fn truncate(lt: &str) -> &str
{
    let ltt =
        match lt.char_indices().nth(lt.len()-1) 
        {
            None => lt,
            Some((idx, _)) => &lt[..idx],
        };
    return ltt;
}

/// Trancates the string up to closest to N byte equiv UTF8
///  if string exceeds size
/// 
/// For example:  
/// ボルテ 'e3 83 9c e3 83 ab e3 83 86' with N=3  
/// will give 'ボ'  
/// 
/// ボルテ 'e3 83 9c e3 83 ab e3 83 86' with N=4  
/// will give 'ボ' 
/// 
/// ボルテ 'e3 83 9c e3 83 ab e3 83 86' with N=1  
/// will give ''
/// 
/// # Arguments
///
/// * `lt` - a string to truncate
///
/// * `n` - a size (in bytes, not in chars)
/// 
/// # Returns 
///
/// * A reference to [str] with the time `'t` which corresponds to
/// the lifetile of the input argument `'t`.
pub 
fn truncate_n<'t>(lt: &'t str, n: usize) -> &'t str
{
    if lt.as_bytes().len() <= n
    {
        return lt;
    }

    let mut nn: usize = 0;
    let mut cc = lt.chars();
    let mut ln: usize;

    loop 
    {
        match cc.next()
        {
            Some(r) =>
            {
                ln = r.len_utf8();
                nn += ln;

                if nn == n
                {
                    return &lt[..nn];
                }
                else if nn > n
                {
                    return &lt[..nn-ln];
                }
            },
            None => 
                return lt,
        }
    }
}

/// Checks if string are:
/// ```text
/// NOT EMPTY
/// MUST be printable US-ASCII strings, and MUST
/// NOT contain an at-sign ('@', ABNF %d64), an equal-sign ('=', ABNF
/// %d61), a closing brace (']', ABNF %d93), a quote-character ('"',
/// ABNF %d34), whitespace, or control characters
/// ```
pub
fn check_printable(a: &str) -> SyRes<()>
{
    if a.is_empty() == true
    {
        throw_error!("empty SD value");
    }

    for p in a.chars()
    {
        if p.is_ascii() == false || p.is_ascii_graphic() == false || p == '@' || p == '=' || p == ']' || p == '\"'
        {
            throw_error!("incorrect char: '{:X}' in SD value", p as u64);
        }
    }

    return Ok(());
}


pub 
fn escape_chars(st: Cow<'static, str>) -> Cow<'static, str>
{
    let mut out = String::with_capacity(st.len());

    for c in st.chars()
    {
        if c.is_control() == true
        {
            out.push_str(ESC_CHAR_REPL);
        }
        else if c == '\"' || c == '\\' || c == ']'
        {
            out.push('\\');
            out.push(c);
        }
        else
        {
            out.push(c);
        }
    }

    if st.len() == out.len()
    {
        return st;
    }
    else
    {
        return Cow::Owned(out);
    }
}


#[cfg(test)]
mod tests
{
    use super::*;

    #[test]
    fn test_truncate()
    {
        let test = "cat\n";

        let trunc = truncate(test);

        assert_eq!("cat", trunc);
    }



    #[test]
    fn test_truncate_n()
    {
        assert_eq!(truncate_n("abcde", 3), "abc");
        assert_eq!(truncate_n("ボルテ", 4), "ボ");
        assert_eq!(truncate_n("ボルテ", 5), "ボ");
        assert_eq!(truncate_n("ボルテ", 6), "ボル");
        assert_eq!(truncate_n("abcde", 0), "");
        assert_eq!(truncate_n("abcde", 5), "abcde");
        assert_eq!(truncate_n("abcde", 6), "abcde");
        assert_eq!(truncate_n("ДАТА", 3), "Д");
        assert_eq!(truncate_n("ДАТА", 4), "ДА");
        assert_eq!(truncate_n("ДАТА", 1), "");
    }

}
