//! I/O abstractions.

pub use std::io::{
    Error,
    ErrorKind,
    Read,
    Result,
    Write,
    sink,
};

/// Infallible shim for writers.
///
/// GnuPG doesn't do error checking on writes, except on the status-fd
/// if `--exit-on-status-write-error` is given.  This shim swallows
/// write errors.
struct InfallibleWriter<W: Write> {
    sink: W,
    verbose: Option<&'static str>,
}

impl<W: Write> InfallibleWriter<W> {
    /// Wraps the given writer making it infallible.
    fn new(sink: W) -> Self {
        InfallibleWriter {
            sink,
            verbose: None,
        }
    }

    /// Wraps the given writer making it infallible, but write errors
    /// to stderr.
    fn fail_verbosely(sink: W, name: &'static str) -> Self {
        InfallibleWriter {
            sink,
            verbose: Some(name),
        }
    }
}

impl<W: Write> Write for InfallibleWriter<W> {
    fn write(&mut self, buf: &[u8]) -> Result<usize> {
        match self.sink.write(buf) {
            Ok(l) => Ok(l),
            Err(e) => {
                if let Some(name) = &self.verbose {
                    safe_eprintln!("gpg: [{}]: write error: {}",
                                   name, e);
                }

                Ok(buf.len())
            },
        }
    }

    fn flush(&mut self) -> Result<()> {
        self.sink.flush().or(Ok(()))
    }
}

impl<W: Write> Drop for InfallibleWriter<W> {
    fn drop(&mut self) {
        if let Err(e) = self.sink.flush() {
            if self.verbose.is_some() {
                safe_eprintln!("gpg: filter_flush failed on close: {}", e);
            }
        }
    }
}

/// Like `std::io::stdout`, but infallible.
pub fn stdout() -> impl Write {
    InfallibleWriter::new(std::io::stdout())
}

/// Like `std::io::stdout`, but infallible, and verbose on errors.
pub fn stdout_fail_verbosely() -> impl Write {
    InfallibleWriter::fail_verbosely(std::io::stdout(), "stdout")
}

/// Like `std::io::stderr`, but infallible.
pub fn stderr() -> impl Write {
    InfallibleWriter::new(std::io::stderr())
}

/// Opens the tty for reading, falling back to stdin
pub fn tty_in() -> Box<dyn Read + Send + Sync> {
    platform! {
        unix => {
            std::fs::File::options()
                .create(false)
                .read(true)
                .write(false)
                .open("/dev/tty")
                .map(|f| -> Box<dyn Read + Send + Sync> { Box::new(f) })
                .unwrap_or_else(|_| Box::new(std::io::stdin()))
        },
        windows => {
            // XXX: Open console.
            Box::new(io::stdin())
        },
    }
}

/// Opens the tty for writing, falling back to stderr.
pub fn tty_out() -> Box<dyn Write + Send + Sync> {
    platform! {
        unix => {
            std::fs::File::options()
                .create(false)
                .read(false)
                .write(true)
                .open("/dev/tty")
                .map(|f| -> Box<dyn Write + Send + Sync> { Box::new(f) })
                .unwrap_or_else(|_| Box::new(std::io::stderr()))
        },
        windows => {
            // XXX: Open console.
            Box::new(io::stderr())
        },
    }
}
