
extern crate syslog_rs; 

use std::fmt;
use std::io::ErrorKind;
use std::net::Shutdown;
use std::time::Duration;
use std::{borrow::Cow, io::IoSlice};

use syslog_rs::a_sync::AsyncMutexGuard;
use syslog_rs::formatters::DefaultSyslogFormatter;
use syslog_rs::nix::errno::Errno;
use syslog_rs::nix::libc;

use syslog_rs::error::SyslogError;
use syslog_rs::{common, throw_error_errno, throw_error_os, AsyncSyslog, AsyncSyslogTap, AsyncTap, LogFacility, LogStat, Priority, SyslogDestMsg, SyslogLocal, TapType, PATH_CONSOLE, PATH_LOG, PATH_LOG_PRIV, PATH_OLDLOG, PATH_OSX};
use syslog_rs::{AsyncSyslogInternal, AsyncSyslogInternalIO, AsyncMutex, error::SyRes, formatters::SyslogFormatter, map_error_os, AsyncSyslogDestination};
use tokio::fs::File;
use tokio::io::AsyncWriteExt;
use tokio::net::UnixDatagram;
use tokio::sync::MutexGuard;
use tokio::time::sleep;
use tokio::{io::AsyncWrite, sync::Mutex};

/// Implemeting the custom IO and thread control things which is needed by the crate.
#[derive(Debug)]
pub struct OurAsyncSyslogIO;

impl OurAsyncSyslogIO
{
    /// Sends to the FD i.e file of stderr, stdout or any which 
    /// implements [Write] `write_vectored` in async manner
    ///
    /// # 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) async 
    fn async_send_to_fd<W>(mut file_fd: W, msg: &str, newline: &str) -> SyRes<usize>
    where W: AsyncWrite + Unpin
    {
        return 
            file_fd
                .write_vectored(&[IoSlice::new(msg.as_bytes()), IoSlice::new(newline.as_bytes())])
                .await
                .map_err(|e|
                    map_error_os!(e, "async_send_to_fd() writev() failed")
                );
    } 
}

impl AsyncSyslogInternalIO for OurAsyncSyslogIO
{
    #[inline]
    async 
    fn send_to_stderr(logstat: LogStat, msg: &str)
    {
        if logstat.intersects(LogStat::LOG_PERROR) == true
        {
            let stderr_lock = tokio::io::stderr();

            let newline = "\n";
            let _ = Self::async_send_to_fd(stderr_lock, msg, newline).await;
        }
    }

    #[inline]
    async 
    fn send_to_syscons(logstat: LogStat, msg_payload: &str)
    {
        if logstat.intersects(LogStat::LOG_CONS)
        {
            let syscons = 
                File
                    ::options()
                        .create(false)
                        .read(false)
                        .write(true)
                        .custom_flags(libc::O_NONBLOCK | libc::O_CLOEXEC)
                        .open(*PATH_CONSOLE)
                        .await;

            if let Ok(file) = syscons
            {
                let newline = "\n";
                let _ = Self::async_send_to_fd(file, msg_payload, newline);
            }
        }
    }
    
    async 
    fn sleep_micro(us: u64) 
    {
        sleep(Duration::from_micros(us)).await;
    }

}

/// Implementing the mutex realization for out executor.
#[derive(Debug)]
struct MutexOveride<T: Sized>(Mutex<T>);

/// Also the mutex guard.
#[derive(Debug)]
struct MutexGuardNative<'a, T>(MutexGuard<'a, T>);

impl<'a, T>  MutexGuardNative<'a, T>
{ 
    fn new(g:  MutexGuard<'a, T>) -> Self
    {
        return Self(g);
    }
}

impl<F: SyslogFormatter + Send, D: AsyncSyslogDestination> AsyncMutex<F, D, AsyncSyslogInternal<F, D, OurAsyncSyslogIO>> 
for MutexOveride<AsyncSyslogInternal<F, D, OurAsyncSyslogIO>>
{
    type MutxGuard<'mux> = MutexGuardNative<'mux, AsyncSyslogInternal<F, D, OurAsyncSyslogIO>>;

    fn a_new(v: AsyncSyslogInternal<F, D, OurAsyncSyslogIO>) -> Self 
    {
        return Self(Mutex::new(v));
    }
    
    async 
    fn a_lock<'mux>(&'mux self) -> Self::MutxGuard<'mux>
    {
        return MutexGuardNative::new(self.0.lock().await);
    }
}

impl<'mux, F: SyslogFormatter + Send, D: AsyncSyslogDestination> AsyncMutexGuard<'mux, F, D, AsyncSyslogInternal<F, D, OurAsyncSyslogIO>>
for MutexGuardNative<'mux, AsyncSyslogInternal<F, D, OurAsyncSyslogIO>>
{
    fn guard(&self) -> &AsyncSyslogInternal<F, D, OurAsyncSyslogIO>
    {
        return &self.0;
    }

    fn guard_mut(&mut self) -> &mut AsyncSyslogInternal<F, D, OurAsyncSyslogIO>
    {
        return &mut self.0;
    }
}

/// Implementing the sylog provider (in this example a SyslogLocal will be wrapped into our struct
/// because it is not possible to implement foreign traits for foreign structs.
#[derive(Debug, Clone)]
struct NativeSyslogLocal(SyslogLocal);

impl NativeSyslogLocal
{
    fn new(s: SyslogLocal) -> Self
    {
        return Self(s);
    }
}

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

/// A async destination where the type of the tap i.e socket is declared.
impl AsyncSyslogDestination for NativeSyslogLocal
{
    type SocketTap = AsyncTap::<tokio::net::UnixDatagram, Self>;

}

/// A maximum message size.
impl SyslogDestMsg for NativeSyslogLocal
{
    fn get_max_msg_len() -> usize 
    {
        if *common::RFC5424_MAX_DGRAM >= common::MAXLINE
        {
            return common::MAXLINE;
        }
        else
        {
            return *common::RFC5424_MAX_DGRAM;
        };
    }
}

/// Implementing a [AsyncSyslogTap] for the previously created [NativeSyslogLocal] for the
/// [AsyncTap] - socket.
impl AsyncSyslogTap<NativeSyslogLocal> for AsyncTap<UnixDatagram, NativeSyslogLocal>
{
    fn new(req_tap: NativeSyslogLocal) -> SyRes<Self>
    {
        return Self::new(req_tap);
    }


    async 
    fn connectlog(&mut self) -> SyRes<()> 
    {
        let sock = 
            UnixDatagram
                ::unbound()
                    .map_err(|e|
                        map_error_os!(e, "unbounded unix datagram initialization failure: {}", e)
                    )?;

        let tap_type = 
            if self.get_tap_data().0.get_use_alternative() == false && self.get_tap_data().0.get_custom_remote_path().is_some() == true 
            {
                if let Err(e) = sock.connect(self.get_tap_data().0.get_custom_remote_path().as_ref().unwrap())
                {
                    throw_error_os!(e, "failed to open connection to syslog server at '{}'", 
                        self.get_tap_data().0.get_custom_remote_path().as_ref().unwrap().display());
                }
                else 
                {
                    TapType::CustomLog
                }
            }
            else if self.get_tap_data().0.get_custom_remote_path().is_some() == true &&
                sock.connect(self.get_tap_data().0.get_custom_remote_path().as_ref().unwrap()).is_ok() == true
            {
                TapType::CustomLog
            }
            else if let Ok(_) = sock.connect(PATH_LOG_PRIV)
            {
                TapType::Priv
            }
            else if let Ok(_) = sock.connect(PATH_LOG)
            {
                TapType::UnPriv
            }
            else if let Ok(_) = sock.connect(PATH_OLDLOG)
            {
                TapType::OldLog
            }
            else if let Ok(_) = sock.connect(PATH_OSX)
            {
                TapType::Priv
            }
            else
            {
                // failed to open socket
                throw_error_errno!(Errno::last(), 
                    "failed to open connection to syslog server, no path to server");
            };

        self.set_sock(sock);
        self.set_cur_tap_type(tap_type);

        return Ok(());
    }

    async 
    fn send(&mut self, msg: &[u8]) -> std::io::Result<usize> 
    {
        let sock = 
            self
                .get_sock_mut()
                .ok_or_else(||
                    std::io::Error::new(ErrorKind::NotConnected, "no connection")
                )?;

        return sock.send(msg).await;
    }

    async 
    fn disconnectlog(&mut self) -> std::io::Result<()>
    {
        match self.take_sock()
        {
            Some(s) => 
            {
                self.set_cur_tap_type(TapType::None);

                s.shutdown(Shutdown::Both)
            },
            None =>
            {
                self.set_cur_tap_type(TapType::None);

                Ok(())
            }
        }
    }

    fn is_connected(&self) -> bool
    {
        return self.get_sock().is_some();
    }

    fn get_type(&self) -> TapType 
    {
        return self.get_tap_type();
    }

    fn get_max_msg_size() -> usize
    {
        return NativeSyslogLocal::get_max_msg_len();
    }

    fn update_tap_data(&mut self, tap_data: NativeSyslogLocal)
    {
        self.update_tap_data(tap_data); 
    }
}

#[tokio::main]
async
fn main() 
{
    // provide everything which was created to the AsyncSyslog.
    let syslog =
        AsyncSyslog
            ::<NativeSyslogLocal, DefaultSyslogFormatter, OurAsyncSyslogIO, MutexOveride<_>>
            ::openlog_with(
                Some("example_cust"), 
                LogStat::LOG_CONS | LogStat::LOG_NDELAY | LogStat::LOG_PID, 
                LogFacility::LOG_DAEMON,
                NativeSyslogLocal::new(SyslogLocal::new())
            )
            .await
            .unwrap();

    syslog.vsyslog(Priority::LOG_DEBUG, "test custom async exec in example").await;

    println!("Hello, world!");
}
