#![allow(unused)]
fn main() {
//! # Fundamentals lesson 5: XCM Executor
//!
//! Create your own executor for XCM.

use super::holding::*;

use sp_std::{marker::PhantomData, prelude::*};
use xcm::latest::prelude::*;
use xcm_executor::traits::{ProcessTransaction, TransactAsset};

pub trait XcmConfig {
	/// How to withdraw and deposit an asset.
	type AssetTransactor: TransactAsset;
	/// Transactional processor for XCM instructions.
	type TransactionalProcessor: ProcessTransaction;
}

/// The heart of the XCM Virtual Machine.
pub struct XcmExecutor<Config: XcmConfig> {
	/// The asset holding registrar, where we keep track of assets being processed by the XCM
	/// Executor.
	pub holding: AssetsInHolding,
	/// Contextual data pertaining to a specific list of XCM instructions. Most relevant the
	/// `origin` of the XCM Message.
	pub context: XcmContext,
	/// Just a placeholder to allow Rust to let us keep `Config`.
	_config: PhantomData<Config>,
}

/// The implementation of the XCM Executor and how it processes XCM.
impl<Config: XcmConfig> XcmExecutor<Config> {
	/// Crete an initialize a new XCM Executor.
	pub fn new(origin: impl Into<Location>) -> Self {
		let origin = origin.into();
		// In our version of the XCM Executor, we ignore `message_id` and `topic`.
		let context =
			XcmContext { origin: Some(origin), message_id: Default::default(), topic: None };
		Self { holding: Default::default(), context, _config: PhantomData }
	}

	/// Process an entire XCM program, instruction by instruction.
	pub fn process(&mut self, xcm: Xcm<()>) -> Result<(), XcmError> {
		log::trace!(target: "xcm::process", "xcm: {:?}", xcm);
		for instruction in xcm.0.into_iter() {
			self.process_instruction(instruction)?;
		}
		Ok(())
	}

	/// Simple helper function to access the `origin` from the XCM Executor `context`.
	pub fn origin_ref(&self) -> Option<&Location> {
		self.context.origin.as_ref()
	}

	/// Process a single XCM instruction, mutating the state of the XCM virtual machine.
	fn process_instruction(&mut self, instr: Instruction<()>) -> Result<(), XcmError> {
		log::trace!(target: "xcm::process_instruction", "=== {:?}", instr);
		match instr {
			// Clear the origin.
			//
			// This may be used by the XCM author to ensure that later instructions cannot command
			// the authority of the origin (e.g. if they are being relayed from an untrusted
			// source, as often the case with `ReserveAssetDeposited`).
			ClearOrigin => {
				self.context.origin = None;
				Ok(())
			},
			// Appends `who` to the current XCM Executor `origin` location.
			DescendOrigin(who) => self
				.context
				.origin
				.as_mut()
				.ok_or(XcmError::BadOrigin)?
				.append_with(who)
				.map_err(|_| XcmError::LocationFull),
			// Withdraw asset(s) (`assets`) from the ownership of `origin` and place equivalent
			// assets under the ownership of `beneficiary`.
			//
			// - `assets`: The asset(s) to be withdrawn.
			// - `beneficiary`: The new owner for the assets.
			TransferAsset { assets, beneficiary } => {
				Config::TransactionalProcessor::process(|| {
					// Take `assets` from the origin account (on-chain) and place into dest account.
					let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?;
					// Transfer each asset using the `AssetTransactor`.
					for asset in assets.inner() {
						Config::AssetTransactor::transfer_asset(
							&asset,
							origin,
							&beneficiary,
							&self.context,
						)?;
					}
					Ok(())
				})
			},
			// Withdraw asset(s) (`assets`) from the ownership of `origin` and place them into the
			// Holding Register.
			//
			// - `assets`: The asset(s) to be withdrawn into holding.
			WithdrawAsset(assets) => {
				Config::TransactionalProcessor::process(|| {
					let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?;
					// Take `assets` from the origin account (on-chain)...
					for asset in assets.inner() {
						Config::AssetTransactor::withdraw_asset(
							asset,
							origin,
							Some(&self.context),
						)?;
					}
					Ok(())
				})
				.and_then(|_| {
					// ...and place into holding.
					self.holding.subsume_assets(assets.into());
					Ok(())
				})
			},
			// Reduce Holding by up to the given assets.
			//
			// Holding is reduced by as much as possible up to the assets in the parameter. It is
			// not an error if the Holding does not contain the assets (to make this an error, use
			// `ExpectAsset` prior).
			BurnAsset(assets) => {
				self.holding.saturating_take(assets.into());
				Ok(())
			},
			// Remove the asset(s) (`assets`) from the Holding Register and place equivalent assets
			// under the ownership of `beneficiary` within this consensus system.
			//
			// - `assets`: The asset(s) to remove from holding.
			// - `beneficiary`: The new owner for the assets.
			DepositAsset { assets, beneficiary } => {
				/* TODO:
					- Make a clone of `self.holding` into a variable `old_holding`.
					- Start a new `TransactionalProcessor`, storing the `result`.
						- `saturating_take` the `assets` into a variable `deposited.
						- For `asset` in `deposited`
							- Use `AssetTransactor` to `deposit_asset` to the `beneficiary`.
					- If anything goes wrong, we should reset `self.holding` to `old_holding`.
					- Return the `result`
				*/
				todo!("{:?} {:?}", assets, beneficiary)
			},
			// Asset(s) (`assets`) have been destroyed on the `origin` system and equivalent assets
			// should be created and placed into the Holding Register.
			//
			// - `assets`: The asset(s) that are minted into the Holding Register.
			ReceiveTeleportedAsset(assets) => {
				/* TODO:
					- Process everything inside `TransactionalProcessor`.
						- Get the `origin` or return `XcmError::BadOrigin`.
						- For `asset` in `assets`:
							- Use `AssetTransactor` to see if we `can_check_in`.
							- Then actually `check_in` those assets.
					- `and_then`, if everything goes okay...
					- `subsume_assets` into the `self.holding`.
				*/
				todo!("{:?}", assets)
			},
			// In this workshop, we won't be implementing every instruction, just the ones above...
			// Our executor will simply panic if you try to execute other instructions.
			_ => unimplemented!(),
		}
	}
}

/// A public trait allowing other systems to access and use the `XcmExecutor`.
pub trait ExecuteXcm {
	/// Execute an XCM from a given `origin`.
	fn execute(origin: impl Into<Location>, xcm: Xcm<()>) -> XcmResult;
}

impl<Config: XcmConfig> ExecuteXcm for XcmExecutor<Config> {
	/// Execute an XCM from a given `origin`.
	fn execute(origin: impl Into<Location>, xcm: Xcm<()>) -> XcmResult {
		log::trace!(target: "xcm::execute", "xcm: {:?}", xcm);
		todo!("{:?}", origin.into())
	}
}
}
#![allow(unused)]
fn main() {
//! # Fundamentals lesson 5: XCM Executor
//!
//! Create your own executor for XCM.

use super::holding::*;

use sp_std::{marker::PhantomData, prelude::*};
use xcm::latest::prelude::*;
use xcm_executor::traits::{ProcessTransaction, TransactAsset};

pub trait XcmConfig {
	/// How to withdraw and deposit an asset.
	type AssetTransactor: TransactAsset;
	/// Transactional processor for XCM instructions.
	type TransactionalProcessor: ProcessTransaction;
}

/// The heart of the XCM Virtual Machine.
pub struct XcmExecutor<Config: XcmConfig> {
	/// The asset holding registrar, where we keep track of assets being processed by the XCM
	/// Executor.
	pub holding: AssetsInHolding,
	/// Contextual data pertaining to a specific list of XCM instructions. Most relevant the
	/// `origin` of the XCM Message.
	pub context: XcmContext,
	/// Just a placeholder to allow Rust to let us keep `Config`.
	_config: PhantomData<Config>,
}

/// The implementation of the XCM Executor and how it processes XCM.
impl<Config: XcmConfig> XcmExecutor<Config> {
	/// Crete an initialize a new XCM Executor.
	pub fn new(origin: impl Into<Location>) -> Self {
		let origin = origin.into();
		// In our version of the XCM Executor, we ignore `message_id` and `topic`.
		let context =
			XcmContext { origin: Some(origin), message_id: Default::default(), topic: None };
		Self { holding: Default::default(), context, _config: PhantomData }
	}

	/// Process an entire XCM program, instruction by instruction.
	pub fn process(&mut self, xcm: Xcm<()>) -> Result<(), XcmError> {
		log::trace!(target: "xcm::process", "xcm: {:?}", xcm);
		for instruction in xcm.0.into_iter() {
			self.process_instruction(instruction)?;
		}
		Ok(())
	}

	/// Simple helper function to access the `origin` from the XCM Executor `context`.
	pub fn origin_ref(&self) -> Option<&Location> {
		self.context.origin.as_ref()
	}

	/// Process a single XCM instruction, mutating the state of the XCM virtual machine.
	fn process_instruction(&mut self, instr: Instruction<()>) -> Result<(), XcmError> {
		log::trace!(target: "xcm::process_instruction", "=== {:?}", instr);
		match instr {
			// Clear the origin.
			//
			// This may be used by the XCM author to ensure that later instructions cannot command
			// the authority of the origin (e.g. if they are being relayed from an untrusted
			// source, as often the case with `ReserveAssetDeposited`).
			ClearOrigin => {
				self.context.origin = None;
				Ok(())
			},
			// Appends `who` to the current XCM Executor `origin` location.
			DescendOrigin(who) => self
				.context
				.origin
				.as_mut()
				.ok_or(XcmError::BadOrigin)?
				.append_with(who)
				.map_err(|_| XcmError::LocationFull),
			// Withdraw asset(s) (`assets`) from the ownership of `origin` and place equivalent
			// assets under the ownership of `beneficiary`.
			//
			// - `assets`: The asset(s) to be withdrawn.
			// - `beneficiary`: The new owner for the assets.
			TransferAsset { assets, beneficiary } => {
				Config::TransactionalProcessor::process(|| {
					// Take `assets` from the origin account (on-chain) and place into dest account.
					let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?;
					// Transfer each asset using the `AssetTransactor`.
					for asset in assets.inner() {
						Config::AssetTransactor::transfer_asset(
							&asset,
							origin,
							&beneficiary,
							&self.context,
						)?;
					}
					Ok(())
				})
			},
			// Withdraw asset(s) (`assets`) from the ownership of `origin` and place them into the
			// Holding Register.
			//
			// - `assets`: The asset(s) to be withdrawn into holding.
			WithdrawAsset(assets) => {
				Config::TransactionalProcessor::process(|| {
					let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?;
					// Take `assets` from the origin account (on-chain)...
					for asset in assets.inner() {
						Config::AssetTransactor::withdraw_asset(
							asset,
							origin,
							Some(&self.context),
						)?;
					}
					Ok(())
				})
				.and_then(|_| {
					// ...and place into holding.
					self.holding.subsume_assets(assets.into());
					Ok(())
				})
			},
			// Reduce Holding by up to the given assets.
			//
			// Holding is reduced by as much as possible up to the assets in the parameter. It is
			// not an error if the Holding does not contain the assets (to make this an error, use
			// `ExpectAsset` prior).
			BurnAsset(assets) => {
				self.holding.saturating_take(assets.into());
				Ok(())
			},
			// Remove the asset(s) (`assets`) from the Holding Register and place equivalent assets
			// under the ownership of `beneficiary` within this consensus system.
			//
			// - `assets`: The asset(s) to remove from holding.
			// - `beneficiary`: The new owner for the assets.
			DepositAsset { assets, beneficiary } => {
				let old_holding = self.holding.clone();
				let result = Config::TransactionalProcessor::process(|| {
					// Take assets from the holding registrar...
					let deposited = self.holding.saturating_take(assets);
					// ... and deposit them to the `beneficiary`.
					for asset in deposited.into_assets_iter() {
						Config::AssetTransactor::deposit_asset(
							&asset,
							&beneficiary,
							Some(&self.context),
						)?;
					}
					Ok(())
				});
				// If we were unable to execute `deposit_asset` in the `AssetTransactor`, we reset
				// the XCM Executor holding registrar since no operations took place.
				if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() {
					self.holding = old_holding;
				}
				result
			},
			// Asset(s) (`assets`) have been destroyed on the `origin` system and equivalent assets
			// should be created and placed into the Holding Register.
			//
			// - `assets`: The asset(s) that are minted into the Holding Register.
			ReceiveTeleportedAsset(assets) => {
				Config::TransactionalProcessor::process(|| {
					let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?;
					// check whether we trust origin to teleport this asset to us via config trait.
					for asset in assets.inner() {
						// We should check that the asset can actually be teleported in (for this to
						// be in error, there would need to be an accounting violation by one of the
						// trusted chains, so it's unlikely, but we don't want to punish a possibly
						// innocent chain/user).
						Config::AssetTransactor::can_check_in(origin, asset, &self.context)?;
						Config::AssetTransactor::check_in(origin, asset, &self.context);
					}
					Ok(())
				})
				.and_then(|_| {
					// ...and place into holding.
					self.holding.subsume_assets(assets.into());
					Ok(())
				})
			},
			// In this workshop, we won't be implementing every instruction, just the ones above...
			// Our executor will simply panic if you try to execute other instructions.
			_ => unimplemented!(),
		}
	}
}

/// A public trait allowing other systems to access and use the `XcmExecutor`.
pub trait ExecuteXcm {
	/// Execute an XCM from a given `origin`.
	fn execute(origin: impl Into<Location>, xcm: Xcm<()>) -> XcmResult;
}

impl<Config: XcmConfig> ExecuteXcm for XcmExecutor<Config> {
	/// Execute an XCM from a given `origin`.
	fn execute(origin: impl Into<Location>, xcm: Xcm<()>) -> XcmResult {
		log::trace!(target: "xcm::execute", "xcm: {:?}", xcm);
		todo!("{:?}", origin.into())
	}
}
}
diff --git a/fundamentals/src/xcm_executor.rs b/fundamentals/src/xcm_executor.rs
index d50d85a..540086f 100644
--- a/fundamentals/src/xcm_executor.rs
+++ b/fundamentals/src/xcm_executor.rs
@@ -132,6 +132,15 @@ impl<Config: XcmConfig> XcmExecutor<Config> {
 			// - `assets`: The asset(s) to remove from holding.
 			// - `beneficiary`: The new owner for the assets.
 			DepositAsset { assets, beneficiary } => {
+				/* TODO:
+					- Make a clone of `self.holding` into a variable `old_holding`.
+					- Start a new `TransactionalProcessor`, storing the `result`.
+						- `saturating_take` the `assets` into a variable `deposited.
+						- For `asset` in `deposited`
+							- Use `AssetTransactor` to `deposit_asset` to the `beneficiary`.
+					- If anything goes wrong, we should reset `self.holding` to `old_holding`.
+					- Return the `result`
+				*/
 				todo!("{:?} {:?}", assets, beneficiary)
 			},
 			// Asset(s) (`assets`) have been destroyed on the `origin` system and equivalent assets
@@ -139,6 +148,15 @@ impl<Config: XcmConfig> XcmExecutor<Config> {
 			//
 			// - `assets`: The asset(s) that are minted into the Holding Register.
 			ReceiveTeleportedAsset(assets) => {
+				/* TODO:
+					- Process everything inside `TransactionalProcessor`.
+						- Get the `origin` or return `XcmError::BadOrigin`.
+						- For `asset` in `assets`:
+							- Use `AssetTransactor` to see if we `can_check_in`.
+							- Then actually `check_in` those assets.
+					- `and_then`, if everything goes okay...
+					- `subsume_assets` into the `self.holding`.
+				*/
 				todo!("{:?}", assets)
 			},
 			// In this workshop, we won't be implementing every instruction, just the ones above...
diff --git a/fundamentals/src/xcm_executor.rs b/fundamentals/src/xcm_executor.rs
index 540086f..f03d79b 100644
--- a/fundamentals/src/xcm_executor.rs
+++ b/fundamentals/src/xcm_executor.rs
@@ -132,32 +132,50 @@ impl<Config: XcmConfig> XcmExecutor<Config> {
 			// - `assets`: The asset(s) to remove from holding.
 			// - `beneficiary`: The new owner for the assets.
 			DepositAsset { assets, beneficiary } => {
-				/* TODO:
-					- Make a clone of `self.holding` into a variable `old_holding`.
-					- Start a new `TransactionalProcessor`, storing the `result`.
-						- `saturating_take` the `assets` into a variable `deposited.
-						- For `asset` in `deposited`
-							- Use `AssetTransactor` to `deposit_asset` to the `beneficiary`.
-					- If anything goes wrong, we should reset `self.holding` to `old_holding`.
-					- Return the `result`
-				*/
-				todo!("{:?} {:?}", assets, beneficiary)
+				let old_holding = self.holding.clone();
+				let result = Config::TransactionalProcessor::process(|| {
+					// Take assets from the holding registrar...
+					let deposited = self.holding.saturating_take(assets);
+					// ... and deposit them to the `beneficiary`.
+					for asset in deposited.into_assets_iter() {
+						Config::AssetTransactor::deposit_asset(
+							&asset,
+							&beneficiary,
+							Some(&self.context),
+						)?;
+					}
+					Ok(())
+				});
+				// If we were unable to execute `deposit_asset` in the `AssetTransactor`, we reset
+				// the XCM Executor holding registrar since no operations took place.
+				if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() {
+					self.holding = old_holding;
+				}
+				result
 			},
 			// Asset(s) (`assets`) have been destroyed on the `origin` system and equivalent assets
 			// should be created and placed into the Holding Register.
 			//
 			// - `assets`: The asset(s) that are minted into the Holding Register.
 			ReceiveTeleportedAsset(assets) => {
-				/* TODO:
-					- Process everything inside `TransactionalProcessor`.
-						- Get the `origin` or return `XcmError::BadOrigin`.
-						- For `asset` in `assets`:
-							- Use `AssetTransactor` to see if we `can_check_in`.
-							- Then actually `check_in` those assets.
-					- `and_then`, if everything goes okay...
-					- `subsume_assets` into the `self.holding`.
-				*/
-				todo!("{:?}", assets)
+				Config::TransactionalProcessor::process(|| {
+					let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?;
+					// check whether we trust origin to teleport this asset to us via config trait.
+					for asset in assets.inner() {
+						// We should check that the asset can actually be teleported in (for this to
+						// be in error, there would need to be an accounting violation by one of the
+						// trusted chains, so it's unlikely, but we don't want to punish a possibly
+						// innocent chain/user).
+						Config::AssetTransactor::can_check_in(origin, asset, &self.context)?;
+						Config::AssetTransactor::check_in(origin, asset, &self.context);
+					}
+					Ok(())
+				})
+				.and_then(|_| {
+					// ...and place into holding.
+					self.holding.subsume_assets(assets.into());
+					Ok(())
+				})
 			},
 			// In this workshop, we won't be implementing every instruction, just the ones above...
 			// Our executor will simply panic if you try to execute other instructions.