use std::time::Instant;
use bytes::Buf;
use std::env;
use number_prefix::NumberPrefix;
use std::io::Write;
use reqwest::Url;

fn draw_bar(status: &str, done: usize, total: usize, is_bytes: bool) {
    let mut bar = String::with_capacity(30);

    if total != 0 {
        for _ in 0..((done as f32 / total as f32) * 25. - 1.) as usize {
            bar.push('=');
        }
        bar.push('>');
    }

    fn display(amt: usize, is_bytes: bool) -> String {
        match is_bytes {
            true => match NumberPrefix::decimal(amt as f64) {
                NumberPrefix::Standalone(bytes) => {
                    format!("{} B", bytes)
                }
                NumberPrefix::Prefixed(prefix, n) => {
                    format!("{:.1} {}B", n, prefix)
                }
            },
            false => amt.to_string(),
        }
    }

    let done_display = display(done, is_bytes);
    let total_display = display(total, is_bytes);

    if total == 0 {
        print!("\r{} ({})", status, done_display);
    } else {
        print!("\r{} [{:25}] {} / {}", status, bar, done_display, total_display);
    }

    let _ = std::io::stdout().flush();

}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let uri = env::args()
        .skip(1)
        .next()
        .map(|s| s.parse::<reqwest::Url>())
        .ok_or(std::io::Error::new(std::io::ErrorKind::NotFound, "Need a URL!"))??;

    let mut response = reqwest::get(uri).await?;

    let size = response.content_length().unwrap_or(0) as usize;
    let mut downloaded = 0usize;

    let mut bytes =
        Vec::with_capacity(size);


    let mut bar_update_timer = Instant::now();
    while let Some(chunk) = response.chunk().await? {
        downloaded += chunk.len();
        let mut reader = chunk.reader();
        std::io::copy(&mut reader, &mut bytes).unwrap();

        if bar_update_timer.elapsed().as_millis() >= 250 {
            draw_bar("Downloading", downloaded, size, true);
            bar_update_timer = Instant::now();
        }
    }
    draw_bar("Downloading", downloaded, size, true);

    let mut archive =
        zip::ZipArchive::new(std::io::Cursor::new(bytes.as_slice()))?;

    let files = archive.len();
    let mut extracted_files = 0usize;

    for i in 0..archive.len() {
        let mut zipfile = archive.by_index(i)?;

        let mangled_name = zipfile.mangled_name();
        let path = zipfile.enclosed_name().unwrap_or(mangled_name.as_path());

        if zipfile.is_dir() {
            std::fs::create_dir_all(path);
        } else {
            let mut file = std::fs::File::create(path)?;
            std::io::copy(&mut zipfile, &mut file);
        }

        extracted_files += 1;

        if bar_update_timer.elapsed().as_millis() >= 250 {
            draw_bar("Extracting", extracted_files, files, false);
            bar_update_timer = Instant::now();
        }
    }

    println!();

    Ok(())
}