extern crate libc;

use std::time::Duration;

#[cfg(target_os = "linux")]
fn clock_nanosleep(clk_id: libc::clockid_t,
                   flags: libc::c_int,
                   rqtp: *const libc::timespec,
                   rmtp: *mut libc::timespec)
                   -> libc::c_int {
    unsafe { libc::clock_nanosleep(clk_id, flags, rqtp as *const _, rmtp as *mut _) }
}

#[cfg(target_os = "macos")]
fn nanosleep(rqtp: *const libc::timespec, rmtp: *mut libc::timespec) -> libc::c_int {
    unsafe { libc::nanosleep(rqtp as *const _, rmtp as *mut _) }
}

/// Sleeps for the given duration.
///
/// Examples:
///
/// ```rust
/// extern crate sleep;
/// use std::time::Duration;
///
/// // sleep 10 ms
/// sleep::sleep(Duration::from_millis(10));
/// ```
///
/// Uses `clock_nanosleep` on linux and `nanosleep` on darwin.
pub fn sleep(duration: Duration) -> Result<Duration, libc::c_int> {
    let ts = duration_to_timespec(duration);
    let mut remain = libc::timespec {
        tv_sec: 0,
        tv_nsec: 0,
    };

    #[cfg(target_os = "linux")]
    let ret = clock_nanosleep(libc::CLOCK_MONOTONIC, 0, &ts, &mut remain);

    #[cfg(target_os = "macos")]
    let ret = nanosleep(&ts, &mut remain);

    if ret == 0 {
        Ok(timespec_to_duration(remain))
    } else {
        Err(ret)
    }
}

fn duration_to_timespec(duration: Duration) -> libc::timespec {
    libc::timespec {
        tv_sec: duration.as_secs() as libc::time_t,
        tv_nsec: duration.subsec_nanos() as libc::c_long,
    }
}

fn timespec_to_duration(timespec: libc::timespec) -> Duration {
    Duration::new(timespec.tv_sec as u64, timespec.tv_nsec as u32)
}

/// Set scheduler policy and priority for process with given pid.
///
/// If `pid` equals zero, the policy of the calling process will be set.
///
/// # Examples
///
/// ```rust
/// extern crate libc;
/// extern crate sleep;
///
/// // set *round-robin* policy (default) and priority 99 (realtime) for own process
/// sleep::set_scheduler(0, libc::SCHED_RR, 99);
/// ```
#[cfg(target_os = "linux")]
pub fn set_scheduler(pid: libc::pid_t, policy: libc::c_int, priority: libc::c_int) -> libc::c_int {
    let param = libc::sched_param { sched_priority: priority };
    unsafe { libc::sched_setscheduler(pid, policy, &param) }
}

/// Set scheduler *round-robin* policy (default) and priority for own process.
///
/// # Examples
///
/// ```rust
/// extern crate sleep;
///
/// // set *round-robin* policy (default) and priority 99 (realtime)
/// sleep::set_priority(99);
/// ```
#[cfg(target_os = "linux")]
pub fn set_priority(priority: i32) -> libc::c_int {
    set_scheduler(0, libc::SCHED_RR, priority)
}
