//! Actual implementation of the NAR encoder.
//!
//! See `parser` for the format description.

use std::{ffi::OsString, io, os::unix::prelude::OsStrExt, path::PathBuf};

pub fn start_archive(buf: &mut [u8]) -> Result<usize, io::Error> {
    write_str(buf, "nix-archive-1")
}

pub fn start_entry(buf: &mut [u8]) -> Result<usize, io::Error> {
    write_str(buf, "(")
}

pub fn close_entry(buf: &mut [u8]) -> Result<usize, io::Error> {
    write_str(buf, ")")
}

pub fn close_dir_entry(buf: &mut [u8]) -> Result<usize, io::Error> {
    let len = write_str(buf, ")")?;
    let len = len + write_str(&mut buf[len..], ")")?;
    Ok(len)
}

pub fn start_dir(buf: &mut [u8]) -> Result<usize, io::Error> {
    let len = write_str(buf, "type")?;
    let len = len + write_str(&mut buf[len..], "directory")?;
    Ok(len)
}

pub fn start_dir_entry(buf: &mut [u8], name: OsString) -> Result<usize, io::Error> {
    let len = write_str(buf, "entry")?;
    let len = len + write_str(&mut buf[len..], "(")?;
    let len = len + write_str(&mut buf[len..], "name")?;
    let len = len + write_bytes(&mut buf[len..], name.as_bytes())?;
    let len = len + write_str(&mut buf[len..], "node")?;
    let len = len + write_str(&mut buf[len..], "(")?;
    Ok(len)
}

pub fn write_symlink(buf: &mut [u8], target: PathBuf) -> Result<usize, io::Error> {
    let len = write_str(buf, "type")?;
    let len = len + write_str(&mut buf[len..], "symlink")?;
    let len = len + write_str(&mut buf[len..], "target")?;
    let len = len + write_str(&mut buf[len..], &target.display().to_string())?;
    Ok(len)
}

pub fn write_file_regular(buf: &mut [u8], executable: bool) -> Result<usize, io::Error> {
    let mut len = write_str(buf, "type")?;
    len += write_str(&mut buf[len..], "regular")?;
    if executable {
        len += write_str(&mut buf[len..], "executable")?;
        len += write_str(&mut buf[len..], "")?;
    }
    len += write_str(&mut buf[len..], "contents")?;
    Ok(len)
}

pub fn write_str(buf: &mut [u8], str: &str) -> Result<usize, io::Error> {
    write_bytes(buf, str.as_bytes())
}

pub fn write_bytes(buf: &mut [u8], bytes: &[u8]) -> Result<usize, io::Error> {
    let len = bytes.len();
    let len_rounded_up = (len + 7) & !7;
    if len_rounded_up + 8 > buf.len() {
        return Err(io::Error::new(
            io::ErrorKind::Other,
            format!(
                "not enough space to serialize {bytes:#?}, need {}, have {}",
                len_rounded_up + 8,
                buf.len()
            ),
        ));
    }
    buf[..8].copy_from_slice(&u64::to_le_bytes(len as u64));
    buf[8..8 + len].copy_from_slice(bytes);
    buf[8 + len..8 + len_rounded_up].fill(0);
    Ok(8 + len_rounded_up)
}

pub fn write_u64_le(buf: &mut [u8], len: u64) -> Result<usize, io::Error> {
    if buf.len() < 8 {
        return Err(io::Error::new(
            io::ErrorKind::Other,
            format!(
                "not enough space to serialize len, need {}, have {}",
                8,
                buf.len()
            ),
        ));
    }
    buf[..8].copy_from_slice(&u64::to_le_bytes(len as u64));
    Ok(8)
}
