/*-
 * 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, num::NonZeroU32};

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

#[cfg(feature = "use_ring")]
use crate::{ScramSha1Ring, ScramSha256Ring, ScramSha512Ring};

use crate::
{
    scram_ierror_map, 
    ScramErrorCode, 
    ScramKey, 
    ScramPassword, 
    ScramResult, 
    ScramSha1RustNative, 
    ScramSha256RustNative, 
    ScramSha512RustNative,
    ScramSha3_512RustNative
};

use super::{common, into_boxed_ptr, CApiScramHasherProvider, CApiScramTypeAlias};

#[unsafe(no_mangle)]
pub unsafe extern "C" 
fn capi_scram_password_not_found(hash_prov: CApiScramHasherProvider, hash_type: CApiScramTypeAlias) -> *mut ScramResult<*mut ScramPassword>
{
    let ret = 
        match (hash_prov, hash_type)
        {
            #[cfg(not(feature = "exclude_sha1"))]
            (CApiScramHasherProvider::RustNative, CApiScramTypeAlias::Sha1) => 
                into_boxed_ptr(ScramPassword::not_found::<ScramSha1RustNative>().map(|s| into_boxed_ptr(s))),

            #[cfg(feature = "exclude_sha1")]
            (CApiScramHasherProvider::RustNative, CApiScramTypeAlias::Sha1) => 
                into_boxed_ptr(Err(scram_ierror_map!(ScramErrorCode::FeatureNotSupported, "compiled with exclude_sha1 support"))),

            (CApiScramHasherProvider::RustNative, CApiScramTypeAlias::Sha256) => 
                into_boxed_ptr(ScramPassword::not_found::<ScramSha256RustNative>().map(|s| into_boxed_ptr(s))),
            (CApiScramHasherProvider::RustNative, CApiScramTypeAlias::Sha512) => 
                into_boxed_ptr(ScramPassword::not_found::<ScramSha512RustNative>().map(|s| into_boxed_ptr(s))),
            (CApiScramHasherProvider::RustNative, CApiScramTypeAlias::Sha3_512) => 
                into_boxed_ptr(ScramPassword::not_found::<ScramSha3_512RustNative>().map(|s| into_boxed_ptr(s))),
            
            #[cfg(all(feature = "use_ring", not(feature = "exclude_sha1")))]
            (CApiScramHasherProvider::RustRing, CApiScramTypeAlias::Sha1) => 
                into_boxed_ptr(ScramPassword::not_found::<ScramSha1Ring>().map(|s| into_boxed_ptr(s))),

            #[cfg(any(not(feature = "use_ring"), feature = "exclude_sha1"))]
            (CApiScramHasherProvider::RustRing, CApiScramTypeAlias::Sha1) => 
                into_boxed_ptr(Err(scram_ierror_map!(ScramErrorCode::FeatureNotSupported, "compiled without ring / with exclude_sha1 support"))),
            
            #[cfg(feature = "use_ring")]
            (CApiScramHasherProvider::RustRing, CApiScramTypeAlias::Sha256) => 
                into_boxed_ptr(ScramPassword::not_found::<ScramSha256Ring>().map(|s| into_boxed_ptr(s))),

            #[cfg(not(feature = "use_ring"))]
            (CApiScramHasherProvider::RustRing, CApiScramTypeAlias::Sha256) => 
                into_boxed_ptr(Err(scram_ierror_map!(ScramErrorCode::FeatureNotSupported, "compiled without ring support"))),
            
            #[cfg(feature = "use_ring")]
            (CApiScramHasherProvider::RustRing, CApiScramTypeAlias::Sha512) => 
                into_boxed_ptr(ScramPassword::not_found::<ScramSha512Ring>().map(|s| into_boxed_ptr(s))),

            #[cfg(feature = "use_ring")]
            (CApiScramHasherProvider::RustRing, CApiScramTypeAlias::Sha3_512) => 
                into_boxed_ptr(Err(scram_ierror_map!(ScramErrorCode::FeatureNotSupported, "RING does not support SHA3 at the moment"))),

            #[cfg(not(feature = "use_ring"))]
            (CApiScramHasherProvider::RustRing, CApiScramTypeAlias::Sha512) => 
                into_boxed_ptr(Err(scram_ierror_map!(ScramErrorCode::FeatureNotSupported, "compiled without ring support"))),

            #[cfg(not(feature = "use_ring"))]
            (CApiScramHasherProvider::RustRing, CApiScramTypeAlias::Sha3_512) => 
                into_boxed_ptr(Err(scram_ierror_map!(ScramErrorCode::FeatureNotSupported, "RING does not support SHA3 at the moment"))),
        };
    
    return ret;
}

#[unsafe(no_mangle)]
pub unsafe extern "C" 
fn capi_scram_password_found_plaintext(hash_prov: CApiScramHasherProvider, hash_type: CApiScramTypeAlias, 
    password: *const u8, pass_len: usize, scram_keys_opt: *mut ScramKey) -> *mut ScramResult<*mut ScramPassword>
{
    let pass = 
        match common::carr_to_slice_n(password, pass_len, "password is not valid")
        {
            Ok(r) => r,
            Err(e) =>
                return into_boxed_ptr(Err(e))
        };

    let scram_keys = 
        if scram_keys_opt.is_null() == false
        {
            Some(* unsafe { Box::from_raw(scram_keys_opt) })
        }
        else
        {
            None
        };

    let ret = 
        match (hash_prov, hash_type)
        {
            #[cfg(not(feature = "exclude_sha1"))]
            (CApiScramHasherProvider::RustNative, CApiScramTypeAlias::Sha1)  => 
                into_boxed_ptr(
                    ScramPassword
                        ::found_plaintext_password
                        ::<ScramSha1RustNative>(pass, scram_keys)
                            .map(|s| into_boxed_ptr(s))
                ),

            #[cfg(feature = "exclude_sha1")]
            (CApiScramHasherProvider::RustNative, CApiScramTypeAlias::Sha1)  => 
                into_boxed_ptr(Err(scram_ierror_map!(ScramErrorCode::FeatureNotSupported, "Compiled without SHA1 support"))),

            (CApiScramHasherProvider::RustNative, CApiScramTypeAlias::Sha256) => 
                into_boxed_ptr(
                    ScramPassword
                        ::found_plaintext_password
                        ::<ScramSha256RustNative>(pass, scram_keys)
                            .map(|s| into_boxed_ptr(s))
                ),

            (CApiScramHasherProvider::RustNative, CApiScramTypeAlias::Sha512) => 
                into_boxed_ptr(
                    ScramPassword
                        ::found_plaintext_password
                        ::<ScramSha512RustNative>(pass, scram_keys)
                            .map(|s| into_boxed_ptr(s))
                ),

            (CApiScramHasherProvider::RustNative, CApiScramTypeAlias::Sha3_512) => 
                into_boxed_ptr(
                    ScramPassword
                        ::found_plaintext_password
                        ::<ScramSha3_512RustNative>(pass, scram_keys)
                            .map(|s| into_boxed_ptr(s))
                ),

            #[cfg(all(feature = "use_ring", not(feature = "exclude_sha1")))]
            (CApiScramHasherProvider::RustRing, CApiScramTypeAlias::Sha1) => 
                into_boxed_ptr(
                    ScramPassword
                        ::found_plaintext_password
                        ::<ScramSha1Ring>(pass, scram_keys)
                            .map(|s| into_boxed_ptr(s))
                ),
            
            #[cfg(any(not(feature = "use_ring"), feature = "exclude_sha1"))]
            (CApiScramHasherProvider::RustRing, CApiScramTypeAlias::Sha1) => 
                into_boxed_ptr(Err(scram_ierror_map!(ScramErrorCode::FeatureNotSupported, "Rust Ring is not available"))),

            #[cfg(feature = "use_ring")]                
            (CApiScramHasherProvider::RustRing, CApiScramTypeAlias::Sha256) => 
                into_boxed_ptr(
                    ScramPassword
                        ::found_plaintext_password
                        ::<ScramSha256Ring>(pass, scram_keys)
                            .map(|s| into_boxed_ptr(s))
                ),

            #[cfg(not(feature = "use_ring"))]   
            (CApiScramHasherProvider::RustRing, CApiScramTypeAlias::Sha256) => 
                into_boxed_ptr(Err(scram_ierror_map!(ScramErrorCode::FeatureNotSupported, "Rust Ring is not available"))),

            #[cfg(feature = "use_ring")]
            (CApiScramHasherProvider::RustRing, CApiScramTypeAlias::Sha512) => 
                into_boxed_ptr(
                    ScramPassword
                        ::found_plaintext_password
                        ::<ScramSha512Ring>(pass, scram_keys)
                            .map(|s| into_boxed_ptr(s))
                ),

            #[cfg(feature = "use_ring")] 
            (CApiScramHasherProvider::RustRing, CApiScramTypeAlias::Sha3_512) => 
                into_boxed_ptr(Err(scram_ierror_map!(ScramErrorCode::FeatureNotSupported, "Rust Ring does not support SHA3"))),

            #[cfg(not(feature = "use_ring"))]   
            (CApiScramHasherProvider::RustRing, CApiScramTypeAlias::Sha512) => 
                into_boxed_ptr(Err(scram_ierror_map!(ScramErrorCode::FeatureNotSupported, "Rust Ring is not available"))),

            #[cfg(not(feature = "use_ring"))]   
            (CApiScramHasherProvider::RustRing, CApiScramTypeAlias::Sha3_512) => 
                into_boxed_ptr(Err(scram_ierror_map!(ScramErrorCode::FeatureNotSupported, "Rust Ring does not support SHA3"))),
        };
    
    return ret;
}

#[unsafe(no_mangle)]
pub unsafe extern "C" 
fn capi_scram_password_with_params(
    hash_prov: CApiScramHasherProvider, 
    hash_type: CApiScramTypeAlias,
    plain_password: *const u8, 
    plain_password_len: usize, 
    salt_plain_opt: *const u8,
    salt_plain_len: usize,
    iterations_opt: *const u32,
    scram_keys_opt: *mut ScramKey
) -> *mut ScramResult<*mut ScramPassword>
{
    let pass = 
        match common::carr_to_slice_n(plain_password, plain_password_len, "plain_password is not valid")
        {
            Ok(r) => r,
            Err(e) =>
                return into_boxed_ptr(Err(e))
        }
        .to_vec();

    let salt = 
        if salt_plain_opt.is_null() == false
        {
            match common::carr_to_slice_n(salt_plain_opt, salt_plain_len, "salt_plain_opt is not valid")
            {
                Ok(r) => Some(r.to_vec()),
                Err(e) =>
                    return into_boxed_ptr(Err(e))
            }
        }
        else
        {
            None
        };
    
    let iters = 
        if let Some(iterations) = unsafe { iterations_opt.as_ref() }
        {
            let iters = NonZeroU32::new(*iterations);

            if iters.is_none() == true
            {
                return into_boxed_ptr(Err(scram_ierror_map!(ScramErrorCode::InternalError, "iterations can not be 0")));
            }

            iters
        }
        else
        {
            None
        };

    let scram_keys = 
        if scram_keys_opt.is_null() == false
        {
            Some(* unsafe { Box::from_raw(scram_keys_opt) })
        }
        else
        {
            None
        };

    let scram_password_res = 
        match (hash_prov, hash_type)
        {
            #[cfg(not(feature = "exclude_sha1"))]
            (CApiScramHasherProvider::RustNative, CApiScramTypeAlias::Sha1) => 
                ScramPassword::salt_password_with_params::<_, ScramSha1RustNative>(&pass, salt, iters, scram_keys),

            #[cfg(feature = "exclude_sha1")]
            (CApiScramHasherProvider::RustNative, CApiScramTypeAlias::Sha1) =>
                return into_boxed_ptr(Err(scram_ierror_map!(ScramErrorCode::FeatureNotSupported, "compiled without sha1 support"))),

            (CApiScramHasherProvider::RustNative, CApiScramTypeAlias::Sha256) => 
                ScramPassword::salt_password_with_params::<_, ScramSha256RustNative>(&pass, salt, iters, scram_keys),
            (CApiScramHasherProvider::RustNative, CApiScramTypeAlias::Sha512) => 
                ScramPassword::salt_password_with_params::<_, ScramSha512RustNative>(&pass, salt, iters, scram_keys),
            (CApiScramHasherProvider::RustNative, CApiScramTypeAlias::Sha3_512) => 
                ScramPassword::salt_password_with_params::<_, ScramSha3_512RustNative>(&pass, salt, iters, scram_keys),
            
            #[cfg(all(feature = "use_ring", not(feature = "exclude_sha1")))]
            (CApiScramHasherProvider::RustRing, CApiScramTypeAlias::Sha1) => 
                ScramPassword::salt_password_with_params::<_, ScramSha1Ring>(&pass, salt, iters, scram_keys),
            
            #[cfg(any(not(feature = "use_ring"), feature = "exclude_sha1"))]
            (CApiScramHasherProvider::RustRing, CApiScramTypeAlias::Sha1) => 
                return into_boxed_ptr(Err(scram_ierror_map!(ScramErrorCode::FeatureNotSupported, "compiled without sha1 or ring support"))),

            #[cfg(feature = "use_ring")]
            (CApiScramHasherProvider::RustRing, CApiScramTypeAlias::Sha256) => 
                ScramPassword::salt_password_with_params::<_, ScramSha256Ring>(&pass, salt, iters, scram_keys),
            
            #[cfg(not(feature = "use_ring"))]
            (CApiScramHasherProvider::RustRing, CApiScramTypeAlias::Sha256) => 
                return into_boxed_ptr(Err(scram_ierror_map!(ScramErrorCode::FeatureNotSupported, "compiled without ring support"))),

            #[cfg(feature = "use_ring")]
            (CApiScramHasherProvider::RustRing, CApiScramTypeAlias::Sha512) =>
                ScramPassword::salt_password_with_params::<_, ScramSha512Ring>(&pass, salt, iters, scram_keys),

            #[cfg(feature = "use_ring")]
            (CApiScramHasherProvider::RustRing, CApiScramTypeAlias::Sha3_512) => 
                return into_boxed_ptr(Err(scram_ierror_map!(ScramErrorCode::FeatureNotSupported, "RING does not support SHA3"))),

            #[cfg(not(feature = "use_ring"))]
            (CApiScramHasherProvider::RustRing, CApiScramTypeAlias::Sha512) => 
                return into_boxed_ptr(Err(scram_ierror_map!(ScramErrorCode::FeatureNotSupported, "compiled without ring support"))),

            #[cfg(not(feature = "use_ring"))]
            (CApiScramHasherProvider::RustRing, CApiScramTypeAlias::Sha3_512) => 
                return into_boxed_ptr(Err(scram_ierror_map!(ScramErrorCode::FeatureNotSupported, "RING does not support SHA3"))),
        };

    if let Err(e) = scram_password_res
    {
        return into_boxed_ptr(Err(e));
    }
        
    let ret = 
        into_boxed_ptr(scram_password_res.unwrap());
    
    return into_boxed_ptr(Ok(ret));
}

#[unsafe(no_mangle)]
pub unsafe extern "C" 
fn capi_scram_password_found_secret_base64_password(
    salted_hashed_password_base64: *const c_char,  
    salt_base64: *const c_char,
    iterations: u32,
    scram_keys_opt: *mut ScramKey
) -> *mut ScramResult<*mut ScramPassword>
{
    let salt_pass = 
        match common::cstr_to_string_n(salted_hashed_password_base64, "salted_hashed_password_base64 is not valid")
        {
            Ok(r) => r,
            Err(e) =>
                return into_boxed_ptr(Err(e))
        };

    let salt_base64_str = 
        match common::cstr_to_string_n(salt_base64, "salt_base64 is not valid")
        {
            Ok(r) => r,
            Err(e) =>
                return into_boxed_ptr(Err(e))
        };
    
    let iter_nonz = 
        match NonZeroU32::new(iterations)
        {
            Some(r) => 
                r,
            None =>
                return into_boxed_ptr(Err(scram_ierror_map!(ScramErrorCode::InternalError, "iterations can not be 0")))
        };

    let scram_keys = 
        if scram_keys_opt.is_null() == false
        {
            Some(* unsafe { Box::from_raw(scram_keys_opt) })
        }
        else
        {
            None
        };

    let ret = 
        into_boxed_ptr(
            ScramPassword
                ::found_secret_base64_password(salt_pass, salt_base64_str, iter_nonz, scram_keys)
                    .map(|s| into_boxed_ptr(s))
        );
    
    return ret;
}

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

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

    return;
}

