//! Plain text data.

use std::io::{Read, Write};

use flate2::{Compression, GzBuilder, bufread::GzDecoder};
use serde::{Deserialize, Serialize};

/// Plain text data.
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
pub enum Plaintext {
    /// Uncompressed data.
    Uncompressed(Vec<u8>),

    /// Compressed using DEFLATE.
    Deflated(Vec<u8>),
}

impl Plaintext {
    /// Create uncompressed plain text.
    pub fn uncompressed(data: &[u8]) -> Self {
        Self::Uncompressed(data.to_vec())
    }

    /// Compress with DEFLATE..
    pub fn deflated(data: &[u8]) -> Result<Self, PlaintextError> {
        Ok(Self::Deflated(compress(data)?))
    }

    /// Data stored within. This is the uncompressed data.
    pub fn as_bytes(&self) -> Result<Vec<u8>, PlaintextError> {
        match self {
            Self::Uncompressed(data) => Ok(data.to_vec()),
            Self::Deflated(data) => decompress(data),
        }
    }

    /// Serialize for storing on disk.
    pub fn serialize(&self) -> Result<Vec<u8>, PlaintextError> {
        postcard::to_allocvec(self).map_err(PlaintextError::PlaintextSer)
    }

    /// De-serialize.
    pub fn parse(data: &[u8]) -> Result<Self, PlaintextError> {
        postcard::from_bytes(data).map_err(PlaintextError::PlaintextDe)
    }
}

fn compress(data: &[u8]) -> Result<Vec<u8>, PlaintextError> {
    let mut out = vec![];
    {
        let mut gz = GzBuilder::new().write(&mut out, Compression::best());
        gz.write_all(data).map_err(PlaintextError::Deflate)?;
    }
    Ok(out)
}

fn decompress(data: &[u8]) -> Result<Vec<u8>, PlaintextError> {
    let mut out = vec![];
    {
        let mut gz = GzDecoder::new(data);
        gz.read_to_end(&mut out).map_err(PlaintextError::Inflate)?;
    }
    Ok(out)
}

/// An error from the `obnam::plaintext` module.
#[derive(Debug, thiserror::Error)]
pub enum PlaintextError {
    /// Can't serialize Plaintext.
    #[error("failed to serialize plain text data")]
    PlaintextSer(#[source] postcard::Error),

    /// Can't de-serialize Plaintext.
    #[error("failed to de-serialize plain text data")]
    PlaintextDe(#[source] postcard::Error),

    /// Can't compress with DEFLATE.
    #[error("failed to compress data with DEFLATE")]
    Deflate(#[source] std::io::Error),

    /// Can't de-compress with DEFLATE.
    #[error("failed to de-compress data with DEFLATE")]
    Inflate(#[source] std::io::Error),
}

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

    #[test]
    fn compression_round_trip() {
        let data = "hello, world";
        let compressed = compress(data.as_bytes()).unwrap();
        let decompressed = decompress(&compressed).unwrap();
        assert_eq!(data.as_bytes(), decompressed);
    }

    #[test]
    fn uncompressed() {
        let msg = b"hello, world";
        let p = Plaintext::uncompressed(msg);
        assert_eq!(p.as_bytes().unwrap(), msg);
    }

    #[test]
    fn compressed() {
        let msg = b"hello, world";
        let p = Plaintext::deflated(msg).unwrap();
        assert_eq!(p.as_bytes().unwrap(), msg);
    }
}
