use crate::{
	storage::{self, unhashed, StorageAppend},
	Never,
};
use codec::{Decode, Encode, EncodeLike, FullCodec};
pub trait StorageValue<T: FullCodec> {
	type Query;
	fn module_prefix() -> &'static [u8];
	fn storage_prefix() -> &'static [u8];
	fn from_optional_value_to_query(v: Option<T>) -> Self::Query;
	fn from_query_to_optional_value(v: Self::Query) -> Option<T>;
	fn storage_value_final_key() -> [u8; 32] {
		crate::storage::storage_prefix(Self::module_prefix(), Self::storage_prefix())
	}
}
impl<T: FullCodec, G: StorageValue<T>> storage::StorageValue<T> for G {
	type Query = G::Query;
	fn hashed_key() -> [u8; 32] {
		Self::storage_value_final_key()
	}
	fn exists() -> bool {
		unhashed::exists(&Self::storage_value_final_key())
	}
	fn get() -> Self::Query {
		let value = unhashed::get(&Self::storage_value_final_key());
		G::from_optional_value_to_query(value)
	}
	fn try_get() -> Result<T, ()> {
		unhashed::get(&Self::storage_value_final_key()).ok_or(())
	}
	fn translate<O: Decode, F: FnOnce(Option<O>) -> Option<T>>(f: F) -> Result<Option<T>, ()> {
		let key = Self::storage_value_final_key();
		let maybe_old = unhashed::get_raw(&key)
			.map(|old_data| O::decode(&mut &old_data[..]).map_err(|_| ()))
			.transpose()?;
		let maybe_new = f(maybe_old);
		if let Some(new) = maybe_new.as_ref() {
			new.using_encoded(|d| unhashed::put_raw(&key, d));
		} else {
			unhashed::kill(&key);
		}
		Ok(maybe_new)
	}
	fn put<Arg: EncodeLike<T>>(val: Arg) {
		unhashed::put(&Self::storage_value_final_key(), &val)
	}
	fn set(maybe_val: Self::Query) {
		if let Some(val) = G::from_query_to_optional_value(maybe_val) {
			unhashed::put(&Self::storage_value_final_key(), &val)
		} else {
			unhashed::kill(&Self::storage_value_final_key())
		}
	}
	fn kill() {
		unhashed::kill(&Self::storage_value_final_key())
	}
	fn mutate<R, F: FnOnce(&mut G::Query) -> R>(f: F) -> R {
		Self::try_mutate(|v| Ok::<R, Never>(f(v))).expect("`Never` can not be constructed; qed")
	}
	fn try_mutate<R, E, F: FnOnce(&mut G::Query) -> Result<R, E>>(f: F) -> Result<R, E> {
		let mut val = G::get();
		let ret = f(&mut val);
		if ret.is_ok() {
			match G::from_query_to_optional_value(val) {
				Some(ref val) => G::put(val),
				None => G::kill(),
			}
		}
		ret
	}
	fn mutate_exists<R, F>(f: F) -> R
	where
		F: FnOnce(&mut Option<T>) -> R,
	{
		Self::try_mutate_exists(|v| Ok::<R, Never>(f(v)))
			.expect("`Never` can not be constructed; qed")
	}
	fn try_mutate_exists<R, E, F>(f: F) -> Result<R, E>
	where
		F: FnOnce(&mut Option<T>) -> Result<R, E>,
	{
		let mut val = G::from_query_to_optional_value(Self::get());
		let ret = f(&mut val);
		if ret.is_ok() {
			match val {
				Some(ref val) => Self::put(val),
				None => Self::kill(),
			}
		}
		ret
	}
	fn take() -> G::Query {
		let key = Self::storage_value_final_key();
		let value = unhashed::get(&key);
		if value.is_some() {
			unhashed::kill(&key)
		}
		G::from_optional_value_to_query(value)
	}
	fn append<Item, EncodeLikeItem>(item: EncodeLikeItem)
	where
		Item: Encode,
		EncodeLikeItem: EncodeLike<Item>,
		T: StorageAppend<Item>,
	{
		let key = Self::storage_value_final_key();
		sp_io::storage::append(&key, item.encode());
	}
}