#import "NativeCryptoBridge.h"

#include "NativeXChaCha20.hpp"

@implementation NativeCryptoBridge

+ (nullable NSData *)xChaCha20Poly1305Encrypt:(NSData *)plaintext
                                          key:(NSData *)key
                                        nonce:(NSData *)nonce {
    if (key.length != 32 || nonce.length != 24) {
        return nil;
    }

    const auto *plainBytes = static_cast<const std::uint8_t *>(plaintext.bytes);
    const auto *keyBytes = static_cast<const std::uint8_t *>(key.bytes);
    const auto *nonceBytes = static_cast<const std::uint8_t *>(nonce.bytes);

    std::vector<std::uint8_t> encrypted;
    const bool ok = rosetta::nativecrypto::xchacha20poly1305_encrypt(
        plainBytes,
        static_cast<std::size_t>(plaintext.length),
        keyBytes,
        nonceBytes,
        encrypted
    );
    if (!ok) {
        return nil;
    }

    if (encrypted.empty()) {
        return [NSData data];
    }
    return [NSData dataWithBytes:encrypted.data() length:encrypted.size()];
}

+ (nullable NSData *)xChaCha20Poly1305Decrypt:(NSData *)ciphertextWithTag
                                          key:(NSData *)key
                                        nonce:(NSData *)nonce {
    if (key.length != 32 || nonce.length != 24 || ciphertextWithTag.length < 16) {
        return nil;
    }

    const auto *cipherBytes = static_cast<const std::uint8_t *>(ciphertextWithTag.bytes);
    const auto *keyBytes = static_cast<const std::uint8_t *>(key.bytes);
    const auto *nonceBytes = static_cast<const std::uint8_t *>(nonce.bytes);

    std::vector<std::uint8_t> plaintext;
    const bool ok = rosetta::nativecrypto::xchacha20poly1305_decrypt(
        cipherBytes,
        static_cast<std::size_t>(ciphertextWithTag.length),
        keyBytes,
        nonceBytes,
        plaintext
    );
    if (!ok) {
        return nil;
    }

    if (plaintext.empty()) {
        return [NSData data];
    }
    return [NSData dataWithBytes:plaintext.data() length:plaintext.size()];
}

+ (nullable NSData *)xChaCha20Xor:(NSData *)input
                               key:(NSData *)key
                             nonce:(NSData *)nonce {
    if (key.length != 32 || nonce.length != 24) {
        return nil;
    }

    const auto *inputBytes = static_cast<const std::uint8_t *>(input.bytes);
    const auto *keyBytes = static_cast<const std::uint8_t *>(key.bytes);
    const auto *nonceBytes = static_cast<const std::uint8_t *>(nonce.bytes);

    std::vector<std::uint8_t> output;
    const bool ok = rosetta::nativecrypto::xchacha20_xor(
        inputBytes,
        static_cast<std::size_t>(input.length),
        keyBytes,
        nonceBytes,
        output
    );
    if (!ok) {
        return nil;
    }

    if (output.empty()) {
        return [NSData data];
    }
    return [NSData dataWithBytes:output.data() length:output.size()];
}

+ (nullable NSData *)naclSharedKeyFromRawDH:(NSData *)rawDH {
    if (rawDH.length != 32) {
        return nil;
    }

    const auto *rawDHBytes = static_cast<const std::uint8_t *>(rawDH.bytes);
    std::vector<std::uint8_t> sharedKey;
    const bool ok = rosetta::nativecrypto::hsalsa20_derive(rawDHBytes, sharedKey);
    if (!ok) {
        return nil;
    }

    if (sharedKey.empty()) {
        return [NSData data];
    }
    return [NSData dataWithBytes:sharedKey.data() length:sharedKey.size()];
}

@end
