mod medium;
mod parse;
mod source;

use std::fs::File;
use std::io::{BufRead, IsTerminal, Read};
use std::path::{Path, PathBuf};

pub use autark_error::Error;
pub use medium::Medium;
use source::{HttpSource, LocalSource, Source};
use tqdm::Tqdm;
pub type Result<T> = std::result::Result<T, Error>;

pub fn autoread(uri: &str) -> Result<Box<dyn BufRead + Send>> {
    let (medium, content) = parse::parse(uri)?;
    match medium {
        Medium::Http => HttpSource::read(&content),
        Medium::Local => LocalSource::read(&content),
    }
}

pub fn local_path(uri: &str) -> Result<Option<PathBuf>> {
    let (medium, content) = parse::parse(uri)?;
    Ok(match medium {
        Medium::Local => Some(PathBuf::from(content)),
        Medium::Http => None,
    })
}

pub fn autoread_to_bytes(uri: &str) -> Result<Vec<u8>> {
    let (medium, content) = parse::parse(uri)?;
    match medium {
        Medium::Local => {
            let path = Path::new(&content);
            let file = File::open(path)?;
            let total = file.metadata().ok().map(|m| m.len());
            read_with_tqdm(file, total, "reading")
        }
        Medium::Http => {
            let response = reqwest::blocking::get(&content)
                .map_err(|err| Error::Reader(format!("http error: {err}")))?
                .error_for_status()
                .map_err(|err| Error::Reader(format!("http error: {err}")))?;
            let total = response.content_length();
            read_with_tqdm(response, total, "downloading")
        }
    }
}

fn read_with_tqdm<R: Read>(mut reader: R, total: Option<u64>, desc: &str) -> Result<Vec<u8>> {
    let mut pb = Tqdm::new(std::iter::empty::<()>())
        .desc(desc)
        .unit("B")
        .unit_scale(true)
        .disable(!std::io::stderr().is_terminal());
    if let Some(total) = total.and_then(|n| usize::try_from(n).ok()) {
        pb = pb.total(total);
    }

    let mut out = Vec::new();
    let mut chunk = [0_u8; 64 * 1024];
    loop {
        let n = reader.read(&mut chunk)?;
        if n == 0 {
            break;
        }
        out.extend_from_slice(&chunk[..n]);
        pb.update(n, false);
    }

    Ok(out)
}
