//! Basic `rscrypto` usage across checksums, digests, MACs, KDFs, XOFs, fast hashes, AEAD,
//! hex formatting, and I/O adapters.
//!
//! Run with: `cargo run --example basic --features full,getrandom`

use std::io::{Cursor, Read, Write};

use rscrypto::{
  Aead, Blake3, ChaCha20Poly1305, ChaCha20Poly1305Key, Checksum, Crc32C, Digest, Ed25519Keypair, Ed25519SecretKey,
  FastHash, HkdfSha256, HmacSha256, Mac, RapidHash, Sha256, Shake256, Xof, Xxh3, aead::Nonce96,
};

fn main() -> Result<(), Box<dyn core::error::Error>> {
  println!("rscrypto basic example\n");

  checksum_api();
  digest_api();
  auth_api()?;
  aead_api()?;
  hex_api()?;
  serialization_api();
  xof_api();
  fast_hash_api();
  io_api()?;

  Ok(())
}

/// Checksums follow `new`, `update`, `finalize`, `reset`.
fn checksum_api() {
  println!("Checksums\n");

  let data = b"hello world";

  let oneshot = Crc32C::checksum(data);
  assert_eq!(oneshot, 0xC994_65AA);

  let mut streaming = Crc32C::new();
  streaming.update(b"hello ");
  streaming.update(b"world");
  assert_eq!(streaming.finalize(), oneshot);
  streaming.reset();
  streaming.update(data);
  assert_eq!(streaming.finalize(), oneshot);

  println!("CRC-32C(\"hello world\") = 0x{oneshot:08X}\n");
}

/// Digests follow `new`, `update`, `finalize`, `reset`.
fn digest_api() {
  println!("Digests\n");

  let data = b"hello world";

  let sha_oneshot = Sha256::digest(data);
  let mut sha_stream = Sha256::new();
  sha_stream.update(b"hello ");
  sha_stream.update(b"world");
  assert_eq!(sha_stream.finalize(), sha_oneshot);
  sha_stream.reset();
  sha_stream.update(data);
  assert_eq!(sha_stream.finalize(), sha_oneshot);

  let blake3_oneshot = Blake3::digest(data);
  let mut blake3_stream = Blake3::new();
  blake3_stream.update(b"hello ");
  blake3_stream.update(b"world");
  assert_eq!(blake3_stream.finalize(), blake3_oneshot);
  blake3_stream.reset();
  blake3_stream.update(data);
  assert_eq!(blake3_stream.finalize(), blake3_oneshot);

  println!("SHA-256 output size = {} bytes", sha_oneshot.len());
  println!("BLAKE3 output size  = {} bytes\n", blake3_oneshot.len());
}

/// MACs and KDFs keep the same one-shot vs stateful split.
fn auth_api() -> Result<(), Box<dyn core::error::Error>> {
  println!("Auth\n");

  let key = b"shared-secret";
  let data = b"hello world";

  let tag = HmacSha256::mac(key, data);

  let mut mac = HmacSha256::new(key);
  mac.update(b"hello ");
  mac.update(b"world");
  assert!(mac.verify(&tag).is_ok());
  mac.reset();
  mac.update(data);
  assert!(mac.verify(&tag).is_ok());

  let hkdf = HkdfSha256::new(b"salt", b"input key material");
  let mut okm = [0u8; 42];
  hkdf.expand(b"context", &mut okm)?;

  let oneshot = HkdfSha256::derive_array::<42>(b"salt", b"input key material", b"context")?;
  assert_eq!(okm, oneshot);

  println!("HMAC-SHA256 tag size = {} bytes", tag.as_slice().len());
  println!("HKDF-SHA256 output   = {} bytes\n", okm.len());

  Ok(())
}

/// AEAD: encrypt, authenticate, and decrypt with typed keys and nonces.
fn aead_api() -> Result<(), Box<dyn core::error::Error>> {
  println!("AEAD\n");

  let key = ChaCha20Poly1305Key::from_bytes([0x11; 32]);
  let aead = ChaCha20Poly1305::new(&key);

  let mut sealed = [0u8; 5 + ChaCha20Poly1305::TAG_SIZE];
  let nonce = aead.seal_random(b"", b"hello", &mut sealed)?;

  let mut opened = [0u8; 5];
  aead.decrypt(&nonce, b"", &sealed, &mut opened)?;
  assert_eq!(&opened, b"hello");

  println!("ChaCha20-Poly1305 round-trip succeeded");
  println!("  nonce = {nonce}\n");

  Ok(())
}

/// Hex Display, FromStr, and secret key masking.
fn hex_api() -> Result<(), Box<dyn core::error::Error>> {
  println!("Hex formatting\n");

  let nonce = Nonce96::from_bytes([0xab; 12]);
  println!("Display:  {nonce}");
  println!("LowerHex: {nonce:x}");
  println!("UpperHex: {nonce:X}");
  println!("Debug:    {nonce:?}");

  let parsed: Nonce96 = "abababababababababababab".parse()?;
  assert_eq!(parsed, nonce);
  println!("FromStr:  round-trip succeeded");

  let key = ChaCha20Poly1305Key::from_bytes([0x42; 32]);
  let key_debug = format!("{key:?}");
  assert_eq!(key_debug, "ChaCha20Poly1305Key(****)");
  println!("\nSecret Debug: {key_debug}");

  let ed_sk = Ed25519SecretKey::from_bytes([7u8; 32]);
  let kp = Ed25519Keypair::from_secret_key(ed_sk);
  println!("\nEd25519 public key: {}", kp.public_key());
  let sig = kp.sign(b"hello");
  println!("Ed25519 signature:  {sig}");
  println!();

  Ok(())
}

/// Serialization via from_bytes / to_bytes / as_bytes.
fn serialization_api() {
  println!("Serialization\n");

  let key = ChaCha20Poly1305Key::from_bytes([0x42; 32]);
  let raw: [u8; 32] = *key.as_bytes();
  let restored = ChaCha20Poly1305Key::from_bytes(raw);
  assert_eq!(key.as_bytes(), restored.as_bytes());
  println!("Key round-trip via as_bytes/from_bytes succeeded");

  let nonce = Nonce96::from_bytes([0xab; 12]);
  assert_eq!(nonce, Nonce96::from_bytes(nonce.to_bytes()));
  println!("Nonce round-trip succeeded\n");
}

/// XOFs support both `new`, `update`, `finalize_xof` and `xof(data)`.
fn xof_api() {
  println!("XOFs\n");

  let data = b"hello world";

  let mut shake_stream = Shake256::xof(data);
  let mut shake_stream_out = [0u8; 64];
  shake_stream.squeeze(&mut shake_stream_out);

  let mut shake = Shake256::new();
  shake.update(data);
  let mut shake_oneshot_out = [0u8; 64];
  shake.finalize_xof().squeeze(&mut shake_oneshot_out);
  assert_eq!(shake_stream_out, shake_oneshot_out);

  let mut blake3_xof = Blake3::xof(data);
  let mut blake3_out = [0u8; 64];
  blake3_xof.squeeze(&mut blake3_out);
  assert_eq!(&blake3_out[..32], &Blake3::digest(data));

  println!("SHAKE256 produced {} bytes", shake_stream_out.len());
  println!("BLAKE3 XOF produced {} bytes\n", blake3_out.len());
}

fn fast_hash_api() {
  println!("Fast hashes\n");

  let data = b"hello world";

  let xxh_default = Xxh3::hash(data);
  let xxh_seeded = Xxh3::hash_with_seed(7, data);
  assert_ne!(xxh_default, xxh_seeded);

  let rapid_default = RapidHash::hash(data);
  let rapid_seeded = RapidHash::hash_with_seed(7, data);
  assert_ne!(rapid_default, rapid_seeded);

  println!("Xxh3(default)       = 0x{xxh_default:016X}");
  println!("RapidHash(default) = 0x{rapid_default:016X}\n");
}

fn io_api() -> std::io::Result<()> {
  println!("I/O adapters\n");

  let data = b"stream me through adapters";

  let mut reader = Sha256::reader(Cursor::new(data.to_vec()));
  let mut copied = Vec::new();
  reader.read_to_end(&mut copied)?;
  assert_eq!(copied, data);
  assert_eq!(reader.digest(), Sha256::digest(data));

  let mut checksum_writer = Crc32C::writer(Vec::new());
  checksum_writer.write_all(data)?;
  let (written, crc) = checksum_writer.into_parts();
  assert_eq!(written, data);
  assert_eq!(crc, Crc32C::checksum(data));

  let mut digest_writer = Blake3::writer(Vec::new());
  digest_writer.write_all(data)?;
  let (written, digest) = digest_writer.into_parts();
  assert_eq!(written, data);
  assert_eq!(digest, Blake3::digest(data));

  println!("reader digest matches Sha256::digest()");
  println!("writer checksum matches Crc32C::checksum()");
  println!("writer digest matches Blake3::digest()\n");

  Ok(())
}
