#![cfg_attr(not(feature = "std"), no_std)]
use sp_std::{
	borrow::Cow, marker::PhantomData, mem, iter::Iterator, result, vec::Vec,
};
#[cfg(feature = "std")]
mod wasmi_impl;
#[cfg(feature = "std")]
pub type Result<T> = result::Result<T, String>;
#[cfg(not(feature = "std"))]
pub type Result<T> = result::Result<T, &'static str>;
#[derive(Copy, Clone, PartialEq, Debug, Eq)]
pub enum ValueType {
	
	I32,
	
	I64,
	
	F32,
	
	F64,
}
impl From<ValueType> for u8 {
	fn from(val: ValueType) -> u8 {
		match val {
			ValueType::I32 => 0,
			ValueType::I64 => 1,
			ValueType::F32 => 2,
			ValueType::F64 => 3,
		}
	}
}
impl sp_std::convert::TryFrom<u8> for ValueType {
	type Error = ();
	fn try_from(val: u8) -> sp_std::result::Result<ValueType, ()> {
		match val {
			0 => Ok(Self::I32),
			1 => Ok(Self::I64),
			2 => Ok(Self::F32),
			3 => Ok(Self::F64),
			_ => Err(()),
		}
	}
}
#[derive(PartialEq, Debug, Clone, Copy, codec::Encode, codec::Decode)]
pub enum Value {
	
	I32(i32),
	
	I64(i64),
	
	
	
	F32(u32),
	
	
	
	F64(u64),
}
impl Value {
	
	pub fn value_type(&self) -> ValueType {
		match self {
			Value::I32(_) => ValueType::I32,
			Value::I64(_) => ValueType::I64,
			Value::F32(_) => ValueType::F32,
			Value::F64(_) => ValueType::F64,
		}
	}
	
	pub fn as_i32(&self) -> Option<i32> {
		match self {
			Self::I32(val) => Some(*val),
			_ => None,
		}
	}
}
mod private {
	pub trait Sealed {}
	impl Sealed for u8 {}
	impl Sealed for u16 {}
	impl Sealed for u32 {}
	impl Sealed for u64 {}
}
pub trait PointerType: Sized + private::Sealed {
	
	const SIZE: u32 = mem::size_of::<Self>() as u32;
}
impl PointerType for u8 {}
impl PointerType for u16 {}
impl PointerType for u32 {}
impl PointerType for u64 {}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct Pointer<T: PointerType> {
	ptr: u32,
	_marker: PhantomData<T>,
}
impl<T: PointerType> Pointer<T> {
	
	pub fn new(ptr: u32) -> Self {
		Self {
			ptr,
			_marker: Default::default(),
		}
	}
	
	
	
	
	
	pub fn offset(self, offset: u32) -> Option<Self> {
		offset.checked_mul(T::SIZE).and_then(|o| self.ptr.checked_add(o)).map(|ptr| {
			Self {
				ptr,
				_marker: Default::default(),
			}
		})
	}
	
	pub fn null() -> Self {
		Self::new(0)
	}
	
	pub fn cast<R: PointerType>(self) -> Pointer<R> {
		Pointer::new(self.ptr)
	}
}
impl<T: PointerType> From<u32> for Pointer<T> {
	fn from(ptr: u32) -> Self {
		Pointer::new(ptr)
	}
}
impl<T: PointerType> From<Pointer<T>> for u32 {
	fn from(ptr: Pointer<T>) -> Self {
		ptr.ptr
	}
}
impl<T: PointerType> From<Pointer<T>> for u64 {
	fn from(ptr: Pointer<T>) -> Self {
		u64::from(ptr.ptr)
	}
}
impl<T: PointerType> From<Pointer<T>> for usize {
	fn from(ptr: Pointer<T>) -> Self {
		ptr.ptr as _
	}
}
impl<T: PointerType> IntoValue for Pointer<T> {
	const VALUE_TYPE: ValueType = ValueType::I32;
	fn into_value(self) -> Value { Value::I32(self.ptr as _) }
}
impl<T: PointerType> TryFromValue for Pointer<T> {
	fn try_from_value(val: Value) -> Option<Self> {
		match val {
			Value::I32(val) => Some(Self::new(val as _)),
			_ => None,
		}
	}
}
pub type WordSize = u32;
#[derive(Eq, PartialEq, Debug, Clone)]
pub struct Signature {
	
	pub args: Cow<'static, [ValueType]>,
	
	pub return_value: Option<ValueType>,
}
impl Signature {
	
	pub fn new<T: Into<Cow<'static, [ValueType]>>>(args: T, return_value: Option<ValueType>) -> Self {
		Self {
			args: args.into(),
			return_value,
		}
	}
	
	pub fn new_with_args<T: Into<Cow<'static, [ValueType]>>>(args: T) -> Self {
		Self {
			args: args.into(),
			return_value: None,
		}
	}
}
#[cfg(feature = "std")]
pub trait MaybeRefUnwindSafe: std::panic::RefUnwindSafe {}
#[cfg(feature = "std")]
impl<T: std::panic::RefUnwindSafe> MaybeRefUnwindSafe for T {}
#[cfg(not(feature = "std"))]
pub trait MaybeRefUnwindSafe {}
#[cfg(not(feature = "std"))]
impl<T> MaybeRefUnwindSafe for T {}
pub trait Function: MaybeRefUnwindSafe + Send + Sync {
	
	fn name(&self) -> &str;
	
	fn signature(&self) -> Signature;
	
	fn execute(
		&self,
		context: &mut dyn FunctionContext,
		args: &mut dyn Iterator<Item = Value>,
	) -> Result<Option<Value>>;
}
impl PartialEq for dyn Function {
	fn eq(&self, other: &Self) -> bool {
		other.name() == self.name() && other.signature() == self.signature()
	}
}
pub trait FunctionContext {
	
	fn read_memory(&self, address: Pointer<u8>, size: WordSize) -> Result<Vec<u8>> {
		let mut vec = Vec::with_capacity(size as usize);
		vec.resize(size as usize, 0);
		self.read_memory_into(address, &mut vec)?;
		Ok(vec)
	}
	
	fn read_memory_into(&self, address: Pointer<u8>, dest: &mut [u8]) -> Result<()>;
	
	fn write_memory(&mut self, address: Pointer<u8>, data: &[u8]) -> Result<()>;
	
	fn allocate_memory(&mut self, size: WordSize) -> Result<Pointer<u8>>;
	
	fn deallocate_memory(&mut self, ptr: Pointer<u8>) -> Result<()>;
	
	fn sandbox(&mut self) -> &mut dyn Sandbox;
}
pub type MemoryId = u32;
pub trait Sandbox {
	
	fn memory_get(
		&mut self,
		memory_id: MemoryId,
		offset: WordSize,
		buf_ptr: Pointer<u8>,
		buf_len: WordSize,
	) -> Result<u32>;
	
	fn memory_set(
		&mut self,
		memory_id: MemoryId,
		offset: WordSize,
		val_ptr: Pointer<u8>,
		val_len: WordSize,
	) -> Result<u32>;
	
	fn memory_teardown(&mut self, memory_id: MemoryId) -> Result<()>;
	
	
	fn memory_new(&mut self, initial: u32, maximum: u32) -> Result<MemoryId>;
	
	fn invoke(
		&mut self,
		instance_id: u32,
		export_name: &str,
		args: &[u8],
		return_val: Pointer<u8>,
		return_val_len: WordSize,
		state: u32,
	) -> Result<u32>;
	
	fn instance_teardown(&mut self, instance_id: u32) -> Result<()>;
	
	fn instance_new(
		&mut self,
		dispatch_thunk_id: u32,
		wasm: &[u8],
		raw_env_def: &[u8],
		state: u32,
	) -> Result<u32>;
	
	
	
	
	fn get_global_val(&self, instance_idx: u32, name: &str) -> Result<Option<Value>>;
}
pub trait HostFunctions: 'static {
	
	fn host_functions() -> Vec<&'static dyn Function>;
}
#[impl_trait_for_tuples::impl_for_tuples(30)]
impl HostFunctions for Tuple {
	fn host_functions() -> Vec<&'static dyn Function> {
		let mut host_functions = Vec::new();
		for_tuples!( #( host_functions.extend(Tuple::host_functions()); )* );
		host_functions
	}
}
pub trait IntoValue {
	
	const VALUE_TYPE: ValueType;
	
	fn into_value(self) -> Value;
}
pub trait TryFromValue: Sized {
	
	fn try_from_value(val: Value) -> Option<Self>;
}
macro_rules! impl_into_and_from_value {
	(
		$(
			$type:ty, $( < $gen:ident >, )? $value_variant:ident,
		)*
	) => {
		$(
			impl $( <$gen> )? IntoValue for $type {
				const VALUE_TYPE: ValueType = ValueType::$value_variant;
				fn into_value(self) -> Value { Value::$value_variant(self as _) }
			}
			impl $( <$gen> )? TryFromValue for $type {
				fn try_from_value(val: Value) -> Option<Self> {
					match val {
						Value::$value_variant(val) => Some(val as _),
						_ => None,
					}
				}
			}
		)*
	}
}
impl_into_and_from_value! {
	u8, I32,
	u16, I32,
	u32, I32,
	u64, I64,
	i8, I32,
	i16, I32,
	i32, I32,
	i64, I64,
}
pub trait WritePrimitive<T: PointerType> {
	
	fn write_primitive(&mut self, ptr: Pointer<T>, t: T) -> Result<()>;
}
impl WritePrimitive<u32> for &mut dyn FunctionContext {
	fn write_primitive(&mut self, ptr: Pointer<u32>, t: u32) -> Result<()> {
		let r = t.to_le_bytes();
		self.write_memory(ptr.cast(), &r)
	}
}
impl WritePrimitive<u64> for &mut dyn FunctionContext {
	fn write_primitive(&mut self, ptr: Pointer<u64>, t: u64) -> Result<()> {
		let r = t.to_le_bytes();
		self.write_memory(ptr.cast(), &r)
	}
}
pub trait ReadPrimitive<T: PointerType> {
	
	fn read_primitive(&self, ptr: Pointer<T>) -> Result<T>;
}
impl ReadPrimitive<u32> for &mut dyn FunctionContext {
	fn read_primitive(&self, ptr: Pointer<u32>) -> Result<u32> {
		let mut r = [0u8; 4];
		self.read_memory_into(ptr.cast(), &mut r)?;
		Ok(u32::from_le_bytes(r))
	}
}
impl ReadPrimitive<u64> for &mut dyn FunctionContext {
	fn read_primitive(&self, ptr: Pointer<u64>) -> Result<u64> {
		let mut r = [0u8; 8];
		self.read_memory_into(ptr.cast(), &mut r)?;
		Ok(u64::from_le_bytes(r))
	}
}
#[derive(Clone, Copy, PartialEq, codec::Encode, codec::Decode, Debug)]
pub enum ReturnValue {
	
	Unit,
	
	Value(Value),
}
impl From<Value> for ReturnValue {
	fn from(v: Value) -> ReturnValue {
		ReturnValue::Value(v)
	}
}
impl ReturnValue {
	
	
	
	
	
	
	pub const ENCODED_MAX_SIZE: usize = 10;
}
#[cfg(test)]
mod tests {
	use super::*;
	use codec::Encode;
	#[test]
	fn pointer_offset_works() {
		let ptr = Pointer::<u32>::null();
		assert_eq!(ptr.offset(10).unwrap(), Pointer::new(40));
		assert_eq!(ptr.offset(32).unwrap(), Pointer::new(128));
		let ptr = Pointer::<u64>::null();
		assert_eq!(ptr.offset(10).unwrap(), Pointer::new(80));
		assert_eq!(ptr.offset(32).unwrap(), Pointer::new(256));
	}
	#[test]
	fn return_value_encoded_max_size() {
		let encoded = ReturnValue::Value(Value::I64(-1)).encode();
		assert_eq!(encoded.len(), ReturnValue::ENCODED_MAX_SIZE);
	}
}