#![recursion_limit = "256"]
#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
#[cfg(test)]
pub mod mock;
#[cfg(test)]
mod tests;
mod functions;
mod impl_nonfungibles;
mod types;
pub mod migration;
pub mod weights;
use codec::{Decode, Encode};
use frame_support::traits::{
	tokens::Locker, BalanceStatus::Reserved, Currency, EnsureOriginWithArg, ReservableCurrency,
};
use frame_system::Config as SystemConfig;
use sp_runtime::{
	traits::{Saturating, StaticLookup, Zero},
	ArithmeticError, RuntimeDebug,
};
use sp_std::prelude::*;
pub use pallet::*;
pub use types::*;
pub use weights::WeightInfo;
const LOG_TARGET: &str = "runtime::uniques";
type AccountIdLookupOf<T> = <<T as frame_system::Config>::Lookup as StaticLookup>::Source;
#[frame_support::pallet]
pub mod pallet {
	use super::*;
	use frame_support::pallet_prelude::*;
	use frame_system::pallet_prelude::*;
	#[pallet::pallet]
	pub struct Pallet<T, I = ()>(_);
	#[cfg(feature = "runtime-benchmarks")]
	pub trait BenchmarkHelper<CollectionId, ItemId> {
		fn collection(i: u16) -> CollectionId;
		fn item(i: u16) -> ItemId;
	}
	#[cfg(feature = "runtime-benchmarks")]
	impl<CollectionId: From<u16>, ItemId: From<u16>> BenchmarkHelper<CollectionId, ItemId> for () {
		fn collection(i: u16) -> CollectionId {
			i.into()
		}
		fn item(i: u16) -> ItemId {
			i.into()
		}
	}
	#[pallet::config]
	pub trait Config<I: 'static = ()>: frame_system::Config {
		type RuntimeEvent: From<Event<Self, I>>
			+ IsType<<Self as frame_system::Config>::RuntimeEvent>;
		type CollectionId: Member + Parameter + MaxEncodedLen;
		type ItemId: Member + Parameter + MaxEncodedLen + Copy;
		type Currency: ReservableCurrency<Self::AccountId>;
		type ForceOrigin: EnsureOrigin<Self::RuntimeOrigin>;
		type CreateOrigin: EnsureOriginWithArg<
			Self::RuntimeOrigin,
			Self::CollectionId,
			Success = Self::AccountId,
		>;
		type Locker: Locker<Self::CollectionId, Self::ItemId>;
		#[pallet::constant]
		type CollectionDeposit: Get<DepositBalanceOf<Self, I>>;
		#[pallet::constant]
		type ItemDeposit: Get<DepositBalanceOf<Self, I>>;
		#[pallet::constant]
		type MetadataDepositBase: Get<DepositBalanceOf<Self, I>>;
		#[pallet::constant]
		type AttributeDepositBase: Get<DepositBalanceOf<Self, I>>;
		#[pallet::constant]
		type DepositPerByte: Get<DepositBalanceOf<Self, I>>;
		#[pallet::constant]
		type StringLimit: Get<u32>;
		#[pallet::constant]
		type KeyLimit: Get<u32>;
		#[pallet::constant]
		type ValueLimit: Get<u32>;
		#[cfg(feature = "runtime-benchmarks")]
		type Helper: BenchmarkHelper<Self::CollectionId, Self::ItemId>;
		type WeightInfo: WeightInfo;
	}
	#[pallet::storage]
	#[pallet::storage_prefix = "Class"]
	pub(super) type Collection<T: Config<I>, I: 'static = ()> = StorageMap<
		_,
		Blake2_128Concat,
		T::CollectionId,
		CollectionDetails<T::AccountId, DepositBalanceOf<T, I>>,
	>;
	#[pallet::storage]
	pub(super) type OwnershipAcceptance<T: Config<I>, I: 'static = ()> =
		StorageMap<_, Blake2_128Concat, T::AccountId, T::CollectionId>;
	#[pallet::storage]
	pub(super) type Account<T: Config<I>, I: 'static = ()> = StorageNMap<
		_,
		(
			NMapKey<Blake2_128Concat, T::AccountId>, NMapKey<Blake2_128Concat, T::CollectionId>,
			NMapKey<Blake2_128Concat, T::ItemId>,
		),
		(),
		OptionQuery,
	>;
	#[pallet::storage]
	#[pallet::storage_prefix = "ClassAccount"]
	pub(super) type CollectionAccount<T: Config<I>, I: 'static = ()> = StorageDoubleMap<
		_,
		Blake2_128Concat,
		T::AccountId,
		Blake2_128Concat,
		T::CollectionId,
		(),
		OptionQuery,
	>;
	#[pallet::storage]
	#[pallet::storage_prefix = "Asset"]
	pub(super) type Item<T: Config<I>, I: 'static = ()> = StorageDoubleMap<
		_,
		Blake2_128Concat,
		T::CollectionId,
		Blake2_128Concat,
		T::ItemId,
		ItemDetails<T::AccountId, DepositBalanceOf<T, I>>,
		OptionQuery,
	>;
	#[pallet::storage]
	#[pallet::storage_prefix = "ClassMetadataOf"]
	pub(super) type CollectionMetadataOf<T: Config<I>, I: 'static = ()> = StorageMap<
		_,
		Blake2_128Concat,
		T::CollectionId,
		CollectionMetadata<DepositBalanceOf<T, I>, T::StringLimit>,
		OptionQuery,
	>;
	#[pallet::storage]
	#[pallet::storage_prefix = "InstanceMetadataOf"]
	pub(super) type ItemMetadataOf<T: Config<I>, I: 'static = ()> = StorageDoubleMap<
		_,
		Blake2_128Concat,
		T::CollectionId,
		Blake2_128Concat,
		T::ItemId,
		ItemMetadata<DepositBalanceOf<T, I>, T::StringLimit>,
		OptionQuery,
	>;
	#[pallet::storage]
	pub(super) type Attribute<T: Config<I>, I: 'static = ()> = StorageNMap<
		_,
		(
			NMapKey<Blake2_128Concat, T::CollectionId>,
			NMapKey<Blake2_128Concat, Option<T::ItemId>>,
			NMapKey<Blake2_128Concat, BoundedVec<u8, T::KeyLimit>>,
		),
		(BoundedVec<u8, T::ValueLimit>, DepositBalanceOf<T, I>),
		OptionQuery,
	>;
	#[pallet::storage]
	pub(super) type ItemPriceOf<T: Config<I>, I: 'static = ()> = StorageDoubleMap<
		_,
		Blake2_128Concat,
		T::CollectionId,
		Blake2_128Concat,
		T::ItemId,
		(ItemPrice<T, I>, Option<T::AccountId>),
		OptionQuery,
	>;
	#[pallet::storage]
	pub(super) type CollectionMaxSupply<T: Config<I>, I: 'static = ()> =
		StorageMap<_, Blake2_128Concat, T::CollectionId, u32, OptionQuery>;
	#[pallet::event]
	#[pallet::generate_deposit(pub(super) fn deposit_event)]
	pub enum Event<T: Config<I>, I: 'static = ()> {
		Created { collection: T::CollectionId, creator: T::AccountId, owner: T::AccountId },
		ForceCreated { collection: T::CollectionId, owner: T::AccountId },
		Destroyed { collection: T::CollectionId },
		Issued { collection: T::CollectionId, item: T::ItemId, owner: T::AccountId },
		Transferred {
			collection: T::CollectionId,
			item: T::ItemId,
			from: T::AccountId,
			to: T::AccountId,
		},
		Burned { collection: T::CollectionId, item: T::ItemId, owner: T::AccountId },
		Frozen { collection: T::CollectionId, item: T::ItemId },
		Thawed { collection: T::CollectionId, item: T::ItemId },
		CollectionFrozen { collection: T::CollectionId },
		CollectionThawed { collection: T::CollectionId },
		OwnerChanged { collection: T::CollectionId, new_owner: T::AccountId },
		TeamChanged {
			collection: T::CollectionId,
			issuer: T::AccountId,
			admin: T::AccountId,
			freezer: T::AccountId,
		},
		ApprovedTransfer {
			collection: T::CollectionId,
			item: T::ItemId,
			owner: T::AccountId,
			delegate: T::AccountId,
		},
		ApprovalCancelled {
			collection: T::CollectionId,
			item: T::ItemId,
			owner: T::AccountId,
			delegate: T::AccountId,
		},
		ItemStatusChanged { collection: T::CollectionId },
		CollectionMetadataSet {
			collection: T::CollectionId,
			data: BoundedVec<u8, T::StringLimit>,
			is_frozen: bool,
		},
		CollectionMetadataCleared { collection: T::CollectionId },
		MetadataSet {
			collection: T::CollectionId,
			item: T::ItemId,
			data: BoundedVec<u8, T::StringLimit>,
			is_frozen: bool,
		},
		MetadataCleared { collection: T::CollectionId, item: T::ItemId },
		Redeposited { collection: T::CollectionId, successful_items: Vec<T::ItemId> },
		AttributeSet {
			collection: T::CollectionId,
			maybe_item: Option<T::ItemId>,
			key: BoundedVec<u8, T::KeyLimit>,
			value: BoundedVec<u8, T::ValueLimit>,
		},
		AttributeCleared {
			collection: T::CollectionId,
			maybe_item: Option<T::ItemId>,
			key: BoundedVec<u8, T::KeyLimit>,
		},
		OwnershipAcceptanceChanged { who: T::AccountId, maybe_collection: Option<T::CollectionId> },
		CollectionMaxSupplySet { collection: T::CollectionId, max_supply: u32 },
		ItemPriceSet {
			collection: T::CollectionId,
			item: T::ItemId,
			price: ItemPrice<T, I>,
			whitelisted_buyer: Option<T::AccountId>,
		},
		ItemPriceRemoved { collection: T::CollectionId, item: T::ItemId },
		ItemBought {
			collection: T::CollectionId,
			item: T::ItemId,
			price: ItemPrice<T, I>,
			seller: T::AccountId,
			buyer: T::AccountId,
		},
	}
	#[pallet::error]
	pub enum Error<T, I = ()> {
		NoPermission,
		UnknownCollection,
		AlreadyExists,
		WrongOwner,
		BadWitness,
		InUse,
		Frozen,
		WrongDelegate,
		NoDelegate,
		Unapproved,
		Unaccepted,
		Locked,
		MaxSupplyReached,
		MaxSupplyAlreadySet,
		MaxSupplyTooSmall,
		UnknownItem,
		NotForSale,
		BidTooLow,
	}
	impl<T: Config<I>, I: 'static> Pallet<T, I> {
		pub fn owner(collection: T::CollectionId, item: T::ItemId) -> Option<T::AccountId> {
			Item::<T, I>::get(collection, item).map(|i| i.owner)
		}
		pub fn collection_owner(collection: T::CollectionId) -> Option<T::AccountId> {
			Collection::<T, I>::get(collection).map(|i| i.owner)
		}
	}
	#[pallet::call]
	impl<T: Config<I>, I: 'static> Pallet<T, I> {
		#[pallet::call_index(0)]
		#[pallet::weight(T::WeightInfo::create())]
		pub fn create(
			origin: OriginFor<T>,
			collection: T::CollectionId,
			admin: AccountIdLookupOf<T>,
		) -> DispatchResult {
			let owner = T::CreateOrigin::ensure_origin(origin, &collection)?;
			let admin = T::Lookup::lookup(admin)?;
			Self::do_create_collection(
				collection.clone(),
				owner.clone(),
				admin.clone(),
				T::CollectionDeposit::get(),
				false,
				Event::Created { collection, creator: owner, owner: admin },
			)
		}
		#[pallet::call_index(1)]
		#[pallet::weight(T::WeightInfo::force_create())]
		pub fn force_create(
			origin: OriginFor<T>,
			collection: T::CollectionId,
			owner: AccountIdLookupOf<T>,
			free_holding: bool,
		) -> DispatchResult {
			T::ForceOrigin::ensure_origin(origin)?;
			let owner = T::Lookup::lookup(owner)?;
			Self::do_create_collection(
				collection.clone(),
				owner.clone(),
				owner.clone(),
				Zero::zero(),
				free_holding,
				Event::ForceCreated { collection, owner },
			)
		}
		#[pallet::call_index(2)]
		#[pallet::weight(T::WeightInfo::destroy(
			witness.items,
 			witness.item_metadatas,
			witness.attributes,
 		))]
		pub fn destroy(
			origin: OriginFor<T>,
			collection: T::CollectionId,
			witness: DestroyWitness,
		) -> DispatchResultWithPostInfo {
			let maybe_check_owner = match T::ForceOrigin::try_origin(origin) {
				Ok(_) => None,
				Err(origin) => Some(ensure_signed(origin)?),
			};
			let details = Self::do_destroy_collection(collection, witness, maybe_check_owner)?;
			Ok(Some(T::WeightInfo::destroy(
				details.items,
				details.item_metadatas,
				details.attributes,
			))
			.into())
		}
		#[pallet::call_index(3)]
		#[pallet::weight(T::WeightInfo::mint())]
		pub fn mint(
			origin: OriginFor<T>,
			collection: T::CollectionId,
			item: T::ItemId,
			owner: AccountIdLookupOf<T>,
		) -> DispatchResult {
			let origin = ensure_signed(origin)?;
			let owner = T::Lookup::lookup(owner)?;
			Self::do_mint(collection, item, owner, |collection_details| {
				ensure!(collection_details.issuer == origin, Error::<T, I>::NoPermission);
				Ok(())
			})
		}
		#[pallet::call_index(4)]
		#[pallet::weight(T::WeightInfo::burn())]
		pub fn burn(
			origin: OriginFor<T>,
			collection: T::CollectionId,
			item: T::ItemId,
			check_owner: Option<AccountIdLookupOf<T>>,
		) -> DispatchResult {
			let origin = ensure_signed(origin)?;
			let check_owner = check_owner.map(T::Lookup::lookup).transpose()?;
			Self::do_burn(collection, item, |collection_details, details| {
				let is_permitted = collection_details.admin == origin || details.owner == origin;
				ensure!(is_permitted, Error::<T, I>::NoPermission);
				ensure!(
					check_owner.map_or(true, |o| o == details.owner),
					Error::<T, I>::WrongOwner
				);
				Ok(())
			})
		}
		#[pallet::call_index(5)]
		#[pallet::weight(T::WeightInfo::transfer())]
		pub fn transfer(
			origin: OriginFor<T>,
			collection: T::CollectionId,
			item: T::ItemId,
			dest: AccountIdLookupOf<T>,
		) -> DispatchResult {
			let origin = ensure_signed(origin)?;
			let dest = T::Lookup::lookup(dest)?;
			Self::do_transfer(collection, item, dest, |collection_details, details| {
				if details.owner != origin && collection_details.admin != origin {
					let approved = details.approved.take().map_or(false, |i| i == origin);
					ensure!(approved, Error::<T, I>::NoPermission);
				}
				Ok(())
			})
		}
		#[pallet::call_index(6)]
		#[pallet::weight(T::WeightInfo::redeposit(items.len() as u32))]
		pub fn redeposit(
			origin: OriginFor<T>,
			collection: T::CollectionId,
			items: Vec<T::ItemId>,
		) -> DispatchResult {
			let origin = ensure_signed(origin)?;
			let mut collection_details =
				Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
			ensure!(collection_details.owner == origin, Error::<T, I>::NoPermission);
			let deposit = match collection_details.free_holding {
				true => Zero::zero(),
				false => T::ItemDeposit::get(),
			};
			let mut successful = Vec::with_capacity(items.len());
			for item in items.into_iter() {
				let mut details = match Item::<T, I>::get(&collection, &item) {
					Some(x) => x,
					None => continue,
				};
				let old = details.deposit;
				if old > deposit {
					T::Currency::unreserve(&collection_details.owner, old - deposit);
				} else if deposit > old {
					if T::Currency::reserve(&collection_details.owner, deposit - old).is_err() {
						continue
					}
				} else {
					continue
				}
				collection_details.total_deposit.saturating_accrue(deposit);
				collection_details.total_deposit.saturating_reduce(old);
				details.deposit = deposit;
				Item::<T, I>::insert(&collection, &item, &details);
				successful.push(item);
			}
			Collection::<T, I>::insert(&collection, &collection_details);
			Self::deposit_event(Event::<T, I>::Redeposited {
				collection,
				successful_items: successful,
			});
			Ok(())
		}
		#[pallet::call_index(7)]
		#[pallet::weight(T::WeightInfo::freeze())]
		pub fn freeze(
			origin: OriginFor<T>,
			collection: T::CollectionId,
			item: T::ItemId,
		) -> DispatchResult {
			let origin = ensure_signed(origin)?;
			let mut details =
				Item::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::UnknownCollection)?;
			let collection_details =
				Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
			ensure!(collection_details.freezer == origin, Error::<T, I>::NoPermission);
			details.is_frozen = true;
			Item::<T, I>::insert(&collection, &item, &details);
			Self::deposit_event(Event::<T, I>::Frozen { collection, item });
			Ok(())
		}
		#[pallet::call_index(8)]
		#[pallet::weight(T::WeightInfo::thaw())]
		pub fn thaw(
			origin: OriginFor<T>,
			collection: T::CollectionId,
			item: T::ItemId,
		) -> DispatchResult {
			let origin = ensure_signed(origin)?;
			let mut details =
				Item::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::UnknownCollection)?;
			let collection_details =
				Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
			ensure!(collection_details.admin == origin, Error::<T, I>::NoPermission);
			details.is_frozen = false;
			Item::<T, I>::insert(&collection, &item, &details);
			Self::deposit_event(Event::<T, I>::Thawed { collection, item });
			Ok(())
		}
		#[pallet::call_index(9)]
		#[pallet::weight(T::WeightInfo::freeze_collection())]
		pub fn freeze_collection(
			origin: OriginFor<T>,
			collection: T::CollectionId,
		) -> DispatchResult {
			let origin = ensure_signed(origin)?;
			Collection::<T, I>::try_mutate(collection.clone(), |maybe_details| {
				let details = maybe_details.as_mut().ok_or(Error::<T, I>::UnknownCollection)?;
				ensure!(origin == details.freezer, Error::<T, I>::NoPermission);
				details.is_frozen = true;
				Self::deposit_event(Event::<T, I>::CollectionFrozen { collection });
				Ok(())
			})
		}
		#[pallet::call_index(10)]
		#[pallet::weight(T::WeightInfo::thaw_collection())]
		pub fn thaw_collection(
			origin: OriginFor<T>,
			collection: T::CollectionId,
		) -> DispatchResult {
			let origin = ensure_signed(origin)?;
			Collection::<T, I>::try_mutate(collection.clone(), |maybe_details| {
				let details = maybe_details.as_mut().ok_or(Error::<T, I>::UnknownCollection)?;
				ensure!(origin == details.admin, Error::<T, I>::NoPermission);
				details.is_frozen = false;
				Self::deposit_event(Event::<T, I>::CollectionThawed { collection });
				Ok(())
			})
		}
		#[pallet::call_index(11)]
		#[pallet::weight(T::WeightInfo::transfer_ownership())]
		pub fn transfer_ownership(
			origin: OriginFor<T>,
			collection: T::CollectionId,
			owner: AccountIdLookupOf<T>,
		) -> DispatchResult {
			let origin = ensure_signed(origin)?;
			let owner = T::Lookup::lookup(owner)?;
			let acceptable_collection = OwnershipAcceptance::<T, I>::get(&owner);
			ensure!(acceptable_collection.as_ref() == Some(&collection), Error::<T, I>::Unaccepted);
			Collection::<T, I>::try_mutate(collection.clone(), |maybe_details| {
				let details = maybe_details.as_mut().ok_or(Error::<T, I>::UnknownCollection)?;
				ensure!(origin == details.owner, Error::<T, I>::NoPermission);
				if details.owner == owner {
					return Ok(())
				}
				T::Currency::repatriate_reserved(
					&details.owner,
					&owner,
					details.total_deposit,
					Reserved,
				)?;
				CollectionAccount::<T, I>::remove(&details.owner, &collection);
				CollectionAccount::<T, I>::insert(&owner, &collection, ());
				details.owner = owner.clone();
				OwnershipAcceptance::<T, I>::remove(&owner);
				Self::deposit_event(Event::OwnerChanged { collection, new_owner: owner });
				Ok(())
			})
		}
		#[pallet::call_index(12)]
		#[pallet::weight(T::WeightInfo::set_team())]
		pub fn set_team(
			origin: OriginFor<T>,
			collection: T::CollectionId,
			issuer: AccountIdLookupOf<T>,
			admin: AccountIdLookupOf<T>,
			freezer: AccountIdLookupOf<T>,
		) -> DispatchResult {
			let origin = ensure_signed(origin)?;
			let issuer = T::Lookup::lookup(issuer)?;
			let admin = T::Lookup::lookup(admin)?;
			let freezer = T::Lookup::lookup(freezer)?;
			Collection::<T, I>::try_mutate(collection.clone(), |maybe_details| {
				let details = maybe_details.as_mut().ok_or(Error::<T, I>::UnknownCollection)?;
				ensure!(origin == details.owner, Error::<T, I>::NoPermission);
				details.issuer = issuer.clone();
				details.admin = admin.clone();
				details.freezer = freezer.clone();
				Self::deposit_event(Event::TeamChanged { collection, issuer, admin, freezer });
				Ok(())
			})
		}
		#[pallet::call_index(13)]
		#[pallet::weight(T::WeightInfo::approve_transfer())]
		pub fn approve_transfer(
			origin: OriginFor<T>,
			collection: T::CollectionId,
			item: T::ItemId,
			delegate: AccountIdLookupOf<T>,
		) -> DispatchResult {
			let maybe_check: Option<T::AccountId> = T::ForceOrigin::try_origin(origin)
				.map(|_| None)
				.or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?;
			let delegate = T::Lookup::lookup(delegate)?;
			let collection_details =
				Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
			let mut details =
				Item::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::UnknownCollection)?;
			if let Some(check) = maybe_check {
				let permitted = check == collection_details.admin || check == details.owner;
				ensure!(permitted, Error::<T, I>::NoPermission);
			}
			details.approved = Some(delegate);
			Item::<T, I>::insert(&collection, &item, &details);
			let delegate = details.approved.expect("set as Some above; qed");
			Self::deposit_event(Event::ApprovedTransfer {
				collection,
				item,
				owner: details.owner,
				delegate,
			});
			Ok(())
		}
		#[pallet::call_index(14)]
		#[pallet::weight(T::WeightInfo::cancel_approval())]
		pub fn cancel_approval(
			origin: OriginFor<T>,
			collection: T::CollectionId,
			item: T::ItemId,
			maybe_check_delegate: Option<AccountIdLookupOf<T>>,
		) -> DispatchResult {
			let maybe_check: Option<T::AccountId> = T::ForceOrigin::try_origin(origin)
				.map(|_| None)
				.or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?;
			let collection_details =
				Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
			let mut details =
				Item::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::UnknownCollection)?;
			if let Some(check) = maybe_check {
				let permitted = check == collection_details.admin || check == details.owner;
				ensure!(permitted, Error::<T, I>::NoPermission);
			}
			let maybe_check_delegate = maybe_check_delegate.map(T::Lookup::lookup).transpose()?;
			let old = details.approved.take().ok_or(Error::<T, I>::NoDelegate)?;
			if let Some(check_delegate) = maybe_check_delegate {
				ensure!(check_delegate == old, Error::<T, I>::WrongDelegate);
			}
			Item::<T, I>::insert(&collection, &item, &details);
			Self::deposit_event(Event::ApprovalCancelled {
				collection,
				item,
				owner: details.owner,
				delegate: old,
			});
			Ok(())
		}
		#[pallet::call_index(15)]
		#[pallet::weight(T::WeightInfo::force_item_status())]
		pub fn force_item_status(
			origin: OriginFor<T>,
			collection: T::CollectionId,
			owner: AccountIdLookupOf<T>,
			issuer: AccountIdLookupOf<T>,
			admin: AccountIdLookupOf<T>,
			freezer: AccountIdLookupOf<T>,
			free_holding: bool,
			is_frozen: bool,
		) -> DispatchResult {
			T::ForceOrigin::ensure_origin(origin)?;
			Collection::<T, I>::try_mutate(collection.clone(), |maybe_item| {
				let mut item = maybe_item.take().ok_or(Error::<T, I>::UnknownCollection)?;
				let old_owner = item.owner;
				let new_owner = T::Lookup::lookup(owner)?;
				item.owner = new_owner.clone();
				item.issuer = T::Lookup::lookup(issuer)?;
				item.admin = T::Lookup::lookup(admin)?;
				item.freezer = T::Lookup::lookup(freezer)?;
				item.free_holding = free_holding;
				item.is_frozen = is_frozen;
				*maybe_item = Some(item);
				CollectionAccount::<T, I>::remove(&old_owner, &collection);
				CollectionAccount::<T, I>::insert(&new_owner, &collection, ());
				Self::deposit_event(Event::ItemStatusChanged { collection });
				Ok(())
			})
		}
		#[pallet::call_index(16)]
		#[pallet::weight(T::WeightInfo::set_attribute())]
		pub fn set_attribute(
			origin: OriginFor<T>,
			collection: T::CollectionId,
			maybe_item: Option<T::ItemId>,
			key: BoundedVec<u8, T::KeyLimit>,
			value: BoundedVec<u8, T::ValueLimit>,
		) -> DispatchResult {
			let maybe_check_owner = T::ForceOrigin::try_origin(origin)
				.map(|_| None)
				.or_else(|origin| ensure_signed(origin).map(Some))?;
			let mut collection_details =
				Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
			if let Some(check_owner) = &maybe_check_owner {
				ensure!(check_owner == &collection_details.owner, Error::<T, I>::NoPermission);
			}
			let maybe_is_frozen = match maybe_item {
				None => CollectionMetadataOf::<T, I>::get(collection.clone()).map(|v| v.is_frozen),
				Some(item) =>
					ItemMetadataOf::<T, I>::get(collection.clone(), item).map(|v| v.is_frozen),
			};
			ensure!(!maybe_is_frozen.unwrap_or(false), Error::<T, I>::Frozen);
			let attribute = Attribute::<T, I>::get((collection.clone(), maybe_item, &key));
			if attribute.is_none() {
				collection_details.attributes.saturating_inc();
			}
			let old_deposit = attribute.map_or(Zero::zero(), |m| m.1);
			collection_details.total_deposit.saturating_reduce(old_deposit);
			let mut deposit = Zero::zero();
			if !collection_details.free_holding && maybe_check_owner.is_some() {
				deposit = T::DepositPerByte::get()
					.saturating_mul(((key.len() + value.len()) as u32).into())
					.saturating_add(T::AttributeDepositBase::get());
			}
			collection_details.total_deposit.saturating_accrue(deposit);
			if deposit > old_deposit {
				T::Currency::reserve(&collection_details.owner, deposit - old_deposit)?;
			} else if deposit < old_deposit {
				T::Currency::unreserve(&collection_details.owner, old_deposit - deposit);
			}
			Attribute::<T, I>::insert((&collection, maybe_item, &key), (&value, deposit));
			Collection::<T, I>::insert(collection.clone(), &collection_details);
			Self::deposit_event(Event::AttributeSet { collection, maybe_item, key, value });
			Ok(())
		}
		#[pallet::call_index(17)]
		#[pallet::weight(T::WeightInfo::clear_attribute())]
		pub fn clear_attribute(
			origin: OriginFor<T>,
			collection: T::CollectionId,
			maybe_item: Option<T::ItemId>,
			key: BoundedVec<u8, T::KeyLimit>,
		) -> DispatchResult {
			let maybe_check_owner = T::ForceOrigin::try_origin(origin)
				.map(|_| None)
				.or_else(|origin| ensure_signed(origin).map(Some))?;
			let mut collection_details =
				Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
			if let Some(check_owner) = &maybe_check_owner {
				ensure!(check_owner == &collection_details.owner, Error::<T, I>::NoPermission);
			}
			let maybe_is_frozen = match maybe_item {
				None => CollectionMetadataOf::<T, I>::get(collection.clone()).map(|v| v.is_frozen),
				Some(item) =>
					ItemMetadataOf::<T, I>::get(collection.clone(), item).map(|v| v.is_frozen),
			};
			ensure!(!maybe_is_frozen.unwrap_or(false), Error::<T, I>::Frozen);
			if let Some((_, deposit)) =
				Attribute::<T, I>::take((collection.clone(), maybe_item, &key))
			{
				collection_details.attributes.saturating_dec();
				collection_details.total_deposit.saturating_reduce(deposit);
				T::Currency::unreserve(&collection_details.owner, deposit);
				Collection::<T, I>::insert(collection.clone(), &collection_details);
				Self::deposit_event(Event::AttributeCleared { collection, maybe_item, key });
			}
			Ok(())
		}
		#[pallet::call_index(18)]
		#[pallet::weight(T::WeightInfo::set_metadata())]
		pub fn set_metadata(
			origin: OriginFor<T>,
			collection: T::CollectionId,
			item: T::ItemId,
			data: BoundedVec<u8, T::StringLimit>,
			is_frozen: bool,
		) -> DispatchResult {
			let maybe_check_owner = T::ForceOrigin::try_origin(origin)
				.map(|_| None)
				.or_else(|origin| ensure_signed(origin).map(Some))?;
			let mut collection_details =
				Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
			if let Some(check_owner) = &maybe_check_owner {
				ensure!(check_owner == &collection_details.owner, Error::<T, I>::NoPermission);
			}
			ItemMetadataOf::<T, I>::try_mutate_exists(collection.clone(), item, |metadata| {
				let was_frozen = metadata.as_ref().map_or(false, |m| m.is_frozen);
				ensure!(maybe_check_owner.is_none() || !was_frozen, Error::<T, I>::Frozen);
				if metadata.is_none() {
					collection_details.item_metadatas.saturating_inc();
				}
				let old_deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit);
				collection_details.total_deposit.saturating_reduce(old_deposit);
				let mut deposit = Zero::zero();
				if !collection_details.free_holding && maybe_check_owner.is_some() {
					deposit = T::DepositPerByte::get()
						.saturating_mul(((data.len()) as u32).into())
						.saturating_add(T::MetadataDepositBase::get());
				}
				if deposit > old_deposit {
					T::Currency::reserve(&collection_details.owner, deposit - old_deposit)?;
				} else if deposit < old_deposit {
					T::Currency::unreserve(&collection_details.owner, old_deposit - deposit);
				}
				collection_details.total_deposit.saturating_accrue(deposit);
				*metadata = Some(ItemMetadata { deposit, data: data.clone(), is_frozen });
				Collection::<T, I>::insert(&collection, &collection_details);
				Self::deposit_event(Event::MetadataSet { collection, item, data, is_frozen });
				Ok(())
			})
		}
		#[pallet::call_index(19)]
		#[pallet::weight(T::WeightInfo::clear_metadata())]
		pub fn clear_metadata(
			origin: OriginFor<T>,
			collection: T::CollectionId,
			item: T::ItemId,
		) -> DispatchResult {
			let maybe_check_owner = T::ForceOrigin::try_origin(origin)
				.map(|_| None)
				.or_else(|origin| ensure_signed(origin).map(Some))?;
			let mut collection_details =
				Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
			if let Some(check_owner) = &maybe_check_owner {
				ensure!(check_owner == &collection_details.owner, Error::<T, I>::NoPermission);
			}
			ItemMetadataOf::<T, I>::try_mutate_exists(collection.clone(), item, |metadata| {
				let was_frozen = metadata.as_ref().map_or(false, |m| m.is_frozen);
				ensure!(maybe_check_owner.is_none() || !was_frozen, Error::<T, I>::Frozen);
				if metadata.is_some() {
					collection_details.item_metadatas.saturating_dec();
				}
				let deposit = metadata.take().ok_or(Error::<T, I>::UnknownCollection)?.deposit;
				T::Currency::unreserve(&collection_details.owner, deposit);
				collection_details.total_deposit.saturating_reduce(deposit);
				Collection::<T, I>::insert(&collection, &collection_details);
				Self::deposit_event(Event::MetadataCleared { collection, item });
				Ok(())
			})
		}
		#[pallet::call_index(20)]
		#[pallet::weight(T::WeightInfo::set_collection_metadata())]
		pub fn set_collection_metadata(
			origin: OriginFor<T>,
			collection: T::CollectionId,
			data: BoundedVec<u8, T::StringLimit>,
			is_frozen: bool,
		) -> DispatchResult {
			let maybe_check_owner = T::ForceOrigin::try_origin(origin)
				.map(|_| None)
				.or_else(|origin| ensure_signed(origin).map(Some))?;
			let mut details =
				Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
			if let Some(check_owner) = &maybe_check_owner {
				ensure!(check_owner == &details.owner, Error::<T, I>::NoPermission);
			}
			CollectionMetadataOf::<T, I>::try_mutate_exists(collection.clone(), |metadata| {
				let was_frozen = metadata.as_ref().map_or(false, |m| m.is_frozen);
				ensure!(maybe_check_owner.is_none() || !was_frozen, Error::<T, I>::Frozen);
				let old_deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit);
				details.total_deposit.saturating_reduce(old_deposit);
				let mut deposit = Zero::zero();
				if maybe_check_owner.is_some() && !details.free_holding {
					deposit = T::DepositPerByte::get()
						.saturating_mul(((data.len()) as u32).into())
						.saturating_add(T::MetadataDepositBase::get());
				}
				if deposit > old_deposit {
					T::Currency::reserve(&details.owner, deposit - old_deposit)?;
				} else if deposit < old_deposit {
					T::Currency::unreserve(&details.owner, old_deposit - deposit);
				}
				details.total_deposit.saturating_accrue(deposit);
				Collection::<T, I>::insert(&collection, details);
				*metadata = Some(CollectionMetadata { deposit, data: data.clone(), is_frozen });
				Self::deposit_event(Event::CollectionMetadataSet { collection, data, is_frozen });
				Ok(())
			})
		}
		#[pallet::call_index(21)]
		#[pallet::weight(T::WeightInfo::clear_collection_metadata())]
		pub fn clear_collection_metadata(
			origin: OriginFor<T>,
			collection: T::CollectionId,
		) -> DispatchResult {
			let maybe_check_owner = T::ForceOrigin::try_origin(origin)
				.map(|_| None)
				.or_else(|origin| ensure_signed(origin).map(Some))?;
			let details =
				Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
			if let Some(check_owner) = &maybe_check_owner {
				ensure!(check_owner == &details.owner, Error::<T, I>::NoPermission);
			}
			CollectionMetadataOf::<T, I>::try_mutate_exists(collection.clone(), |metadata| {
				let was_frozen = metadata.as_ref().map_or(false, |m| m.is_frozen);
				ensure!(maybe_check_owner.is_none() || !was_frozen, Error::<T, I>::Frozen);
				let deposit = metadata.take().ok_or(Error::<T, I>::UnknownCollection)?.deposit;
				T::Currency::unreserve(&details.owner, deposit);
				Self::deposit_event(Event::CollectionMetadataCleared { collection });
				Ok(())
			})
		}
		#[pallet::call_index(22)]
		#[pallet::weight(T::WeightInfo::set_accept_ownership())]
		pub fn set_accept_ownership(
			origin: OriginFor<T>,
			maybe_collection: Option<T::CollectionId>,
		) -> DispatchResult {
			let who = ensure_signed(origin)?;
			let old = OwnershipAcceptance::<T, I>::get(&who);
			match (old.is_some(), maybe_collection.is_some()) {
				(false, true) => {
					frame_system::Pallet::<T>::inc_consumers(&who)?;
				},
				(true, false) => {
					frame_system::Pallet::<T>::dec_consumers(&who);
				},
				_ => {},
			}
			if let Some(collection) = maybe_collection.as_ref() {
				OwnershipAcceptance::<T, I>::insert(&who, collection);
			} else {
				OwnershipAcceptance::<T, I>::remove(&who);
			}
			Self::deposit_event(Event::OwnershipAcceptanceChanged { who, maybe_collection });
			Ok(())
		}
		#[pallet::call_index(23)]
		#[pallet::weight(T::WeightInfo::set_collection_max_supply())]
		pub fn set_collection_max_supply(
			origin: OriginFor<T>,
			collection: T::CollectionId,
			max_supply: u32,
		) -> DispatchResult {
			let maybe_check_owner = T::ForceOrigin::try_origin(origin)
				.map(|_| None)
				.or_else(|origin| ensure_signed(origin).map(Some))?;
			ensure!(
				!CollectionMaxSupply::<T, I>::contains_key(&collection),
				Error::<T, I>::MaxSupplyAlreadySet
			);
			let details =
				Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
			if let Some(check_owner) = &maybe_check_owner {
				ensure!(check_owner == &details.owner, Error::<T, I>::NoPermission);
			}
			ensure!(details.items <= max_supply, Error::<T, I>::MaxSupplyTooSmall);
			CollectionMaxSupply::<T, I>::insert(&collection, max_supply);
			Self::deposit_event(Event::CollectionMaxSupplySet { collection, max_supply });
			Ok(())
		}
		#[pallet::call_index(24)]
		#[pallet::weight(T::WeightInfo::set_price())]
		pub fn set_price(
			origin: OriginFor<T>,
			collection: T::CollectionId,
			item: T::ItemId,
			price: Option<ItemPrice<T, I>>,
			whitelisted_buyer: Option<AccountIdLookupOf<T>>,
		) -> DispatchResult {
			let origin = ensure_signed(origin)?;
			let whitelisted_buyer = whitelisted_buyer.map(T::Lookup::lookup).transpose()?;
			Self::do_set_price(collection, item, origin, price, whitelisted_buyer)
		}
		#[pallet::call_index(25)]
		#[pallet::weight(T::WeightInfo::buy_item())]
		pub fn buy_item(
			origin: OriginFor<T>,
			collection: T::CollectionId,
			item: T::ItemId,
			bid_price: ItemPrice<T, I>,
		) -> DispatchResult {
			let origin = ensure_signed(origin)?;
			Self::do_buy_item(collection, item, origin, bid_price)
		}
	}
}