/*-
 * 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, net::IpAddr, num::NonZero, time::Duration};

use chrono::{Local, SecondsFormat};

use crate::
{
    ATSIGN, CBRACE, EQSIGN, IANA_PRIV_ENT_NUM, NILVALUE, OBRACE, QCHAR, SyslogMsgPriFac, 
    WSPACE, check_printable, error::SyRes, escape_chars, map_error, portable, throw_error, 
    truncate, truncate_n
};

use super::{SyslogFormatted, SyslogFormatter};

/// A trait for the unification of the all Structured Data IDs (IANA-registered SD-IDs).
pub trait SdIdIntrf: fmt::Display
{
    /// SD-ID identifier.
    const SD_ID_TEXT: &'static str;

    /// Formats the internals.
    fn format_msg(&self) -> String;
}


/// RFC:
/// > The SD-ID "timeQuality" MAY be used by the originator to describe its
/// > notion of system time.  This SD-ID SHOULD be written if the
/// > originator is not properly synchronized with a reliable external time
/// > source or if it does not know whether its time zone information is
/// > correct.  The main use of this structured data element is to provide
/// > some information on the level of trust it has in the TIMESTAMP
/// > described in Section 6.2.3.  All parameters are OPTIONAL.
/// 
/// RFC: 
/// > The "tzKnown" parameter indicates whether the originator knows its
/// > time zone.  If it does, the value "1" MUST be used.  If the time zone
/// > information is in doubt, the value "0" MUST be used.  If the
/// > originator knows its time zone but decides to emit time in UTC, the
/// > value "1" MUST be used (because the time zone is known).
/// tz_known
/// 
/// RFC:
/// > The "isSynced" parameter indicates whether the originator is
/// > synchronized to a reliable external time source, e.g., via NTP.  If
/// > the originator is time synchronized, the value "1" MUST be used.  If
/// > not, the value "0" MUST be used.
/// is_synced
/// 
/// RFC: 
/// > The "syncAccuracy" parameter indicates how accurate the originator
/// > thinks its time synchronization is.  It is an integer describing the
/// > maximum number of microseconds that its clock may be off between
/// > synchronization intervals.
/// > If the value "0" is used for "isSynced", this parameter MUST NOT be
/// > specified.  If the value "1" is used for "isSynced" but the
/// > "syncAccuracy" parameter is absent, a collector or relay can assume
/// > that the time information provided is accurate enough to be
/// > considered correct.  The "syncAccuracy" parameter MUST be written
/// > only if the originator actually has knowledge of the reliability of
/// > the external time source.
/// sync_acc
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SdIdTimeQuality(String);

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

impl SdIdIntrf for SdIdTimeQuality
{
    const SD_ID_TEXT: &'static str = "timeQuality";

    fn format_msg(&self) -> String 
    {
        return self.0.clone();
    }
}

impl SdIdTimeQuality
{
    pub 
    fn new(tz_known: bool, is_synced: bool, sync_acc: Option<u64>) -> Self
    {
        if let Some(sa) = sync_acc
        {
            let msg = 
                [
                    Self::SD_ID_TEXT, WSPACE,
                    "tzKnown", "=", QCHAR, tz_known.to_string().as_str(), QCHAR, WSPACE,
                    "isSynced", "=", QCHAR, is_synced.to_string().as_str(), QCHAR, WSPACE,
                    "syncAccuracy", "=", QCHAR, sa.to_string().as_str(), QCHAR,
                ]
                .concat();

            return Self(msg);
        }
        else
        {
            let msg = 
                [
                    Self::SD_ID_TEXT, WSPACE,
                    "tzKnown", "=", QCHAR, tz_known.to_string().as_str(), QCHAR, WSPACE,
                    "isSynced", "=", QCHAR, is_synced.to_string().as_str(), QCHAR,
                ]
                .concat();

            return Self(msg);
        }
    }
}

/// RFC:
/// > The SD-ID "origin" MAY be used to indicate the origin of a syslog
/// > message.  The following parameters can be used.  All parameters are
/// > OPTIONAL.
/// > 
/// > Specifying any of these parameters is primarily an aid to log
/// > analyzers and similar applications.
/// 
/// RFC:
/// > The "ip" parameter denotes an IP address that the originator knows it
/// > had at the time of originating the message.  It MUST contain the
/// > textual representation of an IP address as outlined in Section 6.2.4.
/// > 
/// > This parameter can be used to provide identifying information in
/// > addition to what is present in the HOSTNAME field.  It might be
/// > especially useful if the host's IP address is included in the message
/// > while the HOSTNAME field still contains the FQDN.  It is also useful
/// > for describing all IP addresses of a multihomed host.
/// > 
/// > If an originator has multiple IP addresses, it MAY either list one of
/// > its IP addresses in the "ip" parameter or it MAY include multiple
/// > "ip" parameters in a single "origin" structured data element.
/// ip
/// 
/// Some sh$t which grandpas from IANA invented and grabbed.
/// enterprise_id
/// 
/// RFC:
/// > The "software" parameter uniquely identifies the software that
/// > generated the message.  If it is used, "enterpriseId" SHOULD also be
/// > specified, so that a specific vendor's software can be identified.
/// > The "software" parameter is not the same as the APP-NAME header
/// > field.
/// > The "software" parameter is a string.  It MUST NOT be longer than 48
/// > characters.
/// software
/// 
/// RFC: 
/// > The "swVersion" parameter uniquely identifies the version of the
/// > software that generated the message.  If it is used, the "software"
/// > and "enterpriseId" parameters SHOULD be provided, too.
/// > 
/// > The "swVersion" parameter is a string.  It MUST NOT be longer than 32
/// > characters.
/// sw_version
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SdIdOrigin(String);

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

impl SdIdIntrf for SdIdOrigin
{
    const SD_ID_TEXT: &'static str = "origin";

    fn format_msg(&self) -> String
    {
        return self.0.clone();
    }
}

impl SdIdOrigin
{
    pub 
    fn new(ips: Vec<IpAddr>, enterprise_id: Option<&str>, software: Option<&str>, sw_version: Option<&str>) -> Self
    {
        let mut items: Vec<String> = 
            Vec::with_capacity(ips.len() + 1 + enterprise_id.map_or(0, |_| 1) + 
                software.map_or(0, |_| 1) + sw_version.map_or(0, |_| 1));

        items.push(Self::SD_ID_TEXT.to_string());

        ips.iter().for_each(|ip| items.push(["ip=", QCHAR, ip.to_string().as_str(), QCHAR].concat()));

        if let Some(ent_id) = enterprise_id
        {
            items.push(["enterpriseId=", QCHAR, ent_id, QCHAR].concat());

            if let Some(softw) = software
            {
                items.push(["software=", QCHAR, softw, QCHAR].concat());

                if let Some(sw_ver) = sw_version
                {
                    items.push(["swVersion=", QCHAR, sw_ver, QCHAR].concat());
                }
                else
                {
                    items.push(["swVersion=", QCHAR, "upspecified", QCHAR].concat());
                }
            }
            else
            {
                items.push(["software=", QCHAR, "upspecified", QCHAR].concat());
            }
        }

        let ret = Self(items.join(WSPACE));

        return ret;
    }
}


/// RFC:
/// > The SD-ID "meta" MAY be used to provide meta-information about the
/// > message.  The following parameters can be used.  All parameters are
/// > OPTIONAL.  If the "meta" SD-ID is used, at least one parameter SHOULD
/// > be specified.
/// 
/// RFC: 
/// > The "sequenceId" parameter tracks the sequence in which the
/// > originator submits messages to the syslog transport for sending.  It
/// > is an integer that MUST be set to 1 when the syslog function is
/// > started and MUST be increased with every message up to a maximum
/// > value of 2147483647.  If that value is reached, the next message MUST
/// > be sent with a sequenceId of 1.
/// sequenceId
/// 
/// RFC: 
/// > The "sysUpTime" parameter MAY be used to include the SNMP "sysUpTime"
/// > parameter in the message.  Its syntax and semantics are as defined in
/// > [RFC3418].
/// sysUpTime
/// 
/// RFC:
/// > The "language" parameter MAY be specified by the originator to convey
/// > information about the natural language used inside MSG.  If it is
/// > specified, it MUST contain a language identifier as defined in BCP 47
/// > [RFC4646].
/// language
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SdIdMeta(String);

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

impl SdIdIntrf for SdIdMeta
{
    const SD_ID_TEXT: &'static str = "meta";

    fn format_msg(&self) -> String
    {
        return self.0.clone();
    }
}

impl SdIdMeta
{
    /// Creates new instance. This includes the uptime request and sequence number.
    /// 
    /// This message can not be cached as it 
    pub 
    fn new(language: &str, msg: &FormatRfc5424) -> Self
    {
        let uptime = portable::get_uptime().unwrap_or(Duration::from_secs(0));

       
        
        let msg = 
            [
                Self::SD_ID_TEXT, WSPACE,
                "sequenceId", "=", QCHAR, msg.seq.to_string().as_str(), QCHAR, WSPACE,
                "sysUpTime", "=", QCHAR, uptime.as_secs().to_string().as_str(), QCHAR, WSPACE,
                "language", "=", QCHAR, language, QCHAR,
            ]
            .concat();

        return Self(msg);
    }

    #[cfg(test)]
    pub 
    fn new_test(language: &str, uptime: u64, msg: &FormatRfc5424) -> Self
    {
        let msg = 
            [
                Self::SD_ID_TEXT, WSPACE,
                "sequenceId", "=", QCHAR, msg.seq.to_string().as_str(), QCHAR, WSPACE,
                "sysUpTime", "=", QCHAR, uptime.to_string().as_str(), QCHAR, WSPACE,
                "language", "=", QCHAR, language, QCHAR,
            ]
            .concat();

        return Self(msg);
    }
}

/// A RFC5424 SD-ELEMENT of the STRUCTURED-DATA.
/// 
/// i.e [exampleSDID@32473 iut="3" eventSource="Application" eventID="1011"]
/// 
/// RFC:
/// > SD-IDs are case-sensitive and uniquely identify the type and purpose
/// > of the SD-ELEMENT.  The same SD-ID MUST NOT exist more than once in a
/// > message.
/// > 
/// > There are two formats for SD-ID names:
/// > 
/// > * - Names that do not contain an at-sign ("@", ABNF %d64) are reserved
/// > to be assigned by IETF
/// > 
/// > * - Anyone can define additional SD-IDs using names in the format
/// > name@<private enterprise number>, e.g., "ourSDID@32473".  The
/// > format of the part preceding the at-sign is not specified;
/// sd_id
/// 
/// RFC:
/// > Each SD-PARAM consists of a name, referred to as PARAM-NAME, and a
/// > value, referred to as PARAM-VALUE. PARAM-NAME is case-sensitive.
/// > To support international characters, the PARAM-VALUE field MUST be
/// > encoded using UTF-8.
/// vals
#[derive(Debug, Clone)]
pub struct FormatRfc5424Sd
{
    // SD-ID offset 0, rest - data.
    vals: Vec<Cow<'static, str>>
}

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

impl FormatRfc5424Sd
{
    /// Creates the second type of the SD-ID in format of
    /// name@<private enterprise number>, e.g., "ourSDID@32473". 
    /// 
    /// RFC5424:
    /// ```text
    /// The format of the part preceding the at-sign is not specified;
    /// however, these names 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.  The part following
    /// the at-sign MUST be a private enterprise number as specified in
    /// Section 7.2.2.  Please note that throughout this document the
    /// value of 32473 is used for all private enterprise numbers.  This
    /// value has been reserved by IANA to be used as an example number in
    /// documentation.  Implementors will need to use their own private
    /// enterprise number for the enterpriseId parameter, and when
    /// creating locally extensible SD-ID names.
    /// ```
    /// 
    /// # Arguments
    /// 
    /// * `sd_id` - a sd_id which is stored with static lifetime.
    /// 
    /// * `vals_cnt` - amount of the values to be added.
    /// 
    /// # Returns
    /// 
    /// A [Result] in form of [SyRes] is returned.
    pub 
    fn new(sd_id: &'static str, vals_cnt: usize) -> SyRes<Self>
    {
        if vals_cnt == 0
        {
            throw_error!("params should present.");
        }

        if sd_id.contains(ATSIGN) == false
        {
            throw_error!("SD-ID should contain @, otherwise see pre-defined ID's.");
        }

        let (name, ent_num) = 
            sd_id
                .split_once(ATSIGN)
                .ok_or_else(||
                    map_error!("incorrect SD-ID format")
                )?;

        check_printable(name)?;

        let ent_num: u64 = 
            ent_num
                .parse()
                .map_err(|e|
                    map_error!("incorrect SD-ID entrprise number format, {}", e)
                )?;

        if ent_num == IANA_PRIV_ENT_NUM
        {
            throw_error!("SD-ID entrprise number can not be '{}' because it is private", ent_num);
        }

        let mut vals: Vec<Cow<'static, str>> = Vec::with_capacity(vals_cnt*3 + 2);
        vals.push(Cow::Borrowed(sd_id));

        return Ok(Self{ vals: vals });
    }

    /// Inserts the SD-PARAM into list. 
    /// An SD-PARAM MAY be repeated multiple times inside an SD-ELEMENT.
    pub 
    fn push(&mut self, key: Cow<'static, str>, val: Cow<'static, str>) -> SyRes<()>
    {
        check_printable(&key)?;

        let escp_val = escape_chars(val);
        self.vals.push( Cow::Owned([WSPACE, key.as_ref(), EQSIGN, QCHAR, escp_val.as_ref(), QCHAR].concat()));

        return Ok(());
    }

    fn format_sd(&self) -> String
    {        
        return self.vals.concat();
    }

    #[inline]
    fn get_sd_id(&self) -> &str
    {
        return self.vals[0].as_ref();
    }
}


/// The "sequenceId" parameter tracks the sequence in which the
/// originator submits messages to the syslog transport for sending.
#[cfg(target_has_atomic = "64")]
static SEQUENCE_ID: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);

/// The "sequenceId" parameter tracks the sequence in which the
/// originator submits messages to the syslog transport for sending.
#[cfg(not(target_has_atomic = "64"))]
static SEQUENCE_ID: std::sync::Mutex<u32> = std::sync::Mutex::new(0);

/// A RFC5424 formatter which formats the syslog message according to the
/// RFC5424 requirments so it could be received by the syslog server.
/// 
/// # Usage
/// 
/// ```ignore
/// let mut m = FormatRfc5424::from("test123");
///
/// m.add_msg_id_str("ATX41").unwrap();
///
/// let v = SdIdMeta::new_test("jp", 12345, &m);
///
/// m.add_internal(&v).unwrap();
/// ```
#[derive(Debug, Clone)]
pub struct FormatRfc5424
{
    /// A sequence number of the message
    seq: NonZero<u32>,

    /// A payload message.
    msg: Cow<'static, str>,

    /// RFC: The MSGID SHOULD identify the type of message.  For example, a
    /// firewall might use the MSGID "TCPIN" for incoming TCP traffic and the
    /// MSGID "TCPOUT" for outgoing TCP traffic.  Messages with the same
    /// MSGID should reflect events of the same semantics.  The MSGID itself
    /// is a string without further semantics.  It is intended for filtering
    //// messages on a relay or collector.
    msg_id: Cow<'static, str>,

    /// All STRUCTURED-DATA added to the current message.
    st_data: Vec<(Cow<'static, str>, String)>,
}

impl FormatRfc5424
{
    /// Creates a new Rfc5424 template with provided `seq_num`. The sequence is not
    /// verified!
    /// 
    /// # Arguments 
    /// 
    /// * `value` - [String] a log message.
    /// 
    /// * `seq_num` - [u32] a sequence number.
    /// 
    /// # Returns
    /// 
    /// A template is retuened.
    pub 
    fn new(value: String, seq_num: NonZero<u32>) -> FormatRfc5424 
    {
        return 
            Self
            {
                seq: seq_num,
                msg: Cow::Owned(value),
                msg_id: Cow::Borrowed(NILVALUE),
                st_data: Vec::new(),
            };
    }

    /// Generates a [NonZero] sequence number of the message. When reaching [u32::MAX] 
    /// starts from 1. Using atomics.
    #[cfg(target_has_atomic = "64")]
    fn generate_sequence_num() -> NonZero<u32>
    {
        use std::sync::atomic::{Ordering};

        let mut last = SEQUENCE_ID.load(Ordering::Relaxed);
        
        loop 
        {
            let seq_id = 
                match last.checked_add(1) 
                {
                    Some(id) => id,
                    None =>
                    {
                        1
                    }
                };

            match SEQUENCE_ID.compare_exchange_weak(last, seq_id, Ordering::Relaxed, Ordering::Relaxed) 
            {
                Ok(_) => 
                    return NonZero::new(seq_id).unwrap(),
                Err(id) => 
                    last = id,
            }
        }
    }

    /// Generates a [NonZero] sequence number of the message. When reaching [u32::MAX] 
    /// starts from 1. Using mutex.
    #[cfg(not(target_has_atomic = "64"))]
    fn generate_sequence_num() -> NonZero<u32>
    {
        let mut seq_id_cnt = 
            SEQUENCE_ID.lock().unwrap_or_else(std::sync::PoisonError::into_inner);

        let seq_id = 
            match seq_id_cnt.checked_add(1) 
            {
                Some(id) => id,
                None =>
                {
                    1
                }
            };

        *seq_id_cnt = seq_id;
        drop(seq_id_cnt);

        return NonZero::new(seq_id).unwrap();
    }

    #[cfg(test)]
    #[cfg(target_has_atomic = "64")]
    fn reset_seq_counter()
    {
        SEQUENCE_ID.store(0, std::sync::atomic::Ordering::Relaxed);
    }

    #[cfg(test)]
    #[cfg(not(target_has_atomic = "64"))]
    fn reset_seq_counter()
    {
        *SEQUENCE_ID.lock().unwrap() = 0;
    }

    /// Adds the MSGID which identifies the type of the message. Make sure that
    /// it does not contain any non ASCII characters or non-printable chars. Normally
    /// it should consist from letters and digits.
    #[inline]
    pub 
    fn add_msg_id_str(&mut self, msg_id: &'static str) -> SyRes<()>
    {
        check_printable(msg_id)?;

        self.msg_id = Cow::Borrowed(msg_id);

        return Ok(());
    }

    /// Adds the MSGID which identifies the type of the message. Make sure that
    /// it does not contain any non ASCII characters or non-printable chars. Normally
    /// it should consist from letters and digits.
    #[inline]
    pub 
    fn add_msg_id(&mut self, msg_id: impl Into<String>) -> SyRes<()>
    {
        let msgid = msg_id.into();

        check_printable(&msgid)?;

        self.msg_id = Cow::Owned(msgid);

        return Ok(());
    }

    /// Inserts the instance of the [FormatRfc5424Sd] to the list. This is a user defined
    /// formatted message.
    #[inline]
    pub
    fn add_st_data(&mut self, st_data: FormatRfc5424Sd) -> SyRes<()>
    {
        if self.st_data.iter().any(|(v, _)| v.as_ref() == st_data.get_sd_id()) == true
        {
            throw_error!("SD-ID already exists on the list: '{}'", st_data);
        }

        let items = st_data.format_sd();

        self.st_data.push((Cow::Owned(st_data.get_sd_id().to_string()), items));

        return Ok(());
    }

    /// Inserts the reserved type of the formatted message. The reserved types can be found 
    /// above. [] [] []
    #[inline]
    pub 
    fn add_reserved<I: SdIdIntrf>(&mut self, item: &I) -> SyRes<()>
    {
        if self.st_data.iter().any(|(v, _)| v.as_ref() == I::SD_ID_TEXT) == true
        {
            throw_error!("SD-ID already exists on the list: '{}'", I::SD_ID_TEXT);
        }

        let items = item.format_msg();

        self.st_data.push((Cow::Borrowed(I::SD_ID_TEXT), items));

        return Ok(());
    }

    #[inline]
    fn format(&self) -> String
    {
        let ret = 
            self
                .st_data
                .iter()
                .map(|(_, d)| [OBRACE, d.as_str(), CBRACE].concat())
                .collect::<Vec<String>>()
                .concat();

        return ret;
    }


}

unsafe impl Send for FormatRfc5424 {}

impl From<String> for FormatRfc5424
{
    fn from(value: String) -> FormatRfc5424 
    {
        return 
            Self
            {
                seq: Self::generate_sequence_num(),
                msg: Cow::Owned(value),
                msg_id: Cow::Borrowed(NILVALUE),
                st_data: Vec::new(),
            };
    }
}

impl From<&'static str> for FormatRfc5424
{
    fn from(value: &'static str) -> FormatRfc5424
    {
        return 
            Self
            {
                seq: Self::generate_sequence_num(),
                msg: Cow::Borrowed(value),
                msg_id: Cow::Borrowed(NILVALUE),
                st_data: Vec::new(),
            };
    }
}


impl SyslogFormatter for FormatRfc5424
{
    fn vsyslog1_format(&self, max_msg_size: usize, prifac: SyslogMsgPriFac, progname: &str, pid: Option<&str>) -> SyslogFormatted
    {
        // get timedate
        let timedate = 
            Local::now().to_rfc3339_opts(SecondsFormat::Secs, false);

		let hostname: String =
            portable::portable_gethostname().map_or(NILVALUE.into(), |f| f);

        // message based on RFC 5424
        let msg_pri = 
            [
                "<", prifac.get_val().to_string().as_str(), ">1"
            ]
            .concat();

        let msg_st_data = 
            if self.st_data.is_empty() == false
            {
                Some(self.format())
            }
            else
            {
                None
                //Cow::Borrowed(NILVALUE)
            };

		// message based on RFC 5424
        let msg_pkt = 
            [
                // PRI 
                //Cow::Owned(msg_pri),
                // timedate
                WSPACE, timedate.as_str(), 
                // hostname
                WSPACE, hostname.as_str(),
                // appname
                WSPACE, progname, 
                // PID
                WSPACE, pid.map_or(NILVALUE, |f| f),
                // message ID
                WSPACE, &self.msg_id,
                // structured data
                WSPACE, msg_st_data.as_ref().map_or(NILVALUE, |f| f.as_str()), 
                // msg
                WSPACE, 
                /*b"\xEF\xBB\xBF",*/
            ]
            .concat();

        // calculate the left message space
        let msg_space_left = max_msg_size - msg_pkt.as_bytes().len();

        let msg_payload = truncate_n(&self.msg, msg_space_left);

        let msg_payload_final = 
            if msg_payload.ends_with("\n") == true
            {
                truncate(msg_payload)
            }
            else
            {
                msg_payload
            };

        // set payload
		let msg_payload= [msg_pkt.as_str(), msg_payload_final].concat();

        return 
            SyslogFormatted
            { 
                msg_header: Some(msg_pri), 
                msg_payload: msg_payload, 
                full_msg: None
            };
    }
}

#[cfg(test)]
mod tests
{
    use std::{borrow::Cow, time::Instant};

    use crate::
    {
        LogFacility, Priority, SyslogMsgPriFac, 
        formatters::{FormatRfc5424, SyslogFormatter, syslog_5424::{FormatRfc5424Sd, SdIdMeta}}
    };

    #[test]
    fn test_params1()
    {
        FormatRfc5424::reset_seq_counter();

        let mut m = FormatRfc5424::from("test123");

        m.add_msg_id_str("ATX41").unwrap();

        let mut sd= FormatRfc5424Sd::new("testItem@3424", 2).unwrap();

        sd.push(Cow::Borrowed("testKey"), Cow::Borrowed("testVal")).unwrap();
        sd.push(Cow::Borrowed("testKey"), Cow::Borrowed("testVal")).unwrap();

        m.add_st_data(sd).unwrap();

        let fmtf = m.format();

        assert_eq!(fmtf.as_str(), "[testItem@3424 testKey=\"testVal\" testKey=\"testVal\"]");

        
        let mut res = 
            m.vsyslog1_format(
                1024, 
                SyslogMsgPriFac::set_facility(Priority::LOG_CRIT, LogFacility::LOG_DAEMON),  
                "test", 
                Some("1234")
            );

        let cres = res.get_full_msg();

        println!("{}", cres);

        return;
    }

    #[test]
    fn test_params2()
    {
        FormatRfc5424::reset_seq_counter();

        let mut m = FormatRfc5424::from("test123");

        m.add_msg_id_str("ATX41").unwrap();

        let mut sd= FormatRfc5424Sd::new("exampleSDID@32474", 3).unwrap();

        sd.push(Cow::Borrowed("iut"), Cow::Borrowed("3")).unwrap();
        sd.push(Cow::Borrowed("eventSource"), Cow::Borrowed("Application")).unwrap();
        sd.push(Cow::Borrowed("eventID"), Cow::Borrowed("1011")).unwrap();

        m.add_st_data(sd).unwrap();

        let mut sd= FormatRfc5424Sd::new("examplePriority@32474", 1).unwrap();

        sd.push(Cow::Borrowed("class"), Cow::Borrowed("high")).unwrap();
        m.add_st_data(sd).unwrap();

        let s = Instant::now();

        let fmtf = m.format();

        let e = s.elapsed();

        println!("took: {:?}", e);

        println!("{}", fmtf);

        assert_eq!(fmtf.as_str(), "[exampleSDID@32474 iut=\"3\" eventSource=\"Application\" eventID=\"1011\"][examplePriority@32474 class=\"high\"]");


        let mut res = 
            m.vsyslog1_format(
                1024, 
                SyslogMsgPriFac::set_facility(Priority::LOG_CRIT, LogFacility::LOG_DAEMON), 
                "test", 
                Some("1234")
            );

        let cres = res.get_full_msg();

        println!("{}", cres);

        return;
    }

    #[test]
    fn test_params3()
    {
        fn test_p3(assert_tx: &str)
        {
            let mut m = FormatRfc5424::from("test123");

            m.add_msg_id_str("ATX41").unwrap();

            let v = SdIdMeta::new_test("jp", 12345, &m);

            m.add_reserved(&v).unwrap();

            let fmtf = m.format();

            assert_eq!(fmtf.as_str(), assert_tx);

            let mut res = 
                m.vsyslog1_format( 
                    1024, 
                    SyslogMsgPriFac::set_facility(Priority::LOG_CRIT, LogFacility::LOG_DAEMON), 
                    "test", 
                    Some("1234")
                );

            let cres = res.get_full_msg();

            println!("{}", cres);
        }

        FormatRfc5424::reset_seq_counter();

        test_p3("[meta sequenceId=\"1\" sysUpTime=\"12345\" language=\"jp\"]");
        test_p3("[meta sequenceId=\"2\" sysUpTime=\"12345\" language=\"jp\"]");
        

        return;
    }

    #[test]
    fn test_fail_params1()
    {
        let mut m = FormatRfc5424::from("test123");

        m.add_msg_id_str("ATX41").unwrap();

        let sd= FormatRfc5424Sd::new("exampleSDID", 1);

        assert_eq!(sd.is_err(), true);

        let sd= FormatRfc5424Sd::new("exampleSDID@", 1);

        assert_eq!(sd.is_err(), true);

        let sd= FormatRfc5424Sd::new("@23", 1);

        assert_eq!(sd.is_err(), true);

        let sd= FormatRfc5424Sd::new("dfe@23", 0);

        assert_eq!(sd.is_err(), true);
    }
}

