use std::path::PathBuf;
use bcur::{Encoder, UrType, encode};
use clap::Args;
use crate::error::{Error, Result};
use crate::io_util::read_bytes;
use crate::qr::{animate_encoder, show_static, tty_max_chars};
#[derive(Debug, Args)]
pub(crate) struct EncodeArgs {
#[arg(long = "type")]
ur_type: String,
#[arg(long)]
max_fragment: Option<usize>,
#[arg(long)]
max_chars: Option<usize>,
#[arg(long)]
hex: bool,
#[arg(long)]
qr: bool,
#[arg(long)]
animate: bool,
#[arg(long)]
count: Option<u32>,
#[arg(long, default_value_t = 200)]
interval_ms: u64,
input: Option<PathBuf>,
}
pub(crate) fn run(args: &EncodeArgs) -> Result<()> {
let max_chars = resolve_max_chars(args)?;
let data = read_bytes(args.input.as_deref(), args.hex)?;
let ur_type = UrType::new(&args.ur_type)?;
let single = encode(&data, &ur_type);
let use_single = !args.animate && single.len() <= max_chars;
if use_single {
if args.qr {
show_static(&single)?;
} else {
println!("{single}");
}
return Ok(());
}
if data.is_empty() {
return Err(Error::msg(
"empty payload cannot be fountain-encoded; omit --animate",
));
}
let fragment_len = match args.max_fragment {
Some(n) => n,
None => fit_fragment_len(&data, &ur_type, max_chars)?,
};
if fragment_len == 0 {
return Err(Error::msg("--max-fragment must be greater than zero"));
}
let mut encoder = Encoder::new(&data, fragment_len, &ur_type)?;
if args.qr {
let first = encoder.next_part()?;
if first.len() > max_chars {
return Err(Error::msg(format!(
"first part is {} chars (limit {max_chars}); lower --max-fragment or raise --max-chars",
first.len()
)));
}
animate_encoder(&mut encoder, Some(first), args.interval_ms)?;
} else {
let k = encoder.fragment_count();
let n = args.count.unwrap_or_else(|| k.saturating_mul(3).max(20));
for _ in 0..n {
println!("{}", encoder.next_part()?);
}
}
Ok(())
}
fn resolve_max_chars(args: &EncodeArgs) -> Result<usize> {
if let Some(n) = args.max_chars {
if n == 0 {
return Err(Error::msg("--max-chars must be greater than zero"));
}
return Ok(n);
}
if args.qr {
Ok(tty_max_chars().unwrap_or(80))
} else {
Ok(400)
}
}
const PART_LEN_SLACK: usize = 12;
fn fit_fragment_len(data: &[u8], ur_type: &UrType, max_chars: usize) -> Result<usize> {
let mut lo = 1_usize;
let mut hi = data.len();
let mut best = None;
while lo <= hi {
let mid = lo + (hi - lo) / 2;
match measure_part(data, ur_type, mid) {
Ok(len) if len.saturating_add(PART_LEN_SLACK) <= max_chars => {
best = Some(mid);
lo = mid.saturating_add(1);
}
Ok(_) | Err(Error::Bcur(_)) => {
hi = mid.saturating_sub(1);
}
Err(e) => return Err(e),
}
}
best.ok_or_else(|| {
Error::msg(format!(
"cannot fit a fountain part into {max_chars} chars; raise --max-chars"
))
})
}
fn measure_part(data: &[u8], ur_type: &UrType, fragment_len: usize) -> Result<usize> {
let mut encoder = Encoder::new(data, fragment_len, ur_type)?;
Ok(encoder.next_part()?.len())
}