use std::mem::transmute;

use gc_abi_sys::{eGCLogLevel, eGCLogLevel_CRITICAL, eGCLogLevel_DEBUG, eGCLogLevel_ERROR, eGCLogLevel_INFO, eGCLogLevel_WARNING};
use crate::error::GCPluginABIError;

/// Enum representing the level of a log message
#[repr(u32)]
#[derive(PartialEq, Debug, Clone, Copy, Eq, PartialOrd, Ord)]
pub enum GCLogLevel {
    DEBUG = eGCLogLevel_DEBUG,
    INFO = eGCLogLevel_INFO,
    WARNING = eGCLogLevel_WARNING,
    ERROR = eGCLogLevel_ERROR,
    CRITICAL = eGCLogLevel_CRITICAL,
}

impl GCLogLevel {
    /// Converts a u32 value to a GCLogLevel
    /// If the value is not a valid GCLogLevel, an error is returned
    pub fn from_c(c_value: eGCLogLevel) -> Result<GCLogLevel, GCPluginABIError> {
        if c_value <= eGCLogLevel_DEBUG {
            Ok(unsafe { transmute::<u32, GCLogLevel>(c_value) })
        } else {
            Err(GCPluginABIError::ErrorInvalidLogLevel(c_value))
        }
    }

    pub fn into_c(self) -> eGCLogLevel {
        self as eGCLogLevel
    }
}

impl From<log::LevelFilter> for GCLogLevel {
    fn from(level: log::LevelFilter) -> Self {
        match level {
            log::LevelFilter::Debug | log::LevelFilter::Trace => GCLogLevel::DEBUG,
            log::LevelFilter::Info => GCLogLevel::INFO,
            log::LevelFilter::Warn => GCLogLevel::WARNING,
            log::LevelFilter::Error => GCLogLevel::ERROR,
            log::LevelFilter::Off => GCLogLevel::CRITICAL,
        }
    }
}

impl From<GCLogLevel> for log::LevelFilter {
    fn from(level: GCLogLevel) -> Self {
        match level {
            GCLogLevel::DEBUG => log::LevelFilter::Debug,
            GCLogLevel::INFO => log::LevelFilter::Info,
            GCLogLevel::WARNING => log::LevelFilter::Warn,
            GCLogLevel::ERROR => log::LevelFilter::Error,
            GCLogLevel::CRITICAL => log::LevelFilter::Off,
        }
    }
}

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

    #[test]
    fn test_conversion_from_u32() {
        let error = 4;
        let error = GCLogLevel::from_c(error);
        assert_eq!(error.unwrap(), GCLogLevel::DEBUG);
    }

    #[test]
    fn test_conversion_from_u32_invalid() {
        let error = 5;
        let error = GCLogLevel::from_c(error);
        assert!(error.is_err());
    }

    #[test]
    fn test_debug_higher_level() {
        assert_eq!(
            gc_abi_sys::eGCLogLevel_DEBUG,
            4,
            "The value of eGCLogLevel_DEBUG has changed, the conversion might need to be updated if there are levels bigger then 4"
        );
    }
}
