use std::sync::Arc;

use imbl::{HashMap, OrdSet};
use object_rainbow::{
    Address, ByteNode, DiffHashes, FailFuture, Hash, ObjectHashes, Resolve, ToOutput,
};

struct EntryInner {
    topology: Vec<Hash>,
    data: Vec<u8>,
}

#[derive(Clone)]
struct Entry {
    inner: Arc<EntryInner>,
    referenced_by: OrdSet<Hash>,
}

#[derive(Clone, Default)]
pub struct LocalMap {
    map: HashMap<Hash, Entry>,
}

impl LocalMap {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn insert(
        &mut self,
        hash: Hash,
        tags_hash: Hash,
        mangle_hash: Hash,
        topology: Vec<Hash>,
        data: Vec<u8>,
    ) -> object_rainbow::Result<()> {
        if self.contains(hash) {
            return Ok(());
        }
        let mut map = self.map.clone();
        let expected = ObjectHashes {
            diff: DiffHashes {
                tags: tags_hash,
                topology: topology.data_hash(),
                mangle: mangle_hash,
            }
            .data_hash(),
            data: data.data_hash(),
        }
        .data_hash();
        if expected != hash {
            return Err(object_rainbow::Error::FullHashMismatch);
        }
        for referenced in &topology {
            map.get_mut(referenced)
                .ok_or(object_rainbow::Error::HashNotFound)?
                .referenced_by
                .insert(hash);
        }
        let inner = Arc::new(EntryInner { topology, data });
        let entry = Entry {
            inner,
            referenced_by: Default::default(),
        };
        map.insert(hash, entry);
        self.map = map;
        Ok(())
    }

    pub fn referenced_by(&self, hash: Hash) -> Option<impl use<> + Iterator<Item = Hash>> {
        if let Some(entry) = self.map.get(&hash)
            && !entry.referenced_by.is_empty()
        {
            Some(entry.referenced_by.clone().into_iter())
        } else {
            None
        }
    }

    pub fn remove(&mut self, hash: Hash) -> Result<(), impl use<> + Iterator<Item = Hash>> {
        if let Some(referenced_by) = self.referenced_by(hash) {
            return Err(referenced_by);
        }
        let mut map = self.map.clone();
        if let Some(Entry { inner, .. }) = self.map.remove(&hash) {
            for referenced in &inner.topology {
                map.get_mut(referenced)
                    .expect("unknown")
                    .referenced_by
                    .remove(&hash);
            }
        }
        self.map = map;
        Ok(())
    }

    pub fn get(&self, hash: Hash) -> Option<(&[Hash], &[u8])> {
        self.map
            .get(&hash)
            .map(|entry| (&*entry.inner.topology, &*entry.inner.data))
    }

    pub fn contains(&self, hash: Hash) -> bool {
        self.map.contains_key(&hash)
    }

    pub fn to_resolve(&self) -> Arc<dyn Resolve> {
        Arc::new(self.clone())
    }

    fn resolve_bytes(&self, address: Address) -> object_rainbow::Result<Vec<u8>> {
        self.get(address.hash)
            .map(|(_, data)| data.to_owned())
            .ok_or(object_rainbow::Error::HashNotFound)
    }

    pub fn is_empty(&self) -> bool {
        self.map.is_empty()
    }

    pub fn len(&self) -> usize {
        self.map.len()
    }
}

impl Resolve for LocalMap {
    fn resolve<'a>(
        &'a self,
        address: Address,
        this: &'a Arc<dyn Resolve>,
    ) -> FailFuture<'a, ByteNode> {
        Box::pin(async move {
            let data = self.resolve_bytes(address)?;
            Ok((data, this.clone()))
        })
    }

    fn resolve_data(&'_ self, address: Address) -> FailFuture<'_, Vec<u8>> {
        Box::pin(async move { self.resolve_bytes(address) })
    }

    fn try_resolve_local(
        &self,
        address: Address,
        this: &Arc<dyn Resolve>,
    ) -> object_rainbow::Result<Option<ByteNode>> {
        let data = self.resolve_bytes(address)?;
        Ok(Some((data, this.clone())))
    }
}
