use super::*;
use bounded_collections::ConstU32;

// The following constants are copied from polkavm's `MemoryMap` source file.
const ADDRESS_SPACE_SIZE: u64 = 0x100000000_u64;
const VM_MAX_PAGE_SIZE: u32 = 0x10000;
const VM_ADDRESS_SPACE_TOP: u32 = (ADDRESS_SPACE_SIZE - VM_MAX_PAGE_SIZE as u64) as u32;
const VM_ADDR_RETURN_TO_HOST: u32 = 0xffff0000;

/// Plain-old-data struct of the same length and layout to `ValKeyset` struct. This does not
/// bring in any cryptography.
#[derive(Copy, Clone, Encode, Decode, Debug, Eq, PartialEq)]
pub struct OpaqueValKeyset {
	pub ed25519: OpaqueEd25519Public,
	pub bandersnatch: OpaqueBandersnatchPublic,
	pub metadata: OpaqueValidatorMetadata,
}
impl Default for OpaqueValKeyset {
	fn default() -> Self {
		Self {
			ed25519: OpaqueEd25519Public::zero(),
			bandersnatch: OpaqueBandersnatchPublic::zero(),
			metadata: OpaqueValidatorMetadata::zero(),
		}
	}
}

/// The opaque keys for each validator.
pub type OpaqueValKeysets = FixedVec<OpaqueValKeyset, ConstU32<{ VAL_COUNT as u32 }>>;

#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub enum RootIdentifier {
	Direct(SegmentTreeRoot),
	Indirect(WorkPackageHash),
}

impl From<SegmentTreeRoot> for RootIdentifier {
	fn from(root: SegmentTreeRoot) -> Self {
		Self::Direct(root)
	}
}
impl From<WorkPackageHash> for RootIdentifier {
	fn from(hash: WorkPackageHash) -> Self {
		Self::Indirect(hash)
	}
}
impl TryFrom<RootIdentifier> for SegmentTreeRoot {
	type Error = WorkPackageHash;
	fn try_from(root: RootIdentifier) -> Result<Self, Self::Error> {
		match root {
			RootIdentifier::Direct(root) => Ok(root),
			RootIdentifier::Indirect(hash) => Err(hash),
		}
	}
}

#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct ImportSpec {
	pub root: RootIdentifier,
	pub index: u16,
}

impl Encode for ImportSpec {
	fn encode_to<T: scale::Output + ?Sized>(&self, dest: &mut T) {
		let off = match &self.root {
			RootIdentifier::Direct(root) => {
				root.encode_to(dest);
				0
			},
			RootIdentifier::Indirect(hash) => {
				hash.encode_to(dest);
				1 << 15
			},
		};
		(self.index + off).encode_to(dest);
	}
}

impl Decode for ImportSpec {
	fn decode<I: scale::Input>(input: &mut I) -> Result<Self, scale::Error> {
		let h = Hash::decode(input)?;
		let i = u16::decode(input)?;
		let root = if i & (1 << 15) == 0 {
			SegmentTreeRoot::from(h).into()
		} else {
			WorkPackageHash::from(h).into()
		};
		Ok(Self { root, index: i & !(1 << 15) })
	}
}

#[derive(Clone, Encode, Decode, Debug)]
pub struct ExtrinsicSpec {
	pub hash: ExtrinsicHash,
	pub len: u32,
}

#[derive(Clone, Encode, Decode, Debug)]
pub struct GenericWorkItem<Xt> {
	/// Service identifier to which this work item relates.
	pub service: ServiceId,
	/// The service's code hash at the time of reporting. This must be available in-core at the
	/// time of the lookup-anchor block.
	pub code_hash: CodeHash,
	/// Payload blob.
	pub payload: WorkPayload,
	/// Gas limit to execute this work item.
	pub gas_limit: Gas,
	/// Sequence of imported data segments.
	pub import_segments: WorkItemImportsVec,
	/// Extrinsics.
	pub extrinsics: Vec<Xt>,
	/// Number of segments exported by this work item.
	pub export_count: u16,
}

pub type WorkItemImportsVec = BoundedVec<ImportSpec, ConstU32<{ MAX_IMPORTS }>>;

/// Just a Blake2-256 hash of an EncodedWorkPackage.
#[derive(Clone, Encode, Decode, Debug, Eq, PartialEq)]
pub struct RefineContext {
	/// The most recent header hash of the chain when building. This must be no more than
	/// `RECENT_BLOCKS` blocks old when reported.
	pub anchor: HeaderHash,
	/// Must be state root of block `anchor`. This is checked on-chain when reported.
	pub state_root: StateRootHash,
	/// Must be Beefy root of block `anchor`. This is checked on-chain when reported.
	pub beefy_root: MmrPeakHash,
	/// The hash of a header of a block which is final. Availability will not succeed unless a
	/// super-majority of validators have attested to this.
	/// Preimage `lookup`s will be judged according to this block.
	///
	/// NOTE: Storage pallet may not cycle more frequently than 48 hours (24 hours above
	///   + 24 hours dispute period).
	pub lookup_anchor: HeaderHash,
	/// The slot of `lookup_anchor` on the chain. This is checked in availability and the
	/// report's package will not be made available without it being correct.
	/// This value must be at least `anchor_slot + 14400`.
	pub lookup_anchor_slot: Slot,
	/// An optional hash of a Work Package, a report of which must be reported prior to this one.
	/// This is checked on-chain when reported.
	pub prerequisites: VecSet<WorkPackageHash>,
}

#[derive(Clone, Encode, Decode, Debug)]
pub struct GenericWorkPackage<Xt> {
	/// Authorization token.
	pub authorization: Authorization,
	/// Service identifier.
	pub auth_code_host: ServiceId,
	/// Authorizer.
	pub authorizer: Authorizer,
	/// Refinement context.
	pub context: RefineContext,
	/// Sequence of work items.
	pub items: GenericWorkItems<Xt>,
}

pub type GenericWorkItems<Xt> = BoundedVec<GenericWorkItem<Xt>, ConstU32<MAX_WORK_ITEMS>>;
pub type WorkItems = GenericWorkItems<ExtrinsicSpec>;

pub type WorkItem = GenericWorkItem<ExtrinsicSpec>;
pub type FatWorkItem = GenericWorkItem<Vec<u8>>;
pub type RefWorkItem = GenericWorkItem<[u8]>;

pub type WorkPackage = GenericWorkPackage<ExtrinsicSpec>;
pub type FatWorkPackage = GenericWorkPackage<Vec<u8>>;
pub type RefWorkPackage = GenericWorkPackage<[u8]>;

#[derive(Clone, Encode, Decode, Debug)]
pub struct Authorizer {
	/// Authorization code hash.
	pub code_hash: CodeHash,
	/// Parameters blob.
	pub param: AuthParam,
}

impl Authorizer {
	pub fn any() -> Self {
		Self { code_hash: CodeHash::zero(), param: Default::default() }
	}
}

#[derive(Clone, Encode, Decode, Debug)]
pub struct PackageInfo {
	pub package_hash: WorkPackageHash,
	pub context: RefineContext,
	pub authorizer: Authorizer,
	pub auth_output: AuthOutput,
}

/// Potential errors encountered during the refinement of a [`WorkItem`].
///
/// Although additional errors may be generated internally by the PVM engine,
/// these are the specific errors designated by the GP for the [`WorkResult`]
/// and that are eligible to be forwarded to the accumulate process as part
/// of the [`AccumulateItem`].
#[derive(Clone, Encode, Decode, Debug, Eq, PartialEq)]
pub enum WorkError {
	/// Gas exhausted (∞).
	OutOfGas = 1,
	/// Unexpected terminatin (☇).
	Panic = 2,
	/// Bad code for the service (`BAD`).
	///
	/// This may occur due to an unknown service identifier or unavailable code preimage.
	BadCode = 3,
	/// Out of bounds code size (`BIG`).
	CodeOversize = 4,
}

#[derive(Clone, Encode, Decode, Debug, Eq, PartialEq)]
pub struct WorkResult {
	pub service: ServiceId,
	/// The service's code hash at the time of reporting. This must be available in-core at the
	/// time of the lookup-anchor block.
	pub code_hash: CodeHash,
	pub payload_hash: PayloadHash,
	pub gas: Gas,
	#[codec(encoded_as = "CompactRefineResult")]
	pub result: Result<WorkOutput, WorkError>,
}

#[derive(Debug, Encode, Decode)]
pub struct OnChainContext {
	pub slot: Slot,
	pub id: ServiceId,
}

#[derive(Debug, Encode, Decode)]
pub struct AccumulateItem {
	pub package: WorkPackageHash,
	pub auth: AuthOutput,
	pub payload: PayloadHash,
	#[codec(encoded_as = "CompactRefineResult")]
	pub result: Result<WorkOutput, WorkError>,
}

#[derive(Debug, Encode, Decode)]
pub struct AccumulateParams {
	pub context: OnChainContext,
	pub results: Vec<AccumulateItem>,
}

#[derive(Debug, Encode)]
pub struct AccumulateParamsRef<'a> {
	pub context: OnChainContext,
	pub results: &'a [AccumulateItem],
}

/// A deferred transfer.
///
/// Each of these must be executed following all `accumulate` calls in the
/// ordered first by descending `source` service ID and then by the order in which the source
/// service enqueued them.
#[derive(Debug, Clone, Encode, Decode)]
pub struct TransferRecord {
	pub source: ServiceId,
	pub destination: ServiceId,
	pub amount: Balance,
	pub memo: Memo,
	pub gas_limit: Gas,
}

impl Default for TransferRecord {
	fn default() -> Self {
		Self {
			source: Default::default(),
			destination: Default::default(),
			amount: Default::default(),
			memo: Memo::zero(),
			gas_limit: Default::default(),
		}
	}
}

#[derive(Debug, Encode, Decode)]
pub struct OnTransferParams {
	pub context: OnChainContext,
	pub transfers: Vec<TransferRecord>,
}

#[derive(Debug, Encode)]
pub struct OnTransferParamsRef<'a> {
	pub context: OnChainContext,
	pub transfers: &'a [TransferRecord],
}

#[derive(Debug, Encode, Decode)]
pub struct RefineParams {
	pub id: ServiceId,
	pub payload: WorkPayload,
	pub package_info: PackageInfo,
	pub extrinsics: Vec<Vec<u8>>,
}

#[derive(Debug, Encode)]
pub struct RefineParamsRef<'a, T: 'a + core::fmt::Debug + Encode> {
	pub id: ServiceId,
	pub payload: &'a WorkPayload,
	pub package_info: &'a PackageInfo,
	pub extrinsics: &'a [T],
}

#[derive(Debug, Clone, Encode, Decode, MaxEncodedLen)]
pub struct ServiceInfo {
	pub code_hash: CodeHash,
	pub balance: Balance,
	/// The minimum amount of gas which must be provided to this service's `accumulate` for each
	/// work item it must process.
	pub min_item_gas: Gas,
	/// The minimum amount of gas which must be provided to this service's `on_transfer` for each
	/// memo (i.e. transfer receipt) it must process.
	pub min_memo_gas: Gas,
	pub bytes: u64,
	pub items: u32,
}

impl scale::ConstEncodedLen for ServiceInfo {}

/// Args for invoking an inner PVM.
#[repr(C)]
#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode, MaxEncodedLen, Default)]
pub struct InvokeArgsT<RegT> {
	pub gas: i64,
	pub regs: [RegT; 13],
}

impl InvokeArgsT<u32> {
	pub const BYTESIZE: usize = {
		// We calculate this manually instead of using `size_of` on the struct
		// due to the extra alignment padding Rust puts in there.
		let size = core::mem::size_of::<u64>() + core::mem::size_of::<u32>() * 13;

		// Make sure the size matches with the size from the GP.
		assert!(size >= 60);
		size
	};

	pub fn from_bytes(xs: &[u8]) -> Self {
		// Extracting the args this way might be ugly, but we can avoid having to use `unsafe`.
		assert!(xs.len() >= Self::BYTESIZE); // To help the compiler remove bound checks.
		Self {
			gas: i64::from_le_bytes([xs[0], xs[1], xs[2], xs[3], xs[4], xs[5], xs[6], xs[7]]),
			regs: {
				let mut n = 8;
				[(); 13].map(|_| {
					let value = u32::from_le_bytes([xs[n], xs[n + 1], xs[n + 2], xs[n + 3]]);
					n += 4;
					value
				})
			},
		}
	}

	pub fn to_bytes(&self, output: &mut [u8]) {
		assert!(output.len() >= Self::BYTESIZE);
		output[0..8].copy_from_slice(&self.gas.to_le_bytes());
		for (nth_reg, value) in self.regs.into_iter().enumerate() {
			output[8 + nth_reg * 4..8 + (nth_reg + 1) * 4].copy_from_slice(&value.to_le_bytes());
		}
	}
}

impl InvokeArgsT<u64> {
	pub const BYTESIZE: usize = {
		// We calculate this manually instead of using `size_of` on the struct
		// due to the extra alignment padding Rust puts in there.
		let size = core::mem::size_of::<u64>() + core::mem::size_of::<u64>() * 13;

		// Make sure the size matches with the size from the GP.
		assert!(size >= 112);
		size
	};

	pub fn from_bytes(xs: &[u8]) -> Self {
		// Extracting the args this way might be ugly, but we can avoid having to use `unsafe`.
		assert!(xs.len() >= Self::BYTESIZE); // To help the compiler remove bound checks.
		Self {
			gas: i64::from_le_bytes([xs[0], xs[1], xs[2], xs[3], xs[4], xs[5], xs[6], xs[7]]),
			regs: {
				let mut n = 8;
				[(); 13].map(|_| {
					let value = u64::from_le_bytes([
						xs[n],
						xs[n + 1],
						xs[n + 2],
						xs[n + 3],
						xs[n + 4],
						xs[n + 5],
						xs[n + 6],
						xs[n + 7],
					]);
					n += 8;
					value
				})
			},
		}
	}

	pub fn to_bytes(&self, output: &mut [u8]) {
		assert!(output.len() >= Self::BYTESIZE);
		output[0..8].copy_from_slice(&self.gas.to_le_bytes());
		for (nth_reg, value) in self.regs.into_iter().enumerate() {
			output[8 + nth_reg * 8..8 + (nth_reg + 1) * 8].copy_from_slice(&value.to_le_bytes());
		}
	}
}

impl<RegT> InvokeArgsT<RegT>
where
	RegT: Copy + Default + From<u32>,
{
	/// Initialize the registers for the `invoke` call.
	pub fn new(gas: i64) -> Self {
		let mut args = Self { gas, ..Self::default() };
		args.set_reg(Reg::RA, RegT::from(VM_ADDR_RETURN_TO_HOST));
		args.set_reg(Reg::SP, RegT::from(VM_ADDRESS_SPACE_TOP));
		args
	}

	/// Get the value of the specified register.
	pub fn reg(&self, reg: Reg) -> RegT {
		self.regs[reg as usize]
	}

	/// Set the value of the specified register.
	pub fn set_reg(&mut self, reg: Reg, value: RegT) {
		self.regs[reg as usize] = value;
	}

	/// Set the contents of `A0` and optinally `A1` registers that typically store the return value
	/// of a function call.
	pub fn set_return_value<T: SetReturnValue<RegT>>(&mut self, value: T) {
		value.set_return_value(self);
	}

	/// Get the value of the `i`-th argument's register.
	pub fn get_arg_reg(&self, i: usize) -> RegT {
		self.reg(Reg::ARG_REGS[i])
	}

	/// Get the value of the `i`-th argument.
	pub fn get_arg<T: Arg<RegT>>(&mut self, i: &mut usize) -> T {
		Arg::get(self, i)
	}

	/// Set the value of the `i`-th argument's register.
	pub fn set_arg_reg(&mut self, i: usize, value: RegT) {
		self.set_reg(Reg::ARG_REGS[i], value)
	}

	/// Set the value of the `i`-th argument.
	pub fn set_arg<T: Arg<RegT>>(&mut self, i: &mut usize, value: T) {
		value.set(self, i)
	}
}

#[cfg(target_pointer_width = "32")]
pub type InvokeArgs = InvokeArgsT<u32>;

#[cfg(target_pointer_width = "64")]
pub type InvokeArgs = InvokeArgsT<u64>;

impl scale::ConstEncodedLen for InvokeArgsT<u32> {}
impl scale::ConstEncodedLen for InvokeArgsT<u64> {}

/// Available registers.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
#[repr(u32)]
pub enum Reg {
	RA = 0,
	SP = 1,
	T0 = 2,
	T1 = 3,
	T2 = 4,
	S0 = 5,
	S1 = 6,
	A0 = 7,
	A1 = 8,
	A2 = 9,
	A3 = 10,
	A4 = 11,
	A5 = 12,
}

impl Reg {
	pub const ARG_REGS: [Reg; 9] =
		[Reg::A0, Reg::A1, Reg::A2, Reg::A3, Reg::A4, Reg::A5, Reg::T0, Reg::T1, Reg::T2];
}

pub trait SetReturnValue<RegT> {
	fn set_return_value(self, args: &mut InvokeArgsT<RegT>);
}

impl<RegT> SetReturnValue<RegT> for () {
	fn set_return_value(self, _args: &mut InvokeArgsT<RegT>) {}
}

impl SetReturnValue<u32> for u32 {
	fn set_return_value(self, args: &mut InvokeArgsT<u32>) {
		args.set_reg(Reg::A0, self);
	}
}

impl SetReturnValue<u32> for u64 {
	fn set_return_value(self, args: &mut InvokeArgsT<u32>) {
		args.set_reg(Reg::A0, self as u32);
		args.set_reg(Reg::A1, (self >> 32) as u32);
	}
}

impl SetReturnValue<u64> for u32 {
	fn set_return_value(self, args: &mut InvokeArgsT<u64>) {
		args.set_reg(Reg::A0, u64::from(self));
	}
}

impl SetReturnValue<u64> for u64 {
	fn set_return_value(self, args: &mut InvokeArgsT<u64>) {
		args.set_reg(Reg::A0, self);
	}
}

set_return_value_impl_as! {
	bool => u32,
	u8 => u32,
	u16 => u32,
	i8 => u32,
	i16 => u32,
	i32 => u32,
	i64 => u64
}

#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))]
set_return_value_impl_as! {
	isize => usize
}

pub trait Arg<RegT> {
	fn get(args: &InvokeArgsT<RegT>, i: &mut usize) -> Self
	where
		Self: Sized;

	fn set(self, args: &mut InvokeArgsT<RegT>, i: &mut usize);
}

impl Arg<u32> for u32 {
	fn get(args: &InvokeArgsT<u32>, i: &mut usize) -> Self {
		let value = args.get_arg_reg(*i);
		*i += 1;
		value
	}

	fn set(self, args: &mut InvokeArgsT<u32>, i: &mut usize) {
		args.set_arg_reg(*i, self);
		*i += 1;
	}
}

impl Arg<u64> for u32 {
	fn get(args: &InvokeArgsT<u64>, i: &mut usize) -> Self {
		let value = args.get_arg_reg(*i);
		*i += 1;
		value as u32
	}

	fn set(self, args: &mut InvokeArgsT<u64>, i: &mut usize) {
		args.set_arg_reg(*i, u64::from(self));
		*i += 1;
	}
}

impl Arg<u32> for u64 {
	fn get(args: &InvokeArgsT<u32>, i: &mut usize) -> Self {
		let value_lo = args.get_arg_reg(*i);
		*i += 1;
		let value_hi = args.get_arg_reg(*i);
		*i += 1;
		(value_lo as u64) | ((value_hi as u64) << 32)
	}

	fn set(self, args: &mut InvokeArgsT<u32>, i: &mut usize) {
		let value_lo = self as u32;
		let value_hi = (self >> 32) as u32;
		value_lo.set(args, i);
		value_hi.set(args, i);
	}
}

impl Arg<u64> for u64 {
	fn get(args: &InvokeArgsT<u64>, i: &mut usize) -> Self {
		let value = args.get_arg_reg(*i);
		*i += 1;
		value
	}

	fn set(self, args: &mut InvokeArgsT<u64>, i: &mut usize) {
		args.set_arg_reg(*i, self);
		*i += 1;
	}
}

macro_rules! impl_conv_common {
	($reg_ty:ty) => {
		#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))]
		impl SetReturnValue<$reg_ty> for usize {
			#[cfg(target_pointer_width = "32")]
			fn set_return_value(self, args: &mut InvokeArgsT<$reg_ty>) {
				(self as u32).set_return_value(args)
			}

			#[cfg(target_pointer_width = "64")]
			fn set_return_value(self, args: &mut InvokeArgsT<$reg_ty>) {
				(self as u64).set_return_value(args)
			}
		}

		#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))]
		impl Arg<$reg_ty> for usize {
			#[cfg(target_pointer_width = "32")]
			fn get(args: &InvokeArgsT<$reg_ty>, i: &mut usize) -> Self {
				u32::get(args, i) as usize
			}

			#[cfg(target_pointer_width = "64")]
			fn get(args: &InvokeArgsT<$reg_ty>, i: &mut usize) -> Self {
				u64::get(args, i) as usize
			}

			#[cfg(target_pointer_width = "32")]
			fn set(self, args: &mut InvokeArgsT<$reg_ty>, i: &mut usize) {
				(self as u32).set(args, i);
			}

			#[cfg(target_pointer_width = "64")]
			fn set(self, args: &mut InvokeArgsT<$reg_ty>, i: &mut usize) {
				(self as u64).set(args, i);
			}
		}

		#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))]
		impl<T> Arg<$reg_ty> for *const T {
			fn get(args: &InvokeArgsT<$reg_ty>, i: &mut usize) -> Self {
				usize::get(args, i) as Self
			}

			fn set(self, args: &mut InvokeArgsT<$reg_ty>, i: &mut usize) {
				(self as usize).set(args, i);
			}
		}

		#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))]
		impl<T> Arg<$reg_ty> for *mut T {
			fn get(args: &InvokeArgsT<$reg_ty>, i: &mut usize) -> Self {
				usize::get(args, i) as Self
			}

			fn set(self, args: &mut InvokeArgsT<$reg_ty>, i: &mut usize) {
				(self as usize).set(args, i);
			}
		}

		impl Arg<$reg_ty> for bool {
			fn get(args: &InvokeArgsT<$reg_ty>, i: &mut usize) -> Self {
				u32::get(args, i) != 0
			}

			fn set(self, args: &mut InvokeArgsT<$reg_ty>, i: &mut usize) {
				(self as u32).set(args, i);
			}
		}
	};
}

impl_conv_common!(u32);
impl_conv_common!(u64);

arg_impl_as! {
	u8 => u32,
	u16 => u32,
	i8 => u32,
	i16 => u32,
	i32 => u32,
	i64 => u64
}

#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))]
arg_impl_as! {
	isize => usize
}

macro_rules! set_return_value_impl_as {
    ($($type:ty => $as_type:ty),+) => {
        $(
            impl SetReturnValue<u32> for $type {
                fn set_return_value(self, args: &mut InvokeArgsT<u32>) {
                    (self as $as_type).set_return_value(args);
                }
            }

            impl SetReturnValue<u64> for $type {
                fn set_return_value(self, args: &mut InvokeArgsT<u64>) {
                    (self as $as_type).set_return_value(args);
                }
            }
        )+
    };
}

use set_return_value_impl_as;

macro_rules! arg_impl_as {
    ($($type:ty => $as_type:ty),+) => {
        $(
            impl Arg<u32> for $type {
                fn get(args: &InvokeArgsT<u32>, i: &mut usize) -> Self {
                    <$as_type>::get(args, i) as Self
                }

                fn set(self, args: &mut InvokeArgsT<u32>, i: &mut usize) {
                    (self as $as_type).set(args, i);
                }
            }

            impl Arg<u64> for $type {
                fn get(args: &InvokeArgsT<u64>, i: &mut usize) -> Self {
                    <$as_type>::get(args, i) as Self
                }

                fn set(self, args: &mut InvokeArgsT<u64>, i: &mut usize) {
                    (self as $as_type).set(args, i);
                }
            }
        )+
    };
}

use arg_impl_as;

#[rustfmt::skip]
#[macro_export]
macro_rules! index_for_hostcall {
    // General functions.
	(gas) => {0};
	(lookup) => {1};
	(read) => {2};
	(write) => {3};
	(info) => {4};
    // Accumulate functions.
	(bless) => {5};
	(assign) => {6};
	(designate) => {7};
	(checkpoint) => {8};
	(new) => {9};
	(upgrade) => {10};
	(transfer) => {11};
	(quit) => {12};
	(solicit) => {13};
	(forget) => {14};
    // Refine functions.
	(historical_lookup) => {15};
	(import) => {16};
	(export) => {17};
	(machine) => {18};
	(peek) => {19};
	(poke) => {20};
	(zero) => {21};
	(void) => {22};
	(invoke) => {23};
	(expunge) => {24};

	// Not part of the GP.
	(log) => {100};
}

pub use index_for_hostcall;

// Refine result used for compact encoding of work result as prescribed by GP.
struct CompactRefineResult(Result<WorkOutput, WorkError>);
struct CompactRefineResultRef<'a>(&'a Result<WorkOutput, WorkError>);

impl From<CompactRefineResult> for Result<WorkOutput, WorkError> {
	fn from(value: CompactRefineResult) -> Self {
		value.0
	}
}

impl<'a> From<&'a Result<WorkOutput, WorkError>> for CompactRefineResultRef<'a> {
	fn from(value: &'a Result<WorkOutput, WorkError>) -> Self {
		CompactRefineResultRef(value)
	}
}

impl<'a> scale::EncodeAsRef<'a, Result<WorkOutput, WorkError>> for CompactRefineResult {
	type RefType = CompactRefineResultRef<'a>;
}

impl Encode for CompactRefineResult {
	fn encode_to<T: scale::Output + ?Sized>(&self, dest: &mut T) {
		CompactRefineResultRef(&self.0).encode_to(dest)
	}
}

impl<'a> Encode for CompactRefineResultRef<'a> {
	fn encode_to<T: scale::Output + ?Sized>(&self, dest: &mut T) {
		match &self.0 {
			Ok(o) => {
				dest.push_byte(0);
				o.encode_to(dest)
			},
			Err(e) => e.encode_to(dest),
		}
	}
}

impl Decode for CompactRefineResult {
	fn decode<I: scale::Input>(input: &mut I) -> Result<Self, scale::Error> {
		match input.read_byte()? {
			0 => Ok(Self(Ok(WorkOutput::decode(input)?))),
			e => Ok(Self(Err(WorkError::decode(&mut &[e][..])?))),
		}
	}
}

#[cfg(test)]
mod tests {
	use super::*;

	#[test]
	fn test_invoke_args_32_can_be_converted_to_bytes() {
		let args = InvokeArgsT::<u32> {
			gas: 6245510980648378913,
			regs: [
				3364686619, 2100728089, 1436741328, 1388289407, 722770902, 1607395728, 142847709,
				4089017013, 3970153417, 2326764831, 1722929565, 904569242, 3653777900,
			],
		};

		let mut bytes = [0; core::mem::size_of::<InvokeArgsT<u32>>()];
		args.to_bytes(&mut bytes);
		assert_eq!(InvokeArgsT::<u32>::from_bytes(&bytes), args);
	}

	#[test]
	fn test_invoke_args_64_can_be_converted_to_bytes() {
		let args = InvokeArgsT::<u64> {
			gas: 6245510980648378913,
			regs: [
				3897305703111539400,
				5760987759726533151,
				5505591633088512456,
				7520817185631591303,
				8571768649504054185,
				7628499073207346885,
				5159587593731446957,
				12549592788806540823,
				4259284988670782509,
				1881634007696300103,
				3283781648033715868,
				6132298261455339512,
				3612762733779559463,
			],
		};

		let mut bytes = [0; core::mem::size_of::<InvokeArgsT<u64>>()];
		args.to_bytes(&mut bytes);
		assert_eq!(InvokeArgsT::<u64>::from_bytes(&bytes), args);
	}

	#[test]
	fn compact_refine_result_codec() {
		let enc_dec = |exp_res, exp_buf: &[u8]| {
			let buf = CompactRefineResultRef(&exp_res).encode();
			assert_eq!(buf, exp_buf);
			let res = CompactRefineResult::decode(&mut &buf[..]).unwrap();
			assert_eq!(res.0, exp_res);
		};

		enc_dec(Ok(vec![1, 2, 3].into()), &[0, 3, 1, 2, 3]);
		enc_dec(Err(WorkError::OutOfGas), &[1]);
		enc_dec(Err(WorkError::Panic), &[2]);
		enc_dec(Err(WorkError::BadCode), &[3]);
		enc_dec(Err(WorkError::CodeOversize), &[4]);
	}
}
