/*-
 * 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::{fmt, io::ErrorKind};

#[cfg(target_family = "unix")]
use nix::errno::Errno;

/// Error code
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SyslogErrCode
{
    /// Os Error
    OsError(ErrorKind),

    /// Os Error
    #[cfg(target_family = "unix")]
    OsErrorErrno(Errno),

    #[cfg(target_family = "windows")]
    OsErrorErrno(i32),

    /// A message which can be output to stderr
    InternalError,

    /// Only for syslog_sync_queue.rs and returned only when syslog thread is stopped
    /// and someone is trying to do some operation
    SyslogThreadNotAvailable,

    /// Only for syslog_sync_queue.rs and returned only when syslog thread is failed
    /// to send back data
    UnboundedChannelError,

    /// Mutex poisoned, can not contunue
    MutexPoisoned,

    /// The function is not usable.
    Unusable,

    /// Channel errors during transmission.
    SendError,

    /// Functionality os not available.
    NotAvail,

    /// General error which identifies problems writing back newly copied value.
    /// 
    /// This error should not be rised when using RwLocked CoW, but atomic may.
    CoWWriteError,

    /// Is returned warning that the instance is already exclusivly copied to be written
    /// in neares future, so other thread can `read` or `try_read`.
    CoWAlreadyLocked,

    /// [libc::ENOBUFS] scenario 2, not enoughspace
    UnixNotEnoughSpace,
}

impl fmt::Display for SyslogErrCode
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result 
    {
        match self
        {
            Self::OsError(error_kind) => 
                write!(f, "[ErrorKind: {}]", error_kind),
            Self::InternalError => 
                write!(f, "[InternalError]"),
            Self::SyslogThreadNotAvailable => 
                write!(f, "[SyslogThreadNotAvailable]"),
            Self::UnboundedChannelError => 
                write!(f, "[UnboundedChannelError]"),
            Self::MutexPoisoned => 
                write!(f, "[MutexPoisoned]"),
            Self::OsErrorErrno(errn) =>
                write!(f, "[Errno: {}]", errn),
            Self::Unusable => 
                write!(f, "[Unusable]"),
            Self::SendError =>
                write!(f, "[SendError]"),
            Self::NotAvail => 
                write!(f, "[NotAvail]"),
            Self::CoWWriteError => 
                write!(f, "[CoWWriteError]"),
            Self::CoWAlreadyLocked => 
                write!(f, "[CoWAlreadyLocked]"),
            Self::UnixNotEnoughSpace => 
                write!(f, "[UnixNotEnoughSpace(ENOBUFS)]")
        }
    }
}

impl SyslogErrCode
{
    pub 
    fn get_os_err_code(&self) -> Option<ErrorKind>
    {
        match *self
        {
            Self::OsError(errn) => 
                return Some(errn),
            _ =>
                return None,
        }
    }
}

/// A syslog crate error
#[derive(Debug)]
pub struct SyslogError 
{
    errcode: SyslogErrCode,
    message: String,
}

impl SyslogError
{
    /// Creates new error
    pub
    fn new(errcode: SyslogErrCode, msg: String) -> Self
    {
        return SyslogError{errcode: errcode, message: msg};
    }

    /// Creates new error
    pub
    fn new_io(err: &std::io::Error, msg: String) -> Self
    {
        return SyslogError{errcode: SyslogErrCode::OsError(err.kind()), message: msg};
    }

    #[cfg(target_family = "unix")]
    pub
    fn new_errno(err: Errno, msg: String) -> Self
    {
        return SyslogError{errcode: SyslogErrCode::OsErrorErrno(err), message: msg};
    }

    #[cfg(target_family = "windows")]
    pub
    fn new_errno(err: i32, msg: String) -> Self
    {
        return SyslogError{errcode: SyslogErrCode::OsErrorErrno(err), message: msg};
    }

    /// Retuns a clone of [SyslogErrCode]
    pub 
    fn get_errcode(&self) -> SyslogErrCode
    {
        return self.errcode;
    }

    /// Moves out the error description
    pub 
    fn into_inner(self) -> String
    {
        return self.message;
    }
}

impl fmt::Display for SyslogError 
{
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result 
    {
        write!(f, "{} {}", self.errcode, self.message)
    }
}

pub type SyRes<T> = Result<T, SyslogError>;

#[macro_export]
macro_rules! throw_error 
{
    ($($arg:tt)*) => (
        return std::result::Result::Err($crate::error::SyslogError::new($crate::error::SyslogErrCode::InternalError, format!($($arg)*)))
    )
}

#[macro_export]
macro_rules! throw_error_os
{
    ($err:expr, $($arg:tt)*) => (
        return std::result::Result::Err($crate::error::SyslogError::new_io(&$err, format!($($arg)*)))
    )
}

#[macro_export]
macro_rules! throw_error_errno
{
    ($errno:expr, $($arg:tt)*) => (
        return std::result::Result::Err($crate::error::SyslogError::new_errno($errno, format!($($arg)*)))
    )
}

#[macro_export]
macro_rules! map_error 
{
    ($($arg:tt)*) => (
        $crate::error::SyslogError::new($crate::error::SyslogErrCode::InternalError, format!($($arg)*))
    )
}

#[macro_export]
macro_rules! map_error_os
{
    ($err:expr, $($arg:tt)*) => (
        $crate::error::SyslogError::new_io(&$err, format!($($arg)*))
    )
}

#[macro_export]
macro_rules! throw_error_code
{
    ($code:tt, $($arg:tt)*) => (
        return std::result::Result::Err($crate::error::SyslogError::new($crate::error::SyslogErrCode::$code, format!($($arg)*)))
    )
}

#[macro_export]
macro_rules! map_error_code
{
    ($code:tt, $($arg:tt)*) => (
        $crate::error::SyslogError::new($crate::error::SyslogErrCode::$code, format!($($arg)*))
    )
}
