use qem::{EditorTab, LoadPhase, TextPosition};
use std::env;
use std::path::PathBuf;
use std::thread;
use std::time::{Duration, Instant};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut args = env::args_os().skip(1);
    let input = args
        .next()
        .map(PathBuf::from)
        .ok_or("usage: cargo run --example editor_session --features editor -- <input> <output>")?;
    let output = args
        .next()
        .map(PathBuf::from)
        .ok_or("usage: cargo run --example editor_session --features editor -- <input> <output>")?;

    let mut tab = EditorTab::new(1);
    tab.open_file_async(input.clone())?;
    wait_for_open(&mut tab, Duration::from_secs(5))?;
    let _ = tab.try_insert(TextPosition::new(0, 0), "// generated by qem example\n")?;
    tab.save_as_async(output.clone())?;
    wait_for_save(&mut tab, Duration::from_secs(5))?;
    let status = tab.status();

    println!("opened: {}", input.display());
    println!("saved copy: {}", output.display());
    println!("bytes: {}", status.file_len());
    println!("backing: {}", status.backing().as_str());
    println!(
        "cursor: line {}, column {}",
        status.cursor().line(),
        status.cursor().column()
    );
    println!("dirty: {}", status.is_dirty());
    println!(
        "edit capability at line 1: {:?}",
        tab.edit_capability_at(TextPosition::new(0, 0))
    );

    Ok(())
}

fn wait_for_open(tab: &mut EditorTab, timeout: Duration) -> Result<(), Box<dyn std::error::Error>> {
    let deadline = Instant::now() + timeout;
    let mut last_snapshot = None;
    loop {
        if let Some(progress) = tab.loading_state() {
            let snapshot = (
                progress.completed_bytes(),
                progress.total_bytes(),
                progress.load_phase(),
            );
            if last_snapshot != Some(snapshot) {
                println!(
                    "opening: {}/{} bytes, phase: {:?}",
                    progress.completed_bytes(),
                    progress.total_bytes(),
                    progress.load_phase().unwrap_or(LoadPhase::Opening)
                );
                last_snapshot = Some(snapshot);
            }
        }
        if let Some(result) = tab.poll_load_job() {
            result?;
            return Ok(());
        }
        if !tab.is_loading() {
            return Ok(());
        }
        if Instant::now() >= deadline {
            return Err("background open timed out".into());
        }
        thread::sleep(Duration::from_millis(10));
    }
}

fn wait_for_save(tab: &mut EditorTab, timeout: Duration) -> Result<(), Box<dyn std::error::Error>> {
    let deadline = Instant::now() + timeout;
    let mut last_snapshot = None;
    loop {
        if let Some(progress) = tab.save_state() {
            let snapshot = (progress.completed_bytes(), progress.total_bytes());
            if last_snapshot != Some(snapshot) {
                println!(
                    "saving: {}/{} bytes to {}",
                    progress.completed_bytes(),
                    progress.total_bytes(),
                    progress.path().display()
                );
                last_snapshot = Some(snapshot);
            }
        }
        if let Some(result) = tab.poll_save_job() {
            result?;
            return Ok(());
        }
        if !tab.is_saving() {
            return Ok(());
        }
        if Instant::now() >= deadline {
            return Err("background save timed out".into());
        }
        thread::sleep(Duration::from_millis(10));
    }
}
