#![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 => {
				todo!()
			},
			// Appends `who` to the current XCM Executor `origin` location.
			DescendOrigin(who) => {
				todo!("{:?}", who)
			},
			// 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 } => {
				todo!("{:?} {:?}", assets, beneficiary)
			},
			// 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) => {
				todo!("{:?}", assets)
			},
			// 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) => {
				todo!("{:?}", assets)
			},
			// 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!("{:?} {:?}", 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!("{:?}", 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())
	}
}
}
diff --git a/fundamentals/src/xcm_executor.rs b/fundamentals/src/xcm_executor.rs
index 8b13789..9c6cb16 100644
--- a/fundamentals/src/xcm_executor.rs
+++ b/fundamentals/src/xcm_executor.rs
@@ -1 +1,128 @@
+//! # 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 => {
+				todo!()
+			},
+			// Appends `who` to the current XCM Executor `origin` location.
+			DescendOrigin(who) => {
+				todo!("{:?}", who)
+			},
+			// 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 } => {
+				todo!("{:?} {:?}", assets, beneficiary)
+			},
+			// 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) => {
+				todo!("{:?}", assets)
+			},
+			// 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) => {
+				todo!("{:?}", assets)
+			},
+			// 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!("{:?} {:?}", 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!("{:?}", 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())
+	}
+}