//! Extends and wraps `std::process::Child` to make it clonable. Thus eliminating the problem that
//! `libstd` does not have a cross platfrom and simple way to kill a child while also waiting for
//! it.
//!
//! ## Getting Started
//!
//! Add the dependency to your Cargo.toml:
//!
//! ```toml
//! [dependencies]
//! clonablechild = "0.1"
//! ```
//!
//! ## Example
//!
//! Use it in your program to kill a sleep process before it terminates naturally:
//!
//! ```rust,ignore
//! extern crate clonablechild;
//!
//! use clonablechild::{ChildExt, ClonableChild};
//!
//! use std::process::Command;
//! use std::thread;
//! use std::time::Duration;
//!
//! fn main() {
//!     // This command is specific to unix systems. See tests for Windows examples.
//!     let child = Command::new("sleep").arg("10").spawn().unwrap();
//!     let clonable_child = child.into_clonable();
//!
//!     kill_async(clonable_child.clone());
//!     let exit_status = clonable_child.wait().unwrap();
//!
//!     // Assert child was killed by a signal and did not exit cleanly
//!     assert_eq!(None, exit_status.code());
//!     assert!(!exit_status.success());
//! }
//!
//! fn kill_async(child: ClonableChild) {
//!     thread::spawn(move || {
//!         thread::sleep(Duration::new(1, 0));
//!         child.kill().expect("Expected to be able to kill subprocess");
//!     });
//! }
//! ```

#![deny(missing_docs)]

extern crate libc;

use std::io;
use std::process::{Child, ExitStatus, ChildStdin, ChildStdout, ChildStderr};
use std::sync::{Arc, Mutex};

#[cfg(unix)]
#[path = "unix.rs"]
mod imp;

#[cfg(windows)]
#[path = "windows.rs"]
mod imp;


/// A trait that extends `Child` with a method to convert it into a `ClonableChild`.
pub trait ChildExt {
    /// Consumes the child and wraps it in a `ClonableChild`. Results in an object with a similar
    /// API, but that can be cloned.
    fn into_clonable(self) -> ClonableChild;
}

impl ChildExt for Child {
    fn into_clonable(self) -> ClonableChild {
        ClonableChild::new(self)
    }
}

struct ChildIo {
    pub stdin: Option<ChildStdin>,
    pub stdout: Option<ChildStdout>,
    pub stderr: Option<ChildStderr>,
}

impl ChildIo {
    pub fn new(child: &mut Child) -> Self {
        ChildIo {
            stdin: child.stdin.take(),
            stdout: child.stdout.take(),
            stderr: child.stderr.take(),
        }
    }
}

/// Representation of a clonable `std::process::Child`.
#[derive(Clone)]
pub struct ClonableChild {
    id: u32,
    child: Arc<Mutex<Child>>,
    imp_child: imp::ClonableChild,
    io: Arc<Mutex<ChildIo>>,
}

impl ClonableChild {
    /// Creates a new `ClonableChild` by consuming and wrapping the given `Child`.
    pub fn new(mut child: Child) -> Self {
        let imp_child = imp::ClonableChild::new(&child);
        let io = Arc::new(Mutex::new(ChildIo::new(&mut child)));

        ClonableChild {
            id: child.id(),
            child: Arc::new(Mutex::new(child)),
            imp_child: imp_child,
            io: io,
        }
    }

    /// Forces the child to exit. This is equivalent to sending a SIGKILL on unix platforms and
    /// calling TerminateProcess on Windows.
    ///
    /// This method first tries to use the ordinary `Child::kill()`, but if that is blocked by
    /// another thread waiting for the child it will kill it itself in the same way `Child::kill()`
    /// would have done.
    pub fn kill(&self) -> io::Result<()> {
        let child = self.child.try_lock();
        match child {
            Ok(mut child) => child.kill(),
            Err(..) => self.imp_child.kill(),
        }
    }

    /// Returns the OS-assigned process identifier associated with this child. This value is
    /// obtained from `Child::id()` in `ClonableChild::new()` and then that value is returned every
    /// time.
    pub fn id(&self) -> u32 {
        self.id
    }

    /// Behaves just like `Child::wait()`, see documentation for that method.
    pub fn wait(&self) -> io::Result<ExitStatus> {
        let mut child = self.child.lock().unwrap();
        child.wait()
    }

    /// Retrieve the stdin stream from the child if one exist. Will only return something on the
    /// first call.
    pub fn stdin(&mut self) -> Option<ChildStdin> {
        self.io.lock().unwrap().stdin.take()
    }

    /// Retrieve the stdout stream from the child if one exist. Will only return something on the
    /// first call.
    pub fn stdout(&mut self) -> Option<ChildStdout> {
        self.io.lock().unwrap().stdout.take()
    }

    /// Retrieve the stderr stream from the child if one exist. Will only return something on the
    /// first call.
    pub fn stderr(&mut self) -> Option<ChildStderr> {
        self.io.lock().unwrap().stderr.take()
    }
}