// License: see LICENSE file at root directory of `master` branch

//! # TTY

use std::{
    cmp::Ordering,
    fs::File,
    io,
    mem,
    sync::atomic::{self, AtomicIsize},
};

enum ProcIo {
    In,
    Out,
    Err,
}

static STDIN_IS_TTY: AtomicIsize = AtomicIsize::new(0);
static STDOUT_IS_TTY: AtomicIsize = AtomicIsize::new(0);
static STDERR_IS_TTY: AtomicIsize = AtomicIsize::new(0);

/// # Checks to see if given file descriptor is tty
#[cfg(unix)]
fn is_io_tty(io: ProcIo) -> bool {
    let store = match io {
        ProcIo::In => &STDIN_IS_TTY,
        ProcIo::Out => &STDOUT_IS_TTY,
        ProcIo::Err => &STDERR_IS_TTY,
    };
    match store.load(atomic::Ordering::Relaxed).cmp(&0) {
        Ordering::Equal => {
            use std::os::unix::{
                fs::FileTypeExt,
                io::{AsRawFd, FromRawFd},
            };
            let fd = match io {
                ProcIo::In => io::stdin().as_raw_fd(),
                ProcIo::Out => io::stdout().as_raw_fd(),
                ProcIo::Err => io::stderr().as_raw_fd(),
            };
            let file = unsafe {
                File::from_raw_fd(fd)
            };
            let is_char_device = file.metadata().map(|m| m.file_type().is_char_device()).unwrap_or(false);
            mem::forget(file);

            store.store(match is_char_device { true => 1, false => -1 }, atomic::Ordering::Relaxed);
            is_char_device
        },
        Ordering::Greater => true,
        Ordering::Less => false,
    }
}

/// # This function returns `true` on non-Unix systems
#[cfg(not(unix))]
fn is_io_tty(io: ProcIo) -> bool {
    true
}

/// # Checks to see if stdin is tty
pub fn is_stdin_tty() -> bool {
    is_io_tty(ProcIo::In)
}

/// # Checks to see if stdout is tty
pub fn is_stdout_tty() -> bool {
    is_io_tty(ProcIo::Out)
}

/// # Checks to see if stderr is tty
pub fn is_stderr_tty() -> bool {
    is_io_tty(ProcIo::Err)
}
