//! Stackable writers.

#[cfg(feature = "compression-bzip2")]
mod writer_bzip2;
#[cfg(feature = "compression-bzip2")]
pub use self::writer_bzip2::BZ;
#[cfg(feature = "compression-deflate")]
mod writer_deflate;
#[cfg(feature = "compression-deflate")]
pub use self::writer_deflate::{ZIP, ZLIB};

use std::fmt;
use std::io;

use crypto::{aead, symmetric};
use constants::{
    AEADAlgorithm,
    SymmetricAlgorithm,
};
use {
    Result,
    crypto::SessionKey,
};

/// A stack of writers.
#[derive(Debug)]
pub struct Stack<'a, C>(BoxStack<'a, C>);

impl<'a, C> Stack<'a, C> {
    pub(crate) fn from(bs: BoxStack<'a, C>) -> Self {
        Stack(bs)
    }

    pub(crate) fn as_ref(&self) -> &BoxStack<'a, C> {
        &self.0
    }

    pub(crate) fn as_mut(&mut self) -> &mut BoxStack<'a, C> {
        &mut self.0
    }

    /// Finalizes this writer, returning the underlying writer.
    pub fn finalize_one(self) -> Result<Option<Stack<'a, C>>> {
        Ok(self.0.into_inner()?.map(|bs| Self::from(bs)))
    }

    /// Finalizes all writers, tearing down the whole stack.
    pub fn finalize(self) -> Result<()> {
        let mut stack = self;
        while let Some(s) = stack.finalize_one()? {
            stack = s;
        }
        Ok(())
    }
}

impl<'a, C> io::Write for Stack<'a, C> {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        self.0.write(buf)
    }

    fn flush(&mut self) -> io::Result<()> {
        self.0.flush()
    }
}

impl<'a, C> From<Stack<'a, C>> for BoxStack<'a, C> {
    fn from(s: Stack<'a, C>) -> Self {
        s.0
    }
}

pub(crate) type BoxStack<'a, C> = Box<'a + Stackable<'a, C>>;

/// Makes a writer stackable and provides convenience functions.
pub(crate) trait Stackable<'a, C> : io::Write + fmt::Debug {
    /// Recovers the inner stackable.
    ///
    /// This can fail if the current `Stackable` has buffered data
    /// that hasn't been written to the underlying `Stackable`.
    fn into_inner(self: Box<Self>) -> Result<Option<BoxStack<'a, C>>>;

    /// Pops the stackable from the stack, detaching it.
    ///
    /// Returns the detached stack.
    ///
    /// Note: Only the Signer implements this interface.
    fn pop(&mut self) -> Result<Option<BoxStack<'a, C>>>;

    /// Sets the inner stackable.
    ///
    /// Note: Only the Signer implements this interface.
    fn mount(&mut self, new: BoxStack<'a, C>);

    /// Returns a mutable reference to the inner `Writer`, if
    /// any.
    ///
    /// It is a very bad idea to write any data from the inner
    /// `Writer`, but it can sometimes be useful to get the cookie.
    fn inner_mut(&mut self) -> Option<&mut Stackable<'a, C>>;

    /// Returns a reference to the inner `Writer`.
    fn inner_ref(&self) -> Option<&Stackable<'a, C>>;

    /// Sets the cookie and returns the old value.
    fn cookie_set(&mut self, cookie: C) -> C;

    /// Returns a reference to the cookie.
    fn cookie_ref(&self) -> &C;

    /// Returns a mutable reference to the cookie.
    fn cookie_mut(&mut self) -> &mut C;

    /// Writes a byte.
    fn write_u8(&mut self, b: u8) -> io::Result<()> {
        let b : [u8; 1] = [b; 1];
        self.write_all(&b[..])
    }

    /// Writes a big endian `u16`.
    fn write_be_u16(&mut self, n: u16) -> io::Result<()> {
        let b : [u8; 2] = [ ((n >> 8) & 0xFF) as u8, (n & 0xFF) as u8 ];
        self.write_all(&b[..])
    }

    /// Writes a big endian `u32`.
    fn write_be_u32(&mut self, n: u32) -> io::Result<()> {
        let b : [u8; 4] = [ (n >> 24) as u8, ((n >> 16) & 0xFF) as u8,
                             ((n >> 8) & 0xFF) as u8, (n & 0xFF) as u8 ];
        self.write_all(&b[..])
    }
}

/// Make a `Box<Stackable>` look like a Stackable.
impl <'a, C> Stackable<'a, C> for BoxStack<'a, C> {
    fn into_inner(self: Box<Self>) -> Result<Option<BoxStack<'a, C>>> {
        (*self).into_inner()
    }
    /// Recovers the inner stackable.
    fn pop(&mut self) -> Result<Option<BoxStack<'a, C>>> {
        self.as_mut().pop()
    }
    /// Sets the inner stackable.
    fn mount(&mut self, new: BoxStack<'a, C>) {
        self.as_mut().mount(new);
    }
    fn inner_mut(&mut self) -> Option<&mut Stackable<'a, C>> {
        self.as_mut().inner_mut()
    }
    fn inner_ref(&self) -> Option<&Stackable<'a, C>> {
        self.as_ref().inner_ref()
    }
    fn cookie_set(&mut self, cookie: C) -> C {
        self.as_mut().cookie_set(cookie)
    }
    fn cookie_ref(&self) -> &C {
        self.as_ref().cookie_ref()
    }
    fn cookie_mut(&mut self) -> &mut C {
        self.as_mut().cookie_mut()
    }
}

/// Maps a function over the stack of writers.
#[allow(dead_code)]
pub(crate) fn map<C, F>(head: &Stackable<C>, mut fun: F)
    where F: FnMut(&Stackable<C>) -> bool {
    let mut ow = Some(head);
    while let Some(w) = ow {
        if ! fun(w) {
            break;
        }
        ow = w.inner_ref()
    }
}

/// Maps a function over the stack of mutable writers.
#[allow(dead_code)]
pub(crate) fn map_mut<C, F>(head: &mut Stackable<C>, mut fun: F)
    where F: FnMut(&mut Stackable<C>) -> bool {
    let mut ow = Some(head);
    while let Some(w) = ow {
        if ! fun(w) {
            break;
        }
        ow = w.inner_mut()
    }
}

/// Dumps the writer stack.
#[allow(dead_code)]
pub(crate) fn dump<C>(head: &Stackable<C>) {
    let mut depth = 0;
    map(head, |w| {
        eprintln!("{}: {:?}", depth, w);
        depth += 1;
        true
    });
}

/// The identity writer just relays anything written.
pub struct Identity<'a, C> {
    inner: Option<BoxStack<'a, C>>,
    cookie: C,
}

impl<'a, C: 'a> Identity<'a, C> {
    /// Makes an identity writer.
    pub fn new(inner: Stack<'a, C>, cookie: C)
                  -> Stack<'a, C> {
        Stack::from(Box::new(Self{inner: Some(inner.into()), cookie: cookie}))
    }
}

impl<'a, C> fmt::Debug for Identity<'a, C> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.debug_struct("Identity")
            .field("inner", &self.inner)
            .finish()
    }
}

impl<'a, C> io::Write for Identity<'a, C> {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        let writer = self.inner.as_mut()
            .ok_or_else(|| io::Error::new(io::ErrorKind::BrokenPipe,
                                          "Writer is finalized."))?;
        writer.write(buf)
    }

    fn flush(&mut self) -> io::Result<()> {
        let writer = self.inner.as_mut()
            .ok_or_else(|| io::Error::new(io::ErrorKind::BrokenPipe,
                                          "Writer is finalized."))?;
        writer.flush()
    }
}

impl<'a, C> Stackable<'a, C> for Identity<'a, C> {
    /// Recovers the inner stackable.
    fn into_inner(self: Box<Self>) -> Result<Option<BoxStack<'a, C>>> {
        Ok(self.inner)
    }
    /// Recovers the inner stackable.
    fn pop(&mut self) -> Result<Option<BoxStack<'a, C>>> {
        Ok(self.inner.take())
    }
    /// Sets the inner stackable.
    fn mount(&mut self, new: BoxStack<'a, C>) {
        self.inner = Some(new);
    }
    fn inner_ref(&self) -> Option<&Stackable<'a, C>> {
        if let Some(ref i) = self.inner {
            Some(i)
        } else {
            None
        }
    }
    fn inner_mut(&mut self) -> Option<&mut Stackable<'a, C>> {
        if let Some(ref mut i) = self.inner {
            Some(i)
        } else {
            None
        }
    }
    fn cookie_set(&mut self, cookie: C) -> C {
        ::std::mem::replace(&mut self.cookie, cookie)
    }
    fn cookie_ref(&self) -> &C {
        &self.cookie
    }
    fn cookie_mut(&mut self) -> &mut C {
        &mut self.cookie
    }
}

/// Generic writer wrapping `io::Write`.
pub struct Generic<W: io::Write, C> {
    inner: W,
    cookie: C,
}

impl<'a, W: 'a + io::Write, C: 'a> Generic<W, C> {
    /// Wraps an `io::Write`r.
    pub fn new(inner: W, cookie: C) -> Stack<'a, C> {
        Stack::from(Box::new(Self::new_unboxed(inner.into(), cookie)))
    }

    fn new_unboxed(inner: W, cookie: C) -> Self {
        Generic {
            inner: inner,
            cookie: cookie,
        }
    }
}

impl<W: io::Write, C> fmt::Debug for Generic<W, C> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.debug_struct("writer::Generic")
            .finish()
    }
}

impl<W: io::Write, C> io::Write for Generic<W, C> {
    fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
        self.inner.write(bytes)
    }

    fn flush(&mut self) -> io::Result<()> {
        self.inner.flush()
    }
}

impl<'a, W: io::Write, C> Stackable<'a, C> for Generic<W, C> {
    /// Recovers the inner stackable.
    fn into_inner(self: Box<Self>) -> Result<Option<BoxStack<'a, C>>> {
        Ok(None)
    }
    /// Recovers the inner stackable.
    fn pop(&mut self) -> Result<Option<BoxStack<'a, C>>> {
        Ok(None)
    }
    /// Sets the inner stackable.
    fn mount(&mut self, _new: BoxStack<'a, C>) {
    }
    fn inner_mut(&mut self) -> Option<&mut Stackable<'a, C>> {
        None
    }
    fn inner_ref(&self) -> Option<&Stackable<'a, C>> {
        None
    }
    fn cookie_set(&mut self, cookie: C) -> C {
        ::std::mem::replace(&mut self.cookie, cookie)
    }
    fn cookie_ref(&self) -> &C {
        &self.cookie
    }
    fn cookie_mut(&mut self) -> &mut C {
        &mut self.cookie
    }
}


/// Encrypting writer.
pub struct Encryptor<'a, C: 'a> {
    inner: Generic<symmetric::Encryptor<BoxStack<'a, C>>, C>,
}

impl<'a, C: 'a> Encryptor<'a, C> {
    /// Makes an encrypting writer.
    pub fn new(inner: Stack<'a, C>, cookie: C, algo: SymmetricAlgorithm,
               key: &[u8])
        -> Result<Stack<'a, C>>
    {
        Ok(Stack::from(Box::new(Encryptor {
            inner: Generic::new_unboxed(
                symmetric::Encryptor::new(algo, key, inner.into())?,
                cookie),
        })))
    }
}

impl<'a, C: 'a> fmt::Debug for Encryptor<'a, C> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.debug_struct("writer::Encryptor")
            .field("inner", &self.inner)
            .finish()
    }
}

impl<'a, C: 'a> io::Write for Encryptor<'a, C> {
    fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
        self.inner.write(bytes)
    }

    fn flush(&mut self) -> io::Result<()> {
        self.inner.flush()
    }
}

impl<'a, C: 'a> Stackable<'a, C> for Encryptor<'a, C> {
    fn into_inner(mut self: Box<Self>) -> Result<Option<BoxStack<'a, C>>> {
        let inner = self.inner.inner.finish()?;
        Ok(Some(inner))
    }
    fn pop(&mut self) -> Result<Option<BoxStack<'a, C>>> {
        unreachable!("Only implemented by Signer")
    }
    fn mount(&mut self, _new: BoxStack<'a, C>) {
        unreachable!("Only implemented by Signer")
    }
    fn inner_mut(&mut self) -> Option<&mut Stackable<'a, C>> {
        self.inner.inner_mut()
    }
    fn inner_ref(&self) -> Option<&Stackable<'a, C>> {
        self.inner.inner_ref()
    }
    fn cookie_set(&mut self, cookie: C) -> C {
        self.inner.cookie_set(cookie)
    }
    fn cookie_ref(&self) -> &C {
        self.inner.cookie_ref()
    }
    fn cookie_mut(&mut self) -> &mut C {
        self.inner.cookie_mut()
    }
}


/// AEAD encrypting writer.
pub struct AEADEncryptor<'a, C: 'a> {
    inner: Generic<aead::Encryptor<BoxStack<'a, C>>, C>,
}

impl<'a, C: 'a> AEADEncryptor<'a, C> {
    /// Makes an encrypting writer.
    pub fn new(inner: Stack<'a, C>, cookie: C,
               cipher: SymmetricAlgorithm, aead: AEADAlgorithm,
               chunk_size: usize, iv: &[u8], key: &SessionKey)
        -> Result<Stack<'a, C>>
    {
        Ok(Stack::from(Box::new(AEADEncryptor {
            inner: Generic::new_unboxed(
                aead::Encryptor::new(1, cipher, aead, chunk_size, iv, key,
                                     inner.into())?,
                cookie),
        })))
    }
}

impl<'a, C: 'a> fmt::Debug for AEADEncryptor<'a, C> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.debug_struct("writer::AEADEncryptor")
            .field("inner", &self.inner)
            .finish()
    }
}

impl<'a, C: 'a> io::Write for AEADEncryptor<'a, C> {
    fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
        self.inner.write(bytes)
    }

    fn flush(&mut self) -> io::Result<()> {
        self.inner.flush()
    }
}

impl<'a, C: 'a> Stackable<'a, C> for AEADEncryptor<'a, C> {
    fn into_inner(mut self: Box<Self>) -> Result<Option<BoxStack<'a, C>>> {
        let inner = self.inner.inner.finish()?;
        Ok(Some(inner))
    }
    fn pop(&mut self) -> Result<Option<BoxStack<'a, C>>> {
        unreachable!("Only implemented by Signer")
    }
    fn mount(&mut self, _new: BoxStack<'a, C>) {
        unreachable!("Only implemented by Signer")
    }
    fn inner_mut(&mut self) -> Option<&mut Stackable<'a, C>> {
        self.inner.inner_mut()
    }
    fn inner_ref(&self) -> Option<&Stackable<'a, C>> {
        self.inner.inner_ref()
    }
    fn cookie_set(&mut self, cookie: C) -> C {
        self.inner.cookie_set(cookie)
    }
    fn cookie_ref(&self) -> &C {
        self.inner.cookie_ref()
    }
    fn cookie_mut(&mut self) -> &mut C {
        self.inner.cookie_mut()
    }
}

#[cfg(test)]
mod test {
    use std::io::Write;
    use super::*;

    #[derive(Debug)]
    struct Cookie {
        state: &'static str,
    }

    #[test]
    fn generic_writer() {
        let mut inner = Vec::new();
        {
            let mut w = Generic::new(&mut inner, Cookie { state: "happy" });
            assert_eq!(w.as_ref().cookie_ref().state, "happy");
            dump(w.as_ref());

            w.as_mut().cookie_mut().state = "sad";
            assert_eq!(w.as_ref().cookie_ref().state, "sad");

            w.write_all(b"be happy").unwrap();
            let mut count = 0;
            map_mut(w.as_mut(), |g| {
                let new = Cookie { state: "happy" };
                let old = g.cookie_set(new);
                assert_eq!(old.state, "sad");
                count += 1;
                true
            });
            assert_eq!(count, 1);
            assert_eq!(w.as_ref().cookie_ref().state, "happy");
        }
        assert_eq!(&inner, b"be happy");
    }

    #[test]
    fn stack() {
        let mut inner = Vec::new();
        {
            let w = Generic::new(&mut inner, Cookie { state: "happy" });
            dump(w.as_ref());

            let w = Identity::new(w, Cookie { state: "happy" });
            dump(w.as_ref());

            let mut count = 0;
            map(w.as_ref(), |g| {
                assert_eq!(g.cookie_ref().state, "happy");
                count += 1;
                true
            });
            assert_eq!(count, 2);
        }
    }

}
