// SPDX-License-Identifier: AGPL-3.0-or-later

//! This module contains the [Accessor] type.

use positioned_io::{ReadAt, Size, WriteAt};
use std::io::{self, Read, Seek, SeekFrom, Write};

use crate::{
    sectored_buf::SectoredBuf, BlockMeta, Cursor, Decompose, FlushMeta, MetaAccess, Positioned,
    ReadDual, Result, SecretStream, Sectored, Split, TryCompose, TrySeek, WriteDual,
    ZeroExtendable,
};

pub use private::Accessor;

mod private {
    use super::*;

    /// Wraps a position independent stream of cipher text and exposes the positioned stream of
    /// validated plaintext data it contains.
    pub struct Accessor<T: Size> {
        inner: SectoredBuf<SecretStream<Cursor<T>>>,
    }

    impl<T: ReadAt + Sectored + AsRef<BlockMeta> + Size> Accessor<T> {
        /// Creates a new [Accessor] by wrapping the given type.
        pub fn new(inner: T) -> Result<Accessor<T>> {
            let meta: &BlockMeta = inner.as_ref();
            let key = meta.body.block_key()?.clone();
            let inner = SecretStream::new(key).try_compose(Cursor::new(inner))?;
            Ok(Self {
                inner: SectoredBuf::new(inner)?,
            })
        }
    }

    impl<T: Size> Accessor<T> {
        /// Returns a reference to the inner type.
        pub fn get_ref(&self) -> &T {
            self.inner.get_ref().get_ref().get_ref()
        }

        /// Returns a mutable reference to the inner type.
        pub fn get_mut(&mut self) -> &mut T {
            self.inner.get_mut().get_mut().get_mut()
        }
    }

    impl<T: Size + ReadAt + AsRef<BlockMeta>> Accessor<T> {
        /// Returns a reference to the internal buffer. The returned slice starts at the given
        /// offset in the stream and is no longer than the given size, though it may be shorter in
        /// the case where the stream's total length is shorter than `offset + size`, or if there
        /// are fewer than `size` bytes buffered after `offset`.
        pub fn get_buf(&self, offset: u64, size: u64) -> Result<&[u8]> {
            self.inner.get_buf(offset, size)
        }
    }

    impl<T: Size + ReadAt + WriteAt + MetaAccess> ZeroExtendable for Accessor<T> {
        fn zero_extend(&mut self, len: u64) -> io::Result<()> {
            self.inner.zero_extend(len)
        }
    }

    impl<T: ReadAt + AsRef<BlockMeta> + Size> Read for Accessor<T> {
        fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
            self.inner.read(buf)
        }
    }

    impl<T: ReadAt + WriteAt + MetaAccess> Write for Accessor<T> {
        fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
            self.inner.write(buf)
        }

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

    impl<T: ReadAt + WriteAt + MetaAccess> Seek for Accessor<T> {
        fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
            self.inner.seek(pos)
        }
    }

    impl<U, T: AsRef<U> + Size> AsRef<U> for Accessor<T> {
        fn as_ref(&self) -> &U {
            self.inner.get_ref().as_ref()
        }
    }

    impl<U, T: AsMut<U> + Size> AsMut<U> for Accessor<T> {
        fn as_mut(&mut self) -> &mut U {
            self.inner.get_mut().as_mut()
        }
    }

    impl<T: Size> Decompose<T> for Accessor<T> {
        fn into_inner(self) -> T {
            self.inner.into_inner().into_inner().into_inner()
        }
    }

    impl<T: FlushMeta + Size> FlushMeta for Accessor<T> {
        fn flush_meta(&mut self) -> Result<()> {
            self.get_mut().flush_meta()
        }
    }

    impl<T: Size> Sectored for Accessor<T> {
        fn sector_sz(&self) -> usize {
            self.inner.sector_sz()
        }
    }

    impl<T: Size> Size for Accessor<T> {
        fn size(&self) -> std::io::Result<Option<u64>> {
            self.inner.get_ref().size()
        }
    }

    impl<T: ReadAt + WriteAt + MetaAccess> WriteDual for Accessor<T> {
        fn write_from<R: Read>(&mut self, read: R, count: usize) -> std::io::Result<usize> {
            self.inner.write_from(read, count)
        }
    }

    impl<T: ReadAt + AsRef<BlockMeta> + Size> ReadDual for Accessor<T> {
        fn read_into<W: Write>(&mut self, write: W, count: usize) -> std::io::Result<usize> {
            self.inner.read_into(write, count)
        }
    }

    impl<T: Size> Positioned for Accessor<T> {
        fn pos(&self) -> usize {
            self.inner.pos()
        }
    }

    impl<T: ReadAt + AsRef<BlockMeta> + Size> TrySeek for Accessor<T> {
        fn try_seek(&mut self, seek_from: SeekFrom) -> std::io::Result<()> {
            self.inner.try_seek(seek_from)
        }
    }

    impl<T: Size> Split<Accessor<&'static [u8]>, T> for Accessor<T> {
        fn split(self) -> (Accessor<&'static [u8]>, T) {
            let (sectored_buf, inner) = self.inner.split();
            let (secret_stream, inner) = inner.split();
            let (cursor, inner) = inner.split();
            let new_inner =
                SectoredBuf::combine(sectored_buf, SecretStream::combine(secret_stream, cursor));
            (Accessor { inner: new_inner }, inner)
        }

        fn combine(left: Accessor<&'static [u8]>, right: T) -> Self {
            let (sectored_buf, inner) = left.inner.split();
            let (secret_stream, inner) = inner.split();
            let (cursor, ..) = inner.split();
            let new_inner = SectoredBuf::combine(
                sectored_buf,
                SecretStream::combine(secret_stream, Cursor::combine(cursor, right)),
            );
            Accessor { inner: new_inner }
        }
    }
}

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

    #[test]
    fn can_wrap_block_ref() {
        let block = make_block_with().into_inner().into_inner().into_inner();
        let mut accessor = Accessor::new(block).expect("failed to wrap block");
        const EXPECTED: &[u8] = &[1u8; 8];

        accessor.write_all(EXPECTED).expect("write failed");
        accessor.flush().expect("flush failed");
        accessor.rewind().expect("rewind failed");
        let block = accessor.into_inner();
        let mut wrapped = Accessor::new(&block).expect("failed to wrap block reference");
        let mut actual = [0u8; EXPECTED.len()];
        wrapped.read(&mut actual).expect("read failed");

        assert_eq!(EXPECTED, actual);
    }
}