local ffi = require("ffi")
local loaded, lib = pcall(ffi.load, "sodium")

if not loaded then return nil, lib end

local new = ffi.new
local typeof = ffi.typeof
local format = string.format
ffi.cdef[[
const char *sodium_version_string(void);
const char *crypto_secretbox_primitive(void);

size_t crypto_secretbox_keybytes(void);
size_t crypto_secretbox_noncebytes(void);
size_t crypto_secretbox_macbytes(void);
size_t crypto_secretbox_zerobytes(void);

int crypto_secretbox_easy(
	unsigned char *c,
	const unsigned char *m,
	unsigned long long mlen,
	const unsigned char *n,
	const unsigned char *k
);

int crypto_secretbox_open_easy(
	unsigned char *m,
	const unsigned char *c,
	unsigned long long clen,
	const unsigned char *n,
	const unsigned char *k
);

void randombytes(unsigned char* const buf, const unsigned long long buf_len);
]]
local sodium = {}
local MACBYTES = lib.crypto_secretbox_macbytes()
local NONCEBYTES = lib.crypto_secretbox_noncebytes()
local KEYBYTES = lib.crypto_secretbox_keybytes()
local key_t = typeof(format("const unsigned char[%i]", tonumber(KEYBYTES)))
local nonce_t = typeof(format("unsigned char[%i] const", tonumber(NONCEBYTES)))

function sodium.key(key)
	return key_t(key)
end

function sodium.nonce()
	local nonce = nonce_t()
	lib.randombytes(nonce, NONCEBYTES)
	return nonce, NONCEBYTES
end

function sodium.encrypt(decrypted, decrypted_len, nonce, key)
	local encrypted_len = decrypted_len + MACBYTES
	local encrypted = new("unsigned char[?]", encrypted_len)

	if lib.crypto_secretbox_easy(encrypted, decrypted, decrypted_len, nonce, key) < 0 then
		return error("libsodium encryption failed")
	end

	return encrypted, encrypted_len
end

function sodium.decrypt(encrypted, encrypted_len, nonce, key)
	local decrypted_len = encrypted_len - MACBYTES
	local decrypted = new("unsigned char[?]", decrypted_len)

	if
		lib.crypto_secretbox_open_easy(decrypted, encrypted, encrypted_len, nonce, key) < 0
	then
		return error("libsodium decryption failed")
	end

	return decrypted, decrypted_len
end

return sodium