use object_rainbow_derive::{ParseAsInline, Tagged, Topological};

use crate::*;

#[derive(Tagged, ListHashes, Topological)]
struct ZtInner<T> {
    object: T,
    data: Vec<u8>,
}

/// Zero-terminated value. Used to make [`Inline`]s out of [`Object`]s which don't contain zeroes.
///
/// If you can't guarantee absence of zeroes, see [`length_prefixed::Lp`].
#[derive(Tagged, ListHashes, Topological, ParseAsInline)]
pub struct Zt<T> {
    inner: Arc<ZtInner<T>>,
}

impl<T: ToOutput> Zt<T> {
    /// Create a zero-terminated value.
    ///
    /// Pre-computes the output, errors if it contains a zero.
    pub fn new(object: T) -> crate::Result<Self> {
        let data = object.vec();
        if data.contains(&0) {
            Err(Error::Zero)
        } else {
            Ok(Self {
                inner: Arc::new(ZtInner { object, data }),
            })
        }
    }
}

impl<T> Clone for Zt<T> {
    fn clone(&self) -> Self {
        Self {
            inner: self.inner.clone(),
        }
    }
}

impl<T> Deref for Zt<T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        &self.inner.object
    }
}

impl<T: ToOutput> ToOutput for Zt<T> {
    fn to_output(&self, output: &mut dyn Output) {
        self.inner.data.to_output(output);
        output.write(&[0]);
    }
}

impl<T: ToOutput> InlineOutput for Zt<T> {}

impl<T: Parse<I>, I: ParseInput> ParseInline<I> for Zt<T> {
    fn parse_inline(input: &mut I) -> crate::Result<Self> {
        let data = input.parse_until_zero()?;
        let object = input.reparse(data)?;
        let data = data.into();
        let inner = Arc::new(ZtInner { object, data });
        Ok(Self { inner })
    }
}
