use graphene_hash::CacheHash;
use std::hash::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::ops::Deref;
use std::sync::Arc;

/// Stores both what a node was called with and what it returned.
#[derive(Clone, Debug)]
pub struct IORecord<I, O> {
	pub input: I,
	pub output: O,
}

#[derive(Clone, Debug)]
pub struct MemoHash<T: CacheHash> {
	hash: u64,
	value: Arc<T>,
}

// Compare the value, not the cache `hash`: `CacheHash` is not guaranteed to be an equivalence relation,
// so it can't back `eq`/`cmp`. Cache-identity hashing goes through `CacheHash`.
impl<T: CacheHash + PartialEq> PartialEq for MemoHash<T> {
	fn eq(&self, other: &Self) -> bool {
		self.value == other.value
	}
}

impl<T: CacheHash + Eq> Eq for MemoHash<T> {}

impl<T: CacheHash + PartialOrd> PartialOrd for MemoHash<T> {
	fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
		self.value.partial_cmp(&other.value)
	}
}

impl<T: CacheHash + Ord> Ord for MemoHash<T> {
	fn cmp(&self, other: &Self) -> core::cmp::Ordering {
		self.value.cmp(&other.value)
	}
}

impl<'de, T: serde::Deserialize<'de> + CacheHash> serde::Deserialize<'de> for MemoHash<T> {
	fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
	where
		D: serde::Deserializer<'de>,
	{
		T::deserialize(deserializer).map(|value| Self::new(value))
	}
}

impl<T: CacheHash + serde::Serialize> serde::Serialize for MemoHash<T> {
	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
	where
		S: serde::Serializer,
	{
		self.value.serialize(serializer)
	}
}

impl<T: CacheHash> MemoHash<T> {
	pub fn new(value: T) -> Self {
		let hash = Self::calc_hash(&value);
		Self { hash, value: value.into() }
	}
	pub fn new_with_hash(value: T, hash: u64) -> Self {
		Self { hash, value: value.into() }
	}

	fn calc_hash(data: &T) -> u64 {
		let mut hasher = DefaultHasher::new();
		data.cache_hash(&mut hasher);
		hasher.finish()
	}

	pub fn inner_mut(&mut self) -> MemoHashGuard<'_, T> {
		MemoHashGuard { inner: self }
	}
	pub fn into_inner(self) -> Arc<T> {
		self.value
	}
	pub fn hash_code(&self) -> u64 {
		self.hash
	}
}

impl<T: CacheHash> From<T> for MemoHash<T> {
	fn from(value: T) -> Self {
		Self::new(value)
	}
}

impl<T: CacheHash> CacheHash for MemoHash<T> {
	fn cache_hash<H: Hasher>(&self, state: &mut H) {
		self.hash.hash(state);
	}
}

impl<T: CacheHash> Deref for MemoHash<T> {
	type Target = T;

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

pub struct MemoHashGuard<'a, T: CacheHash> {
	inner: &'a mut MemoHash<T>,
}

impl<T: CacheHash> Drop for MemoHashGuard<'_, T> {
	fn drop(&mut self) {
		let hash = MemoHash::<T>::calc_hash(&self.inner.value);
		self.inner.hash = hash;
	}
}

impl<T: CacheHash> Deref for MemoHashGuard<'_, T> {
	type Target = T;

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

impl<T: CacheHash + Clone> std::ops::DerefMut for MemoHashGuard<'_, T> {
	fn deref_mut(&mut self) -> &mut Self::Target {
		Arc::make_mut(&mut self.inner.value)
	}
}
