use super::{sync::write_outboard_from_mem, TreeNode};
use crate::{blake3, BaoTree, BlockSize, ByteNum};
use std::{fmt, io};
#[derive(Debug)]
pub struct EmptyOutboard {
    tree: BaoTree,
    root: blake3::Hash,
}
impl EmptyOutboard {
    pub fn new(tree: BaoTree, root: blake3::Hash) -> Self {
        Self { tree, root }
    }
}
impl crate::io::sync::Outboard for EmptyOutboard {
    fn root(&self) -> blake3::Hash {
        self.root
    }
    fn tree(&self) -> BaoTree {
        self.tree
    }
    fn load(&self, node: TreeNode) -> io::Result<Option<(blake3::Hash, blake3::Hash)>> {
        Ok(if self.tree.is_relevant_for_outboard(node) {
            Some((blake3::Hash::from([0; 32]), blake3::Hash::from([0; 32])))
        } else {
            None
        })
    }
}
impl crate::io::fsm::Outboard for EmptyOutboard {
    fn root(&self) -> blake3::Hash {
        self.root
    }
    fn tree(&self) -> BaoTree {
        self.tree
    }
    type LoadFuture<'a> = futures::future::Ready<io::Result<Option<(blake3::Hash, blake3::Hash)>>>;
    fn load(&mut self, node: TreeNode) -> Self::LoadFuture<'_> {
        futures::future::ok(if self.tree.is_relevant_for_outboard(node) {
            Some((blake3::Hash::from([0; 32]), blake3::Hash::from([0; 32])))
        } else {
            None
        })
    }
}
impl crate::io::sync::OutboardMut for EmptyOutboard {
    fn save(&mut self, node: TreeNode, _pair: &(blake3::Hash, blake3::Hash)) -> io::Result<()> {
        if self.tree.is_relevant_for_outboard(node) {
            Ok(())
        } else {
            Err(io::Error::new(
                io::ErrorKind::InvalidInput,
                "invalid node for this outboard",
            ))
        }
    }
}
impl crate::io::fsm::OutboardMut for EmptyOutboard {
    fn save(
        &mut self,
        node: TreeNode,
        _pair: &(blake3::Hash, blake3::Hash),
    ) -> Self::SaveFuture<'_> {
        let res = if self.tree.is_relevant_for_outboard(node) {
            Ok(())
        } else {
            Err(io::Error::new(
                io::ErrorKind::InvalidInput,
                "invalid node for this outboard",
            ))
        };
        futures::future::ready(res)
    }
    type SaveFuture<'a> = futures::future::Ready<io::Result<()>>;
    fn sync(&mut self) -> Self::SyncFuture<'_> {
        futures::future::ready(Ok(()))
    }
    type SyncFuture<'a> = futures::future::Ready<io::Result<()>>;
}
#[derive(Debug, Clone)]
pub struct PreOrderOutboard<R> {
    pub root: blake3::Hash,
    pub tree: BaoTree,
    pub data: R,
}
impl<R> PreOrderOutboard<R> {
    pub fn into_inner(self) -> R {
        self.data
    }
}
#[derive(Debug, Clone)]
pub struct PostOrderOutboard<R> {
    pub(crate) root: blake3::Hash,
    pub(crate) tree: BaoTree,
    pub(crate) data: R,
}
impl<R> PostOrderOutboard<R> {
    pub fn into_inner(self) -> R {
        self.data
    }
}
#[derive(Clone, PartialEq, Eq)]
pub struct PostOrderMemOutboard<T = Vec<u8>> {
    pub(crate) root: blake3::Hash,
    pub(crate) tree: BaoTree,
    pub data: T,
}
impl<T: AsRef<[u8]>> fmt::Debug for PostOrderMemOutboard<T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let pairs = self
            .data
            .as_ref()
            .chunks_exact(64)
            .map(|chunk| parse_hash_pair(chunk.try_into().unwrap()))
            .collect::<Vec<_>>();
        f.debug_struct("PostOrderMemOutboard")
            .field("root", &self.root)
            .field("tree", &self.tree)
            .field("data", &pairs)
            .finish()
    }
}
impl PostOrderMemOutboard {
    pub fn create(data: impl AsRef<[u8]>, block_size: BlockSize) -> Self {
        let data = data.as_ref();
        let tree = BaoTree::new(ByteNum(data.len() as u64), block_size);
        let outboard_len: usize = (tree.outboard_hash_pairs() * 64).try_into().unwrap();
        let outboard_data = vec![0; outboard_len];
        let root = blake3::Hash::from_bytes([0; 32]);
        let mut outboard = Self::new(root, tree, outboard_data).unwrap();
        let root = write_outboard_from_mem(data, &mut outboard).unwrap();
        outboard.root = root;
        outboard
    }
    pub fn into_inner_with_suffix(self) -> Vec<u8> {
        let mut res = self.data;
        res.extend_from_slice(self.tree.size.0.to_le_bytes().as_slice());
        res
    }
}
impl<T: AsRef<[u8]>> PostOrderMemOutboard<T> {
    pub fn new(
        root: blake3::Hash,
        tree: BaoTree,
        outboard_data: T,
    ) -> std::result::Result<Self, &'static str> {
        if outboard_data.as_ref().len() as u64 == tree.outboard_hash_pairs() * 64 {
            Ok(Self {
                root,
                tree,
                data: outboard_data,
            })
        } else {
            Err("invalid outboard data size")
        }
    }
    pub fn data(&self) -> &T {
        &self.data
    }
    pub fn map_data<F, U>(self, f: F) -> std::result::Result<PostOrderMemOutboard<U>, &'static str>
    where
        F: FnOnce(T) -> U,
        U: AsRef<[u8]>,
    {
        let len = self.data.as_ref().len();
        let data = f(self.data);
        if data.as_ref().len() == len {
            Ok(PostOrderMemOutboard {
                root: self.root,
                tree: self.tree,
                data,
            })
        } else {
            Err("invalid outboard data size")
        }
    }
    pub fn outboard(&self) -> &[u8] {
        self.data.as_ref()
    }
    pub fn flip(&self) -> PreOrderMemOutboard {
        flip_post(self.root, self.tree, self.data.as_ref())
    }
}
impl<T: AsRef<[u8]>> crate::io::sync::Outboard for PostOrderMemOutboard<T> {
    fn root(&self) -> blake3::Hash {
        self.root
    }
    fn tree(&self) -> BaoTree {
        self.tree
    }
    fn load(&self, node: TreeNode) -> io::Result<Option<(blake3::Hash, blake3::Hash)>> {
        Ok(load_post(&self.tree, self.data.as_ref(), node))
    }
}
impl<T: AsRef<[u8]>> crate::io::fsm::Outboard for PostOrderMemOutboard<T> {
    fn root(&self) -> blake3::Hash {
        self.root
    }
    fn tree(&self) -> BaoTree {
        self.tree
    }
    type LoadFuture<'a> = futures::future::Ready<io::Result<Option<(blake3::Hash, blake3::Hash)>>>
        where T: 'a;
    fn load(&mut self, node: TreeNode) -> Self::LoadFuture<'_> {
        futures::future::ok(load_post(&self.tree, self.data.as_ref(), node))
    }
}
impl<T: AsMut<[u8]>> crate::io::sync::OutboardMut for PostOrderMemOutboard<T> {
    fn save(&mut self, node: TreeNode, pair: &(blake3::Hash, blake3::Hash)) -> io::Result<()> {
        match self.tree.post_order_offset(node) {
            Some(offset) => {
                let offset = usize::try_from(offset.value() * 64).unwrap();
                let data = self.data.as_mut();
                data[offset..offset + 32].copy_from_slice(pair.0.as_bytes());
                data[offset + 32..offset + 64].copy_from_slice(pair.1.as_bytes());
                Ok(())
            }
            None => Err(io::Error::new(
                io::ErrorKind::InvalidInput,
                "invalid node for this outboard",
            )),
        }
    }
}
impl<T: AsMut<[u8]>> crate::io::fsm::OutboardMut for PostOrderMemOutboard<T> {
    type SaveFuture<'a> = futures::future::Ready<io::Result<()>> where T: 'a;
    fn save(
        &mut self,
        node: TreeNode,
        pair: &(blake3::Hash, blake3::Hash),
    ) -> Self::SaveFuture<'_> {
        let res = match self.tree.post_order_offset(node) {
            Some(offset) => {
                let offset = usize::try_from(offset.value() * 64).unwrap();
                let data = self.data.as_mut();
                data[offset..offset + 32].copy_from_slice(pair.0.as_bytes());
                data[offset + 32..offset + 64].copy_from_slice(pair.1.as_bytes());
                Ok(())
            }
            None => Err(io::Error::new(
                io::ErrorKind::InvalidInput,
                "invalid node for this outboard",
            )),
        };
        futures::future::ready(res)
    }
    type SyncFuture<'a> = futures::future::Ready<io::Result<()>> where T: 'a;
    fn sync(&mut self) -> Self::SyncFuture<'_> {
        futures::future::ready(Ok(()))
    }
}
fn load_raw_post_mem(tree: &BaoTree, data: &[u8], node: TreeNode) -> Option<[u8; 64]> {
    let offset = tree.post_order_offset(node)?.value();
    let offset = usize::try_from(offset * 64).unwrap();
    let slice = &data[offset..offset + 64];
    Some(slice.try_into().unwrap())
}
fn load_post(tree: &BaoTree, data: &[u8], node: TreeNode) -> Option<(blake3::Hash, blake3::Hash)> {
    load_raw_post_mem(tree, data, node).map(parse_hash_pair)
}
fn flip_post(root: blake3::Hash, tree: BaoTree, data: &[u8]) -> PreOrderMemOutboard {
    let mut out = vec![0; data.len()];
    for node in tree.post_order_nodes_iter() {
        if let Some((l, r)) = load_post(&tree, data, node) {
            let offset = tree.pre_order_offset(node).unwrap();
            let offset = (offset as usize) * 64;
            out[offset..offset + 32].copy_from_slice(l.as_bytes());
            out[offset + 32..offset + 64].copy_from_slice(r.as_bytes());
        }
    }
    PreOrderMemOutboard {
        root,
        tree,
        data: out,
    }
}
#[derive(Clone, PartialEq, Eq)]
pub struct PreOrderMemOutboard<T = Vec<u8>> {
    pub(crate) root: blake3::Hash,
    pub(crate) tree: BaoTree,
    pub data: T,
}
impl<T: AsRef<[u8]>> fmt::Debug for PreOrderMemOutboard<T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let pairs = self
            .data
            .as_ref()
            .chunks_exact(64)
            .map(|chunk| parse_hash_pair(chunk.try_into().unwrap()))
            .collect::<Vec<_>>();
        f.debug_struct("PreOrderMemOutboard")
            .field("root", &self.root)
            .field("tree", &self.tree)
            .field("data", &pairs)
            .finish()
    }
}
impl PreOrderMemOutboard {
    pub fn into_inner_with_prefix(self) -> Vec<u8> {
        let mut res = self.data;
        res.splice(0..0, self.tree.size.0.to_le_bytes());
        res
    }
    pub fn create(data: impl AsRef<[u8]>, block_size: BlockSize) -> Self {
        let data = data.as_ref();
        let tree = BaoTree::new(ByteNum(data.len() as u64), block_size);
        let outboard_len: usize = (tree.outboard_hash_pairs() * 64).try_into().unwrap();
        let outboard_data = vec![0u8; outboard_len];
        let root = blake3::Hash::from_bytes([0; 32]);
        let mut outboard = Self::new(root, tree, outboard_data).unwrap();
        let root = write_outboard_from_mem(data, &mut outboard).unwrap();
        outboard.root = root;
        outboard
    }
}
impl<T: AsRef<[u8]>> PreOrderMemOutboard<T> {
    pub fn new(
        root: blake3::Hash,
        tree: BaoTree,
        outboard_data: T,
    ) -> std::result::Result<Self, &'static str> {
        if outboard_data.as_ref().len() as u64 == tree.outboard_hash_pairs() * 64 {
            Ok(Self {
                root,
                tree,
                data: outboard_data,
            })
        } else {
            Err("invalid outboard data size")
        }
    }
    pub fn map_data<F, U>(self, f: F) -> std::result::Result<PreOrderMemOutboard<U>, &'static str>
    where
        F: FnOnce(T) -> U,
        U: AsRef<[u8]>,
    {
        let len = self.data.as_ref().len();
        let data = f(self.data);
        if data.as_ref().len() == len {
            Ok(PreOrderMemOutboard {
                root: self.root,
                tree: self.tree,
                data,
            })
        } else {
            Err("invalid outboard data size")
        }
    }
    pub fn outboard(&self) -> &[u8] {
        self.data.as_ref()
    }
    pub fn hash(&self) -> &blake3::Hash {
        &self.root
    }
    pub fn into_inner(self) -> T {
        self.data
    }
    pub fn flip(&self) -> PostOrderMemOutboard {
        flip_pre(self.root, self.tree, self.data.as_ref())
    }
}
impl<T: AsRef<[u8]>> crate::io::sync::Outboard for PreOrderMemOutboard<T> {
    fn root(&self) -> blake3::Hash {
        self.root
    }
    fn tree(&self) -> BaoTree {
        self.tree
    }
    fn load(&self, node: TreeNode) -> io::Result<Option<(blake3::Hash, blake3::Hash)>> {
        Ok(load_pre(&self.tree, self.data.as_ref(), node))
    }
}
impl<T: AsMut<[u8]>> crate::io::sync::OutboardMut for PreOrderMemOutboard<T> {
    fn save(&mut self, node: TreeNode, pair: &(blake3::Hash, blake3::Hash)) -> io::Result<()> {
        match self.tree.pre_order_offset(node) {
            Some(offset) => {
                let offset_u64 = offset * 64;
                let offset = usize::try_from(offset_u64).unwrap();
                let data = self.data.as_mut();
                data[offset..offset + 32].copy_from_slice(pair.0.as_bytes());
                data[offset + 32..offset + 64].copy_from_slice(pair.1.as_bytes());
                Ok(())
            }
            None => Err(io::Error::new(
                io::ErrorKind::InvalidInput,
                "invalid node for this outboard",
            )),
        }
    }
}
impl<T: AsRef<[u8]> + 'static> crate::io::fsm::Outboard for PreOrderMemOutboard<T> {
    fn root(&self) -> blake3::Hash {
        self.root
    }
    fn tree(&self) -> BaoTree {
        self.tree
    }
    type LoadFuture<'a> = futures::future::Ready<io::Result<Option<(blake3::Hash, blake3::Hash)>>>;
    fn load(&mut self, node: TreeNode) -> Self::LoadFuture<'_> {
        let res = load_raw_pre_mem(&self.tree, self.data.as_ref(), node).map(parse_hash_pair);
        futures::future::ok(res)
    }
}
impl<T: AsMut<[u8]>> crate::io::fsm::OutboardMut for PreOrderMemOutboard<T> {
    type SaveFuture<'a> = futures::future::Ready<io::Result<()>> where T: 'a;
    fn save(
        &mut self,
        node: TreeNode,
        pair: &(blake3::Hash, blake3::Hash),
    ) -> Self::SaveFuture<'_> {
        let res = match self.tree.pre_order_offset(node) {
            Some(offset) => {
                let offset_u64 = offset * 64;
                let offset = usize::try_from(offset_u64).unwrap();
                let data = self.data.as_mut();
                data[offset..offset + 32].copy_from_slice(pair.0.as_bytes());
                data[offset + 32..offset + 64].copy_from_slice(pair.1.as_bytes());
                Ok(())
            }
            None => Err(io::Error::new(
                io::ErrorKind::InvalidInput,
                "invalid node for this outboard",
            )),
        };
        futures::future::ready(res)
    }
    type SyncFuture<'a> = futures::future::Ready<io::Result<()>> where T: 'a;
    fn sync(&mut self) -> Self::SyncFuture<'_> {
        futures::future::ready(Ok(()))
    }
}
fn load_raw_pre_mem(tree: &BaoTree, data: &[u8], node: TreeNode) -> Option<[u8; 64]> {
    let offset = tree.pre_order_offset(node)?;
    let offset = usize::try_from(offset * 64).unwrap();
    let slice = &data[offset..offset + 64];
    Some(slice.try_into().unwrap())
}
fn load_pre(tree: &BaoTree, data: &[u8], node: TreeNode) -> Option<(blake3::Hash, blake3::Hash)> {
    load_raw_pre_mem(tree, data, node).map(parse_hash_pair)
}
fn flip_pre(root: blake3::Hash, tree: BaoTree, data: &[u8]) -> PostOrderMemOutboard {
    let mut out = vec![0; data.len()];
    for node in tree.post_order_nodes_iter() {
        if let Some((l, r)) = load_pre(&tree, data, node) {
            let offset = tree.post_order_offset(node).unwrap().value();
            let offset = usize::try_from(offset * 64).unwrap();
            out[offset..offset + 32].copy_from_slice(l.as_bytes());
            out[offset + 32..offset + 64].copy_from_slice(r.as_bytes());
        }
    }
    PostOrderMemOutboard {
        root,
        tree,
        data: out,
    }
}
pub(crate) fn parse_hash_pair(buf: [u8; 64]) -> (blake3::Hash, blake3::Hash) {
    let l_hash = blake3::Hash::from(<[u8; 32]>::try_from(&buf[..32]).unwrap());
    let r_hash = blake3::Hash::from(<[u8; 32]>::try_from(&buf[32..]).unwrap());
    (l_hash, r_hash)
}