/*-
 * Scram-rs - a SCRAM authentification authorization library
 * 
 * Copyright (C) 2021  Aleksandr Morozov
 * Copyright (C) 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 core::ffi::c_char;

#[cfg(feature = "std")]
use std::ffi::CString;

#[cfg(not(feature = "std"))]
use alloc::boxed::Box;

#[cfg(not(feature = "std"))]
use alloc::ffi::CString;

#[cfg(not(feature = "std"))]
use alloc::string::String;




use crate::{ScramErrorCode, ScramRuntimeError, ScramServerError};

#[repr(C)]
#[derive(Debug)]
pub struct CApiScramRuntimeError
{
    /// Error code for internal usage
    err_code: ScramErrorCode,
    /// Error code for exteranl usage i.e RFC5802 errors for error 
    ///  handling on client side if no other signalling is used.
    err_serv: ScramServerError,
    /// Human readable message with description. message + null-term
    err_message: CString,
}

impl CApiScramRuntimeError
{
    pub 
    fn new2(e: ScramRuntimeError) -> CApiScramRuntimeError
    {
        return
            CApiScramRuntimeError
            {
                err_code: e.err_code,
                err_serv: e.err_serv,
                err_message: CString::new(e.message.as_bytes()).unwrap()
            };
    }

    pub 
    fn new_msg2(err_code: ScramErrorCode, err_serv: ScramServerError, msg: String) -> CApiScramRuntimeError
    {
        return
            CApiScramRuntimeError
            {
                err_code: err_code,
                err_serv: err_serv,
                err_message: CString::new(msg.as_bytes()).unwrap()
            };
    }

    pub 
    fn into_capi(self) -> *mut CApiScramRuntimeError
    {
        return Box::into_raw(Box::new(self));
    }
}

/*
pub struct CApiScramRuntimeError 
{
    /// Error code for internal usage
    err_code: ScramErrorCode,
    /// Error code for exteranl usage i.e RFC5802 errors for error 
    ///  handling on client side if no other signalling is used.
    err_serv: ScramServerError,
    /// Human readable message with description. message + null-term
    msg: [u8; 512],

    msg_len: usize,
}

impl CApiScramRuntimeError
{
    pub 
    fn new(err_code: ScramErrorCode, err_serv: ScramServerError) -> Self
    {
        return Self{err_code: err_code, err_serv: err_serv, msg: [0_u8; 512], msg_len: 0};
    }

    #[inline]
    fn capacity(&self) -> usize 
    {
        self.msg.len()
    }

    fn len(&self) -> usize 
    {
        self.msg_len
    }
}

impl fmt::Write for CApiScramRuntimeError
{
    fn write_str(&mut self, s: &str) -> fmt::Result 
    {
        if self.msg_len >= self.capacity() - 1
        {
            // full
            return Ok(());
        }

        let last_idx = s.as_bytes().len().min(self.capacity() - self.len() - 1);
        let last_len = last_idx + self.len();

        self
            .msg[self.msg_len..last_len]
            .copy_from_slice(&s.as_bytes()[0..last_idx]);

        self.msg_len += last_idx;

        Ok(())
    }
}
    */

pub type CApiScramResult<T> = Result<T, CApiScramRuntimeError>;

/*
pub 
fn capi_scram_box_err(err: CApiScramRuntimeError) -> *mut CApiScramRuntimeError
{
    return Box::into_raw(Box::new(err));
}*/


/*
#[macro_export]
macro_rules! capi_scram_error_map
{
    ($src:expr, $serv_err:expr, $($arg:tt)*) => (
        {
            #[cfg(feature = "std")]
            use std::fmt::Write;
            #[cfg(not(feature = "std"))]
            use core::fmt::Write;

            let mut c_scrm_err = $crate::c_api::error::CApiScramRuntimeError::new($src, $serv_err);
            let _ = c_scrm_err.write_fmt(format_args!($($arg)*));
            Box::into_raw(Box::new(c_scrm_err))
        }
    )
}

#[macro_export]
macro_rules! capi_scram_error_map_err
{
    ($er:expr) => (
        {
            #[cfg(feature = "std")]
            use std::fmt::Write;
            #[cfg(not(feature = "std"))]
            use core::fmt::Write;

            let mut c_scrm_err = $crate::c_api::error::CApiScramRuntimeError::new($er.err_code, $er.err_serv);
            let _ = c_scrm_err.write_str($er.message.as_str());
            Box::into_raw(Box::new(c_scrm_err))
        }
    )
}

#[macro_export]
macro_rules! capi_scram_error
{
    ($src:expr, $serv_err:expr, $($arg:tt)*) => (
        return Err(
            {
                #[cfg(feature = "std")]
                use std::fmt::Write;
                #[cfg(not(feature = "std"))]
                use core::fmt::Write;

                let mut c_scrm_err = $crate::c_api::error::CApiScramRuntimeError::new($src, $serv_err);
                let _ = c_scrm_err.write_fmt(format_args!($($arg)*));
                Box::into_raw(Box::new(c_scrm_err))
            }
        )
    )
}
    */



// new

#[cfg(feature = "std")]
#[macro_export]
macro_rules! capi_scram_err_map
{
    ($er:expr) => (
        {
            $crate::c_api::error::CApiScramRuntimeError::new2($er)
        }
    );
    ($src:expr, $serv_err:expr, $($arg:tt)*) => (
        {
            $crate::c_api::error::CApiScramRuntimeError::new_msg2($src, $serv_err, format!($($arg)*))
        }
    )
}


#[cfg(not(feature = "std"))]
#[macro_export]
macro_rules! capi_scram_err_map
{
    ($er:expr) => (
        {
            $crate::c_api::error::CApiScramRuntimeError::new2($er)
        }
    );
    ($src:expr, $serv_err:expr, $($arg:tt)*) => (
        {
            $crate::c_api::error::CApiScramRuntimeError::new_msg2($src, $serv_err, alloc::format!($($arg)*))
        }
    )
}

#[cfg(feature = "std")]
#[macro_export]
macro_rules! capi_scram_err
{
    ($src:expr, $serv_err:expr, $($arg:tt)*) => (
        return Err(
            {
                $crate::c_api::error::CApiScramRuntimeError::new_msg2($src, $serv_err, format!($($arg)*))
            }
        )
    )
}

#[cfg(not(feature = "std"))]
#[macro_export]
macro_rules! capi_scram_err
{
    ($src:expr, $serv_err:expr, $($arg:tt)*) => (
        return Err(
            {
                $crate::c_api::error::CApiScramRuntimeError::new_msg2($src, $serv_err, alloc::format!($($arg)*))
            }
        )
    )
}


#[cfg(feature = "std")]
#[macro_export]
macro_rules! capi_scram_err_res
{
    ($src:expr, $serv_err:expr, $($arg:tt)*) => (
        Err(crate::c_api::error::CApiScramRuntimeError::new_msg2($src, $serv_err, format!($($arg)*)))
    )
}

#[cfg(not(feature = "std"))]
#[macro_export]
macro_rules! capi_scram_err_res
{
    ($src:expr, $serv_err:expr, $($arg:tt)*) => (
        Err($crate::c_api::error::CApiScramRuntimeError::new_msg2($src, $serv_err, alloc::format!($($arg)*)))
            
        
    )
}



#[unsafe(no_mangle)]
pub unsafe extern "C" 
fn capi_scram_error_get_code(error: *mut CApiScramRuntimeError) -> ScramErrorCode
{
    if error.is_null() == true
    {
        panic!("Assertion trap: capi_scram_error_get_code() error is NULL!");
    }

    return unsafe { error.as_ref().unwrap().err_code };
}

#[unsafe(no_mangle)]
pub unsafe extern "C" 
fn capi_scram_error_get_descr(error: *mut CApiScramRuntimeError) -> *const c_char
{
    if error.is_null() == true
    {
        panic!("Assertion trap: capi_scram_error_get_descr() error is NULL!");
    }

    return unsafe { error.as_ref().unwrap().err_message.as_ptr() };
}

#[unsafe(no_mangle)]
pub unsafe extern "C" 
fn capi_scram_error_free(error: *mut CApiScramRuntimeError)
{
    if error.is_null() == true
    {
        return;
    }

    drop(unsafe { Box::from_raw(error) });

    return;
}

/*
#[cfg(test)]
mod tests
{
    use crate::{c_api::error::CApiScramRuntimeError, ScramErrorCode, ScramServerError};

    #[test]
    fn test_error1()
    {
        #[cfg(feature = "std")]
        use std::fmt::Write;
        #[cfg(not(feature = "std"))]
        use core::fmt::Write;

        let mut c_scrm_err = 
            CApiScramRuntimeError::new(ScramErrorCode::InternalError, ScramServerError::None);

        let var1 = 1;
                    
        let _ = c_scrm_err.write_fmt(format_args!("test error message! {}", var1));

        assert_eq!(c_scrm_err.len(), 21);
        
        let var2 = "xaaoofkefxvcyrvvifbvfiafqskcbcjkyairedeeqwbktrzwmcippatbcrojehihfjnccrkbxjlnkwxxrfthamacrddbxlepfjizvbhygclmcnqkwvjdnxzflfolmjmeongdmvmrincilzqlvuvgzyusukysreimctgtxokzlmdsexszffulfwamufindulmfzltnnaiomawtkbopspzlggcmagjidvxgrsvlkogbrqjaiqevmyobocusxskxidatlhutlrkciksuxqotqrecpfrlcqfekhdlriouybxloclrflujvhlqlkqqflbpznvkvcpaehekxwtykushdqlkumkwawgaaaoobngixujlmmpxaeakhysgenhqbsrognwmcztjlxahljuhsucbsftcxdcqmlrswiwpflciouclkpohceplanmkzsblvtseyykuzewzwyycgmuezuhgbsuniubkjkvzqpjrpmtlunqla";
        let _ = c_scrm_err.write_str(var2);

        assert_eq!(c_scrm_err.len(), 21+var2.len());
        assert_eq!(c_scrm_err.msg[c_scrm_err.len()], 0);
        
        let _ = c_scrm_err.write_fmt(format_args!("test error message! {}", var1));

        assert_eq!(c_scrm_err.len(), 21+var2.len());
        assert_eq!(c_scrm_err.msg[c_scrm_err.len()], 0);
    }
}
*/