use std::any::Any;
use std::path::PathBuf;
use std::process::{Command, Stdio};
use std::sync::Arc;
use arcstr::ArcStr;
use derive_builder::Builder;
#[cfg(test)]
mod tests;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ExecOpts {
    pub cpus: Option<usize>,
    pub machines: usize,
    pub logs: LogOutput,
}
impl Default for ExecOpts {
    #[inline]
    fn default() -> Self {
        Self {
            cpus: None,
            machines: 1,
            logs: LogOutput::Stdio,
        }
    }
}
#[derive(Clone, Debug, Eq, PartialEq, Default)]
pub enum LogOutput {
    #[default]
    Stdio,
    File(PathBuf),
}
pub trait Executor: Any + Send + Sync {
    fn execute(&self, command: Command, opts: ExecOpts) -> Result<(), crate::error::Error>;
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Default)]
pub struct LocalExecutor;
impl Executor for LocalExecutor {
    fn execute(&self, mut command: Command, opts: ExecOpts) -> Result<(), crate::error::Error> {
        if let LogOutput::File(ref path) = opts.logs {
            let fout = std::fs::File::create(path).map_err(Arc::new)?;
            let ferr = fout.try_clone().map_err(Arc::new)?;
            command.stdout(Stdio::from(fout)).stderr(Stdio::from(ferr));
        }
        let status = command.status().map_err(Arc::new)?;
        if !status.success() {
            return Err(crate::error::Error::CommandFailed(Arc::new(command)));
        }
        Ok(())
    }
}
#[derive(Clone, Debug, Eq, PartialEq, Builder)]
pub struct LsfExecutor {
    #[builder(setter(into), default = "::arcstr::literal!(\"bsub\")")]
    bsub: ArcStr,
    #[builder(setter(into, strip_option))]
    queue: Option<ArcStr>,
}
impl Default for LsfExecutor {
    fn default() -> Self {
        Self {
            bsub: arcstr::literal!("bsub"),
            queue: None,
        }
    }
}
impl LsfExecutor {
    #[inline]
    pub fn builder() -> LsfExecutorBuilder {
        LsfExecutorBuilder::default()
    }
    pub fn command(&self, command: &Command, opts: ExecOpts) -> Command {
        let mut submit = Command::new(&*self.bsub);
        submit.arg("-K");
        if let Some(ref queue) = self.queue {
            submit.arg("-q").arg(queue.as_str());
        }
        if let Some(cpus) = opts.cpus {
            submit.arg("-n").arg(cpus.to_string());
        }
        submit.arg(command.get_program());
        for arg in command.get_args() {
            submit.arg(arg);
        }
        if let Some(dir) = command.get_current_dir() {
            submit.current_dir(dir);
        }
        for (key, val) in command.get_envs() {
            match val {
                None => submit.env_remove(key),
                Some(val) => submit.env(key, val),
            };
        }
        submit
    }
}
impl Executor for LsfExecutor {
    fn execute(&self, command: Command, opts: ExecOpts) -> Result<(), crate::error::Error> {
        let mut submit = self.command(&command, opts);
        let status = submit.status().map_err(Arc::new)?;
        if !status.success() {
            return Err(crate::error::Error::CommandFailed(Arc::new(submit)));
        }
        Ok(())
    }
}