#![feature(rustc_private, yggdrasil_os)]
use runtime::{abi as yggdrasil_abi, rt as yggdrasil_rt};
use std::{
    io,
    process::ExitCode,
    time::{Duration, Instant},
};
use yggdrasil_abi::io::MountOptions;

use clap::Parser;

#[derive(Debug, thiserror::Error)]
pub enum Error {
    #[error("{0}")]
    Io(#[from] io::Error),
    #[error("Timed out")]
    TimedOut,
}

#[derive(Parser, Debug)]
struct Args {
    #[arg(short, help = "Wait for the device to become present in N seconds")]
    wait: Option<u64>,
    #[arg(short)]
    ty: Option<String>,
    #[arg(short)]
    option: Vec<String>,
    source: String,
    target: Option<String>,
}

fn try_mount(options: &MountOptions) -> io::Result<()> {
    unsafe { yggdrasil_rt::sys::mount(options) }?;
    Ok(())
}

fn run(args: &Args, options: &MountOptions) -> Result<(), Error> {
    let start = Instant::now();
    let timeout = args.wait.map(Duration::from_secs);

    loop {
        match try_mount(options) {
            Ok(()) => return Ok(()),
            Err(e) if e.kind() == io::ErrorKind::NotFound => {
                if let Some(timeout) = timeout {
                    if start.elapsed() >= timeout {
                        return Err(Error::TimedOut);
                    } else {
                        std::thread::sleep(Duration::from_millis(100));
                    }
                } else {
                    return Err(e.into());
                }
            }
            Err(e) => {
                return Err(e.into());
            }
        }
    }
}

fn main() -> ExitCode {
    let args = Args::parse();

    let source = if args.target.is_some() {
        Some(args.source.as_str())
    } else {
        None
    };
    let target = args.target.as_deref().unwrap_or(args.source.as_str());
    let filesystem = args.ty.as_deref();
    let options = (!args.option.is_empty()).then(|| args.option.join(" "));
    let options = options.as_deref();

    // Permissions are not yet implemented, lol
    let options = MountOptions {
        source,
        filesystem,
        options,
        target,
    };

    match run(&args, &options) {
        Ok(()) => ExitCode::SUCCESS,
        Err(err) => {
            eprintln!("mount: {:?}", err);
            ExitCode::FAILURE
        }
    }
}
