#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
pub mod migration;
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
pub mod weights;
extern crate alloc;
use alloc::{borrow::Cow, vec::Vec};
use sp_runtime::{
	traits::{BadOrigin, Hash, Saturating},
	Perbill,
};
use codec::{Decode, Encode, MaxEncodedLen};
use frame_support::{
	dispatch::Pays,
	ensure,
	pallet_prelude::Get,
	traits::{
		Consideration, Currency, Defensive, FetchResult, Footprint, PreimageProvider,
		PreimageRecipient, QueryPreimage, ReservableCurrency, StorePreimage,
	},
	BoundedSlice, BoundedVec,
};
use scale_info::TypeInfo;
pub use weights::WeightInfo;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
pub use pallet::*;
#[derive(Clone, Eq, PartialEq, Encode, Decode, TypeInfo, MaxEncodedLen, RuntimeDebug)]
pub enum OldRequestStatus<AccountId, Balance> {
	Unrequested { deposit: (AccountId, Balance), len: u32 },
	Requested { deposit: Option<(AccountId, Balance)>, count: u32, len: Option<u32> },
}
#[derive(Clone, Eq, PartialEq, Encode, Decode, TypeInfo, MaxEncodedLen, RuntimeDebug)]
pub enum RequestStatus<AccountId, Ticket> {
	Unrequested { ticket: (AccountId, Ticket), len: u32 },
	Requested { maybe_ticket: Option<(AccountId, Ticket)>, count: u32, maybe_len: Option<u32> },
}
type BalanceOf<T> =
	<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
type TicketOf<T> = <T as Config>::Consideration;
const MAX_SIZE: u32 = 4 * 1024 * 1024;
pub const MAX_HASH_UPGRADE_BULK_COUNT: u32 = 1024;
#[frame_support::pallet]
#[allow(deprecated)]
pub mod pallet {
	use super::*;
	const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
	#[pallet::config]
	pub trait Config: frame_system::Config {
		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
		type WeightInfo: weights::WeightInfo;
		type Currency: ReservableCurrency<Self::AccountId>;
		type ManagerOrigin: EnsureOrigin<Self::RuntimeOrigin>;
		type Consideration: Consideration<Self::AccountId, Footprint>;
	}
	#[pallet::pallet]
	#[pallet::storage_version(STORAGE_VERSION)]
	pub struct Pallet<T>(_);
	#[pallet::event]
	#[pallet::generate_deposit(pub(super) fn deposit_event)]
	pub enum Event<T: Config> {
		Noted { hash: T::Hash },
		Requested { hash: T::Hash },
		Cleared { hash: T::Hash },
	}
	#[pallet::error]
	pub enum Error<T> {
		TooBig,
		AlreadyNoted,
		NotAuthorized,
		NotNoted,
		Requested,
		NotRequested,
		TooMany,
		TooFew,
	}
	#[pallet::composite_enum]
	pub enum HoldReason {
		Preimage,
	}
	#[deprecated = "RequestStatusFor"]
	#[pallet::storage]
	pub(super) type StatusFor<T: Config> =
		StorageMap<_, Identity, T::Hash, OldRequestStatus<T::AccountId, BalanceOf<T>>>;
	#[pallet::storage]
	pub(super) type RequestStatusFor<T: Config> =
		StorageMap<_, Identity, T::Hash, RequestStatus<T::AccountId, TicketOf<T>>>;
	#[pallet::storage]
	pub(super) type PreimageFor<T: Config> =
		StorageMap<_, Identity, (T::Hash, u32), BoundedVec<u8, ConstU32<MAX_SIZE>>>;
	#[pallet::call(weight = T::WeightInfo)]
	impl<T: Config> Pallet<T> {
		#[pallet::call_index(0)]
		#[pallet::weight(T::WeightInfo::note_preimage(bytes.len() as u32))]
		pub fn note_preimage(origin: OriginFor<T>, bytes: Vec<u8>) -> DispatchResultWithPostInfo {
			let maybe_sender = Self::ensure_signed_or_manager(origin)?;
			let (system_requested, _) = Self::note_bytes(bytes.into(), maybe_sender.as_ref())?;
			if system_requested || maybe_sender.is_none() {
				Ok(Pays::No.into())
			} else {
				Ok(().into())
			}
		}
		#[pallet::call_index(1)]
		pub fn unnote_preimage(origin: OriginFor<T>, hash: T::Hash) -> DispatchResult {
			let maybe_sender = Self::ensure_signed_or_manager(origin)?;
			Self::do_unnote_preimage(&hash, maybe_sender)
		}
		#[pallet::call_index(2)]
		pub fn request_preimage(origin: OriginFor<T>, hash: T::Hash) -> DispatchResult {
			T::ManagerOrigin::ensure_origin(origin)?;
			Self::do_request_preimage(&hash);
			Ok(())
		}
		#[pallet::call_index(3)]
		pub fn unrequest_preimage(origin: OriginFor<T>, hash: T::Hash) -> DispatchResult {
			T::ManagerOrigin::ensure_origin(origin)?;
			Self::do_unrequest_preimage(&hash)
		}
		#[pallet::call_index(4)]
		#[pallet::weight(T::WeightInfo::ensure_updated(hashes.len() as u32))]
		pub fn ensure_updated(
			origin: OriginFor<T>,
			hashes: Vec<T::Hash>,
		) -> DispatchResultWithPostInfo {
			ensure_signed(origin)?;
			ensure!(hashes.len() > 0, Error::<T>::TooFew);
			ensure!(hashes.len() <= MAX_HASH_UPGRADE_BULK_COUNT as usize, Error::<T>::TooMany);
			let updated = hashes.iter().map(Self::do_ensure_updated).filter(|b| *b).count() as u32;
			let ratio = Perbill::from_rational(updated, hashes.len() as u32);
			let pays: Pays = (ratio < Perbill::from_percent(90)).into();
			Ok(pays.into())
		}
	}
}
impl<T: Config> Pallet<T> {
	fn do_ensure_updated(h: &T::Hash) -> bool {
		#[allow(deprecated)]
		let r = match StatusFor::<T>::take(h) {
			Some(r) => r,
			None => return false,
		};
		let n = match r {
			OldRequestStatus::Unrequested { deposit: (who, amount), len } => {
				T::Currency::unreserve(&who, amount);
				let Ok(ticket) =
					T::Consideration::new(&who, Footprint::from_parts(1, len as usize))
						.defensive_proof("Unexpected inability to take deposit after unreserved")
				else {
					return true
				};
				RequestStatus::Unrequested { ticket: (who, ticket), len }
			},
			OldRequestStatus::Requested { deposit: maybe_deposit, count, len: maybe_len } => {
				let maybe_ticket = if let Some((who, deposit)) = maybe_deposit {
					T::Currency::unreserve(&who, deposit);
					if let Some(len) = maybe_len {
						let Ok(ticket) =
							T::Consideration::new(&who, Footprint::from_parts(1, len as usize))
								.defensive_proof(
									"Unexpected inability to take deposit after unreserved",
								)
						else {
							return true
						};
						Some((who, ticket))
					} else {
						None
					}
				} else {
					None
				};
				RequestStatus::Requested { maybe_ticket, count, maybe_len }
			},
		};
		RequestStatusFor::<T>::insert(h, n);
		true
	}
	fn ensure_signed_or_manager(
		origin: T::RuntimeOrigin,
	) -> Result<Option<T::AccountId>, BadOrigin> {
		if T::ManagerOrigin::ensure_origin(origin.clone()).is_ok() {
			return Ok(None)
		}
		let who = ensure_signed(origin)?;
		Ok(Some(who))
	}
	fn note_bytes(
		preimage: Cow<[u8]>,
		maybe_depositor: Option<&T::AccountId>,
	) -> Result<(bool, T::Hash), DispatchError> {
		let hash = T::Hashing::hash(&preimage);
		let len = preimage.len() as u32;
		ensure!(len <= MAX_SIZE, Error::<T>::TooBig);
		Self::do_ensure_updated(&hash);
		let status = match (RequestStatusFor::<T>::get(hash), maybe_depositor) {
			(Some(RequestStatus::Requested { maybe_ticket, count, .. }), _) =>
				RequestStatus::Requested { maybe_ticket, count, maybe_len: Some(len) },
			(Some(RequestStatus::Unrequested { .. }), Some(_)) =>
				return Err(Error::<T>::AlreadyNoted.into()),
			(Some(RequestStatus::Unrequested { ticket, len }), None) => RequestStatus::Requested {
				maybe_ticket: Some(ticket),
				count: 1,
				maybe_len: Some(len),
			},
			(None, None) =>
				RequestStatus::Requested { maybe_ticket: None, count: 1, maybe_len: Some(len) },
			(None, Some(depositor)) => {
				let ticket =
					T::Consideration::new(depositor, Footprint::from_parts(1, len as usize))?;
				RequestStatus::Unrequested { ticket: (depositor.clone(), ticket), len }
			},
		};
		let was_requested = matches!(status, RequestStatus::Requested { .. });
		RequestStatusFor::<T>::insert(hash, status);
		let _ = Self::insert(&hash, preimage)
			.defensive_proof("Unable to insert. Logic error in `note_bytes`?");
		Self::deposit_event(Event::Noted { hash });
		Ok((was_requested, hash))
	}
	fn do_request_preimage(hash: &T::Hash) {
		Self::do_ensure_updated(&hash);
		let (count, maybe_len, maybe_ticket) =
			RequestStatusFor::<T>::get(hash).map_or((1, None, None), |x| match x {
				RequestStatus::Requested { maybe_ticket, mut count, maybe_len } => {
					count.saturating_inc();
					(count, maybe_len, maybe_ticket)
				},
				RequestStatus::Unrequested { ticket, len } => (1, Some(len), Some(ticket)),
			});
		RequestStatusFor::<T>::insert(
			hash,
			RequestStatus::Requested { maybe_ticket, count, maybe_len },
		);
		if count == 1 {
			Self::deposit_event(Event::Requested { hash: *hash });
		}
	}
	fn do_unnote_preimage(
		hash: &T::Hash,
		maybe_check_owner: Option<T::AccountId>,
	) -> DispatchResult {
		Self::do_ensure_updated(&hash);
		match RequestStatusFor::<T>::get(hash).ok_or(Error::<T>::NotNoted)? {
			RequestStatus::Requested { maybe_ticket: Some((owner, ticket)), count, maybe_len } => {
				ensure!(maybe_check_owner.map_or(true, |c| c == owner), Error::<T>::NotAuthorized);
				let _ = ticket.drop(&owner);
				RequestStatusFor::<T>::insert(
					hash,
					RequestStatus::Requested { maybe_ticket: None, count, maybe_len },
				);
				Ok(())
			},
			RequestStatus::Requested { maybe_ticket: None, .. } => {
				ensure!(maybe_check_owner.is_none(), Error::<T>::NotAuthorized);
				Self::do_unrequest_preimage(hash)
			},
			RequestStatus::Unrequested { ticket: (owner, ticket), len } => {
				ensure!(maybe_check_owner.map_or(true, |c| c == owner), Error::<T>::NotAuthorized);
				let _ = ticket.drop(&owner);
				RequestStatusFor::<T>::remove(hash);
				Self::remove(hash, len);
				Self::deposit_event(Event::Cleared { hash: *hash });
				Ok(())
			},
		}
	}
	fn do_unrequest_preimage(hash: &T::Hash) -> DispatchResult {
		Self::do_ensure_updated(&hash);
		match RequestStatusFor::<T>::get(hash).ok_or(Error::<T>::NotRequested)? {
			RequestStatus::Requested { mut count, maybe_len, maybe_ticket } if count > 1 => {
				count.saturating_dec();
				RequestStatusFor::<T>::insert(
					hash,
					RequestStatus::Requested { maybe_ticket, count, maybe_len },
				);
			},
			RequestStatus::Requested { count, maybe_len, maybe_ticket } => {
				debug_assert!(count == 1, "preimage request counter at zero?");
				match (maybe_len, maybe_ticket) {
					(None, _) => RequestStatusFor::<T>::remove(hash),
					(Some(len), None) => {
						Self::remove(hash, len);
						RequestStatusFor::<T>::remove(hash);
						Self::deposit_event(Event::Cleared { hash: *hash });
					},
					(Some(len), Some(ticket)) => {
						RequestStatusFor::<T>::insert(
							hash,
							RequestStatus::Unrequested { ticket, len },
						);
					},
				}
			},
			RequestStatus::Unrequested { .. } => return Err(Error::<T>::NotRequested.into()),
		}
		Ok(())
	}
	fn insert(hash: &T::Hash, preimage: Cow<[u8]>) -> Result<(), ()> {
		BoundedSlice::<u8, ConstU32<MAX_SIZE>>::try_from(preimage.as_ref())
			.map_err(|_| ())
			.map(|s| PreimageFor::<T>::insert((hash, s.len() as u32), s))
	}
	fn remove(hash: &T::Hash, len: u32) {
		PreimageFor::<T>::remove((hash, len))
	}
	fn have(hash: &T::Hash) -> bool {
		Self::len(hash).is_some()
	}
	fn len(hash: &T::Hash) -> Option<u32> {
		use RequestStatus::*;
		Self::do_ensure_updated(&hash);
		match RequestStatusFor::<T>::get(hash) {
			Some(Requested { maybe_len: Some(len), .. }) | Some(Unrequested { len, .. }) =>
				Some(len),
			_ => None,
		}
	}
	fn fetch(hash: &T::Hash, len: Option<u32>) -> FetchResult {
		let len = len.or_else(|| Self::len(hash)).ok_or(DispatchError::Unavailable)?;
		PreimageFor::<T>::get((hash, len))
			.map(|p| p.into_inner())
			.map(Into::into)
			.ok_or(DispatchError::Unavailable)
	}
}
impl<T: Config> PreimageProvider<T::Hash> for Pallet<T> {
	fn have_preimage(hash: &T::Hash) -> bool {
		Self::have(hash)
	}
	fn preimage_requested(hash: &T::Hash) -> bool {
		Self::do_ensure_updated(hash);
		matches!(RequestStatusFor::<T>::get(hash), Some(RequestStatus::Requested { .. }))
	}
	fn get_preimage(hash: &T::Hash) -> Option<Vec<u8>> {
		Self::fetch(hash, None).ok().map(Cow::into_owned)
	}
	fn request_preimage(hash: &T::Hash) {
		Self::do_request_preimage(hash)
	}
	fn unrequest_preimage(hash: &T::Hash) {
		let res = Self::do_unrequest_preimage(hash);
		debug_assert!(res.is_ok(), "do_unrequest_preimage failed - counter underflow?");
	}
}
impl<T: Config> PreimageRecipient<T::Hash> for Pallet<T> {
	type MaxSize = ConstU32<MAX_SIZE>; fn note_preimage(bytes: BoundedVec<u8, Self::MaxSize>) {
		let _ = Self::note_bytes(bytes.into_inner().into(), None);
	}
	fn unnote_preimage(hash: &T::Hash) {
		let res = Self::do_unrequest_preimage(hash);
		debug_assert!(res.is_ok(), "unnote_preimage failed - request outstanding?");
	}
}
impl<T: Config> QueryPreimage for Pallet<T> {
	type H = T::Hashing;
	fn len(hash: &T::Hash) -> Option<u32> {
		Pallet::<T>::len(hash)
	}
	fn fetch(hash: &T::Hash, len: Option<u32>) -> FetchResult {
		Pallet::<T>::fetch(hash, len)
	}
	fn is_requested(hash: &T::Hash) -> bool {
		Self::do_ensure_updated(&hash);
		matches!(RequestStatusFor::<T>::get(hash), Some(RequestStatus::Requested { .. }))
	}
	fn request(hash: &T::Hash) {
		Self::do_request_preimage(hash)
	}
	fn unrequest(hash: &T::Hash) {
		let res = Self::do_unrequest_preimage(hash);
		debug_assert!(res.is_ok(), "do_unrequest_preimage failed - counter underflow?");
	}
}
impl<T: Config> StorePreimage for Pallet<T> {
	const MAX_LENGTH: usize = MAX_SIZE as usize;
	fn note(bytes: Cow<[u8]>) -> Result<T::Hash, DispatchError> {
		let maybe_hash = Self::note_bytes(bytes, None).map(|(_, h)| h);
		if maybe_hash == Err(DispatchError::from(Error::<T>::TooBig)) {
			Err(DispatchError::Exhausted)
		} else {
			maybe_hash
		}
	}
	fn unnote(hash: &T::Hash) {
		let res = Self::do_unnote_preimage(hash, None);
		debug_assert!(res.is_ok(), "unnote_preimage failed - request outstanding?");
	}
}