#![allow(unused)]
fn main() {
//! # Fundamentals lesson 6: Pallet XCM
//!
//! Implement the core functionality of Pallet XCM

use crate::xcm_executor::ExecuteXcm;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
use xcm::{prelude::*, VersionedAssets, VersionedLocation, VersionedXcm};

pub use pallet::*;

#[frame_support::pallet]
pub mod pallet {
	use super::*;

	#[pallet::pallet]
	pub struct Pallet<T>(_);

	#[pallet::config]
	pub trait Config: frame_system::Config {
		/// Something to execute an XCM message.
		type XcmExecutor: ExecuteXcm;
		/// Required origin for executing XCM messages, including the teleport functionality. If
		/// successful, then it resolves to `Location` which exists as an interior location
		/// within this chain's XCM context.
		type ExecuteXcmOrigin: EnsureOrigin<
			<Self as frame_system::Config>::RuntimeOrigin,
			Success = Location,
		>;
		/// Required origin for sending XCM messages. If successful, it resolves to `Location`
		/// which exists as an interior location within this chain's XCM context.
		type SendXcmOrigin: EnsureOrigin<
			<Self as frame_system::Config>::RuntimeOrigin,
			Success = Location,
		>;
		/// The type used to actually dispatch an XCM to its destination.
		type XcmRouter: SendXcm;
		/// This chain's Universal Location.
		type UniversalLocation: Get<InteriorLocation>;
	}

	#[pallet::error]
	pub enum Error<T> {
		/// The version of the `Versioned` value used is not able to be interpreted.
		BadVersion,
		/// Origin is invalid for sending.
		InvalidOrigin,
		/// Could not re-anchor the assets to declare the fees for the destination chain.
		CannotReanchor,
		/// A general error indicating something went wrong with the XCM Executor.
		ExecutorError,
		/// A general error indicating something went wrong with the XCM Router.
		RouterError,
	}

	#[pallet::call]
	impl<T: Config> Pallet<T> {
		/// Execute an XCM from a local, signed, origin.
		#[pallet::call_index(0)]
		#[pallet::weight(Weight::default())]
		pub fn execute(
			origin: OriginFor<T>,
			message: Box<VersionedXcm<()>>,
			_max_weight: Weight,
		) -> DispatchResult {
			let message = (*message).try_into().map_err(|()| Error::<T>::BadVersion)?;
			// Actually execute the XCM.
			Self::do_execute(origin, message)
		}

		/// Send an XCM to another consensus system.
		#[pallet::call_index(1)]
		#[pallet::weight(Weight::default())]
		pub fn send(
			origin: OriginFor<T>,
			dest: Box<VersionedLocation>,
			message: Box<VersionedXcm<()>>,
		) -> DispatchResult {
			let dest = Location::try_from(*dest).map_err(|()| Error::<T>::BadVersion)?;
			let message: Xcm<()> = (*message).try_into().map_err(|()| Error::<T>::BadVersion)?;
			// Actually send the XCM.
			Self::do_send(origin, dest, message)
		}

		/// Teleport some assets from the local chain to some destination chain.
		#[pallet::call_index(2)]
		#[pallet::weight(Weight::default())]
		pub fn teleport_assets(
			origin: OriginFor<T>,
			dest: Box<VersionedLocation>,
			beneficiary: Box<VersionedLocation>,
			assets: Box<VersionedAssets>,
			fee_asset_item: u32,
		) -> DispatchResult {
			let dest: Location = (*dest).try_into().map_err(|()| Error::<T>::BadVersion)?;
			let beneficiary: Location =
				(*beneficiary).try_into().map_err(|()| Error::<T>::BadVersion)?;
			let assets: Assets = (*assets).try_into().map_err(|()| Error::<T>::BadVersion)?;
			// Actually teleport the assets.
			Self::do_teleport_assets(origin, dest, beneficiary, assets, fee_asset_item)
		}

		/// Transfer some assets from the local chain to the destination chain through their local,
		/// destination or remote reserve.
		#[pallet::call_index(3)]
		#[pallet::weight(Weight::default())]
		pub fn reserve_transfer_assets(
			origin: OriginFor<T>,
			dest: Box<VersionedLocation>,
			beneficiary: Box<VersionedLocation>,
			assets: Box<VersionedAssets>,
			fee_asset_item: u32,
		) -> DispatchResult {
			let dest: Location = (*dest).try_into().map_err(|()| Error::<T>::BadVersion)?;
			let beneficiary: Location =
				(*beneficiary).try_into().map_err(|()| Error::<T>::BadVersion)?;
			let assets: Assets = (*assets).try_into().map_err(|()| Error::<T>::BadVersion)?;
			// Actually reserve transfer the assets.
			Self::do_reserve_transfer_assets(origin, dest, beneficiary, assets, fee_asset_item)
		}
	}
}

impl<T: Config> Pallet<T> {
	/// Execute an XCM locally on this chain on behalf of `origin`.
	pub fn do_execute(origin: OriginFor<T>, message: Xcm<()>) -> DispatchResult {
		let execute_origin: Location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
		T::XcmExecutor::execute(execute_origin, message).map_err(|_| Error::<T>::ExecutorError)?;
		Ok(())
	}

	/// Relay an XCM `message` from a given `interior` location in this context to a given `dest`
	/// location.
	pub fn do_send(origin: OriginFor<T>, dest: Location, mut message: Xcm<()>) -> DispatchResult {
		/* TODO:
			- Use `SendXcmOrigin` to "ensure" that `origin` is valid to send XCM, and assign the resulting location to `origin_location`.
			- Convert `origin_location` to `interior: Junctions`, else return `InvalidOrigin`.
			- If `interior` is not equal to `Junctions::Here`, insert into the XCM `DescendOrigin(interior)`.
			- Use `XcmRouter` to `validate` the `dest` and `message` is valid, and create a `ticket`, else you should return a `RouterError`.
			- Finally, use the `XcmRouter` to `deliver` the `ticket`, else return `RouterError`.
		*/
		todo!("{:?} {:?}", dest, message)
	}

	pub fn do_teleport_assets(
		origin: OriginFor<T>,
		dest: Location,
		beneficiary: Location,
		assets: Assets,
		// The index into `assets` of the item which should be used to pay fees.
		// We don't use this in our naive implementation.
		_fee_asset_item: u32,
	) -> DispatchResult {
		todo!("{:?} {:?} {:?}", dest, beneficiary, assets)
	}

	pub fn do_reserve_transfer_assets(
		_origin: OriginFor<T>,
		_dest: Location,
		_beneficiary: Location,
		_assets: Assets,
		_fee_asset_item: u32,
	) -> DispatchResult {
		// There are 3 different reserve transfer scenarios:
		// - A local reserve transfer: reserve-transfer `asset` to `dest`, using local chain as
		//   reserve.
		// - A destination reserve transfer: reserve-transfer `asset` to `dest`, using `dest` as
		//   reserve.
		// - A remote reserve transfer: reserve-transfer `asset` to `dest`, using remote chain
		//   `Location` as reserve.
		//
		// This is a lot to do in this workshop, but a welcome challenge for the reader to
		// implement.
		unimplemented!()
	}
}
}
#![allow(unused)]
fn main() {
//! # Fundamentals lesson 6: Pallet XCM
//!
//! Implement the core functionality of Pallet XCM

use crate::xcm_executor::ExecuteXcm;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
use xcm::{prelude::*, VersionedAssets, VersionedLocation, VersionedXcm};

pub use pallet::*;

#[frame_support::pallet]
pub mod pallet {
	use super::*;

	#[pallet::pallet]
	pub struct Pallet<T>(_);

	#[pallet::config]
	pub trait Config: frame_system::Config {
		/// Something to execute an XCM message.
		type XcmExecutor: ExecuteXcm;
		/// Required origin for executing XCM messages, including the teleport functionality. If
		/// successful, then it resolves to `Location` which exists as an interior location
		/// within this chain's XCM context.
		type ExecuteXcmOrigin: EnsureOrigin<
			<Self as frame_system::Config>::RuntimeOrigin,
			Success = Location,
		>;
		/// Required origin for sending XCM messages. If successful, it resolves to `Location`
		/// which exists as an interior location within this chain's XCM context.
		type SendXcmOrigin: EnsureOrigin<
			<Self as frame_system::Config>::RuntimeOrigin,
			Success = Location,
		>;
		/// The type used to actually dispatch an XCM to its destination.
		type XcmRouter: SendXcm;
		/// This chain's Universal Location.
		type UniversalLocation: Get<InteriorLocation>;
	}

	#[pallet::error]
	pub enum Error<T> {
		/// The version of the `Versioned` value used is not able to be interpreted.
		BadVersion,
		/// Origin is invalid for sending.
		InvalidOrigin,
		/// Could not re-anchor the assets to declare the fees for the destination chain.
		CannotReanchor,
		/// A general error indicating something went wrong with the XCM Executor.
		ExecutorError,
		/// A general error indicating something went wrong with the XCM Router.
		RouterError,
	}

	#[pallet::call]
	impl<T: Config> Pallet<T> {
		/// Execute an XCM from a local, signed, origin.
		#[pallet::call_index(0)]
		#[pallet::weight(Weight::default())]
		pub fn execute(
			origin: OriginFor<T>,
			message: Box<VersionedXcm<()>>,
			_max_weight: Weight,
		) -> DispatchResult {
			let message = (*message).try_into().map_err(|()| Error::<T>::BadVersion)?;
			// Actually execute the XCM.
			Self::do_execute(origin, message)
		}

		/// Send an XCM to another consensus system.
		#[pallet::call_index(1)]
		#[pallet::weight(Weight::default())]
		pub fn send(
			origin: OriginFor<T>,
			dest: Box<VersionedLocation>,
			message: Box<VersionedXcm<()>>,
		) -> DispatchResult {
			let dest = Location::try_from(*dest).map_err(|()| Error::<T>::BadVersion)?;
			let message: Xcm<()> = (*message).try_into().map_err(|()| Error::<T>::BadVersion)?;
			// Actually send the XCM.
			Self::do_send(origin, dest, message)
		}

		/// Teleport some assets from the local chain to some destination chain.
		#[pallet::call_index(2)]
		#[pallet::weight(Weight::default())]
		pub fn teleport_assets(
			origin: OriginFor<T>,
			dest: Box<VersionedLocation>,
			beneficiary: Box<VersionedLocation>,
			assets: Box<VersionedAssets>,
			fee_asset_item: u32,
		) -> DispatchResult {
			let dest: Location = (*dest).try_into().map_err(|()| Error::<T>::BadVersion)?;
			let beneficiary: Location =
				(*beneficiary).try_into().map_err(|()| Error::<T>::BadVersion)?;
			let assets: Assets = (*assets).try_into().map_err(|()| Error::<T>::BadVersion)?;
			// Actually teleport the assets.
			Self::do_teleport_assets(origin, dest, beneficiary, assets, fee_asset_item)
		}

		/// Transfer some assets from the local chain to the destination chain through their local,
		/// destination or remote reserve.
		#[pallet::call_index(3)]
		#[pallet::weight(Weight::default())]
		pub fn reserve_transfer_assets(
			origin: OriginFor<T>,
			dest: Box<VersionedLocation>,
			beneficiary: Box<VersionedLocation>,
			assets: Box<VersionedAssets>,
			fee_asset_item: u32,
		) -> DispatchResult {
			let dest: Location = (*dest).try_into().map_err(|()| Error::<T>::BadVersion)?;
			let beneficiary: Location =
				(*beneficiary).try_into().map_err(|()| Error::<T>::BadVersion)?;
			let assets: Assets = (*assets).try_into().map_err(|()| Error::<T>::BadVersion)?;
			// Actually reserve transfer the assets.
			Self::do_reserve_transfer_assets(origin, dest, beneficiary, assets, fee_asset_item)
		}
	}
}

impl<T: Config> Pallet<T> {
	/// Execute an XCM locally on this chain on behalf of `origin`.
	pub fn do_execute(origin: OriginFor<T>, message: Xcm<()>) -> DispatchResult {
		let execute_origin: Location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
		T::XcmExecutor::execute(execute_origin, message).map_err(|_| Error::<T>::ExecutorError)?;
		Ok(())
	}

	/// Relay an XCM `message` from a given `interior` location in this context to a given `dest`
	/// location.
	pub fn do_send(origin: OriginFor<T>, dest: Location, mut message: Xcm<()>) -> DispatchResult {
		let origin_location = T::SendXcmOrigin::ensure_origin(origin)?;
		let interior: Junctions =
			origin_location.try_into().map_err(|_| Error::<T>::InvalidOrigin)?;
		if interior != Junctions::Here {
			message.0.insert(0, DescendOrigin(interior));
		}
		let (ticket, _) = T::XcmRouter::validate(&mut Some(dest), &mut Some(message))
			.map_err(|_| Error::<T>::RouterError)?;
		let _message_id = T::XcmRouter::deliver(ticket).map_err(|_| Error::<T>::RouterError)?;
		Ok(())
	}

	pub fn do_teleport_assets(
		origin: OriginFor<T>,
		dest: Location,
		beneficiary: Location,
		assets: Assets,
		// The index into `assets` of the item which should be used to pay fees.
		// We don't use this in our naive implementation.
		_fee_asset_item: u32,
	) -> DispatchResult {
		todo!("{:?} {:?} {:?}", dest, beneficiary, assets)
	}

	pub fn do_reserve_transfer_assets(
		_origin: OriginFor<T>,
		_dest: Location,
		_beneficiary: Location,
		_assets: Assets,
		_fee_asset_item: u32,
	) -> DispatchResult {
		// There are 3 different reserve transfer scenarios:
		// - A local reserve transfer: reserve-transfer `asset` to `dest`, using local chain as
		//   reserve.
		// - A destination reserve transfer: reserve-transfer `asset` to `dest`, using `dest` as
		//   reserve.
		// - A remote reserve transfer: reserve-transfer `asset` to `dest`, using remote chain
		//   `Location` as reserve.
		//
		// This is a lot to do in this workshop, but a welcome challenge for the reader to
		// implement.
		unimplemented!()
	}
}
}
diff --git a/fundamentals/src/pallet_xcm.rs b/fundamentals/src/pallet_xcm.rs
index baec7b7..7169462 100644
--- a/fundamentals/src/pallet_xcm.rs
+++ b/fundamentals/src/pallet_xcm.rs
@@ -132,6 +132,13 @@ impl<T: Config> Pallet<T> {
 	/// Relay an XCM `message` from a given `interior` location in this context to a given `dest`
 	/// location.
 	pub fn do_send(origin: OriginFor<T>, dest: Location, mut message: Xcm<()>) -> DispatchResult {
+		/* TODO:
+			- Use `SendXcmOrigin` to "ensure" that `origin` is valid to send XCM, and assign the resulting location to `origin_location`.
+			- Convert `origin_location` to `interior: Junctions`, else return `InvalidOrigin`.
+			- If `interior` is not equal to `Junctions::Here`, insert into the XCM `DescendOrigin(interior)`.
+			- Use `XcmRouter` to `validate` the `dest` and `message` is valid, and create a `ticket`, else you should return a `RouterError`.
+			- Finally, use the `XcmRouter` to `deliver` the `ticket`, else return `RouterError`.
+		*/
 		todo!("{:?} {:?}", dest, message)
 	}
 
diff --git a/fundamentals/src/pallet_xcm.rs b/fundamentals/src/pallet_xcm.rs
index 7169462..2df57c9 100644
--- a/fundamentals/src/pallet_xcm.rs
+++ b/fundamentals/src/pallet_xcm.rs
@@ -132,14 +132,16 @@ impl<T: Config> Pallet<T> {
 	/// Relay an XCM `message` from a given `interior` location in this context to a given `dest`
 	/// location.
 	pub fn do_send(origin: OriginFor<T>, dest: Location, mut message: Xcm<()>) -> DispatchResult {
-		/* TODO:
-			- Use `SendXcmOrigin` to "ensure" that `origin` is valid to send XCM, and assign the resulting location to `origin_location`.
-			- Convert `origin_location` to `interior: Junctions`, else return `InvalidOrigin`.
-			- If `interior` is not equal to `Junctions::Here`, insert into the XCM `DescendOrigin(interior)`.
-			- Use `XcmRouter` to `validate` the `dest` and `message` is valid, and create a `ticket`, else you should return a `RouterError`.
-			- Finally, use the `XcmRouter` to `deliver` the `ticket`, else return `RouterError`.
-		*/
-		todo!("{:?} {:?}", dest, message)
+		let origin_location = T::SendXcmOrigin::ensure_origin(origin)?;
+		let interior: Junctions =
+			origin_location.try_into().map_err(|_| Error::<T>::InvalidOrigin)?;
+		if interior != Junctions::Here {
+			message.0.insert(0, DescendOrigin(interior));
+		}
+		let (ticket, _) = T::XcmRouter::validate(&mut Some(dest), &mut Some(message))
+			.map_err(|_| Error::<T>::RouterError)?;
+		let _message_id = T::XcmRouter::deliver(ticket).map_err(|_| Error::<T>::RouterError)?;
+		Ok(())
 	}
 
 	pub fn do_teleport_assets(