use wasmparser::{Operator, ValType};

#[allow(non_camel_case_types)]
#[derive(Clone, Copy)]
pub enum LoadType {
	I32,
	I64,
	F32,
	F64,
	I32_I8,
	I32_U8,
	I32_I16,
	I32_U16,
	I64_I8,
	I64_U8,
	I64_I16,
	I64_U16,
	I64_I32,
	I64_U32,
}

impl TryFrom<&Operator<'_>> for LoadType {
	type Error = ();

	fn try_from(inst: &Operator) -> Result<Self, Self::Error> {
		let result = match inst {
			Operator::I32Load { .. } => Self::I32,
			Operator::I64Load { .. } => Self::I64,
			Operator::F32Load { .. } => Self::F32,
			Operator::F64Load { .. } => Self::F64,
			Operator::I32Load8S { .. } => Self::I32_I8,
			Operator::I32Load8U { .. } => Self::I32_U8,
			Operator::I32Load16S { .. } => Self::I32_I16,
			Operator::I32Load16U { .. } => Self::I32_U16,
			Operator::I64Load8S { .. } => Self::I64_I8,
			Operator::I64Load8U { .. } => Self::I64_U8,
			Operator::I64Load16S { .. } => Self::I64_I16,
			Operator::I64Load16U { .. } => Self::I64_U16,
			Operator::I64Load32S { .. } => Self::I64_I32,
			Operator::I64Load32U { .. } => Self::I64_U32,
			_ => return Err(()),
		};

		Ok(result)
	}
}

#[allow(non_camel_case_types)]
#[derive(Clone, Copy)]
pub enum StoreType {
	I32,
	I64,
	F32,
	F64,
	I32_N8,
	I32_N16,
	I64_N8,
	I64_N16,
	I64_N32,
}

impl TryFrom<&Operator<'_>> for StoreType {
	type Error = ();

	fn try_from(inst: &Operator) -> Result<Self, Self::Error> {
		let result = match inst {
			Operator::I32Store { .. } => Self::I32,
			Operator::I64Store { .. } => Self::I64,
			Operator::F32Store { .. } => Self::F32,
			Operator::F64Store { .. } => Self::F64,
			Operator::I32Store8 { .. } => Self::I32_N8,
			Operator::I32Store16 { .. } => Self::I32_N16,
			Operator::I64Store8 { .. } => Self::I64_N8,
			Operator::I64Store16 { .. } => Self::I64_N16,
			Operator::I64Store32 { .. } => Self::I64_N32,
			_ => return Err(()),
		};

		Ok(result)
	}
}

// Order of mnemonics is:
// operation_result_parameter
#[allow(non_camel_case_types)]
#[derive(Clone, Copy)]
pub enum UnOpType {
	Clz_I32,
	Ctz_I32,
	Popcnt_I32,
	Clz_I64,
	Ctz_I64,
	Popcnt_I64,
	Abs_F32,
	Neg_F32,
	Ceil_F32,
	Floor_F32,
	Truncate_F32,
	Nearest_F32,
	Sqrt_F32,
	Abs_F64,
	Neg_F64,
	Ceil_F64,
	Floor_F64,
	Truncate_F64,
	Nearest_F64,
	Sqrt_F64,
	Wrap_I32_I64,
	Truncate_I32_F32,
	Truncate_I32_F64,
	Truncate_U32_F32,
	Truncate_U32_F64,
	Truncate_I64_F32,
	Truncate_I64_F64,
	Truncate_U64_F32,
	Truncate_U64_F64,
	Saturate_I32_F32,
	Saturate_I32_F64,
	Saturate_U32_F32,
	Saturate_U32_F64,
	Saturate_I64_F32,
	Saturate_I64_F64,
	Saturate_U64_F32,
	Saturate_U64_F64,
	Extend_I32_N8,
	Extend_I32_N16,
	Extend_I64_N8,
	Extend_I64_N16,
	Extend_I64_N32,
	Extend_I64_I32,
	Extend_I64_U32,
	Convert_F32_I32,
	Convert_F32_U32,
	Convert_F32_I64,
	Convert_F32_U64,
	Demote_F32_F64,
	Convert_F64_I32,
	Convert_F64_U32,
	Convert_F64_I64,
	Convert_F64_U64,
	Promote_F64_F32,
	Reinterpret_I32_F32,
	Reinterpret_I64_F64,
	Reinterpret_F32_I32,
	Reinterpret_F64_I64,
}

impl TryFrom<&Operator<'_>> for UnOpType {
	type Error = ();

	fn try_from(inst: &Operator) -> Result<Self, Self::Error> {
		let result = match inst {
			Operator::I32Clz => Self::Clz_I32,
			Operator::I32Ctz => Self::Ctz_I32,
			Operator::I32Popcnt => Self::Popcnt_I32,
			Operator::I64Clz => Self::Clz_I64,
			Operator::I64Ctz => Self::Ctz_I64,
			Operator::I64Popcnt => Self::Popcnt_I64,
			Operator::F32Abs => Self::Abs_F32,
			Operator::F32Neg => Self::Neg_F32,
			Operator::F32Ceil => Self::Ceil_F32,
			Operator::F32Floor => Self::Floor_F32,
			Operator::F32Trunc => Self::Truncate_F32,
			Operator::F32Nearest => Self::Nearest_F32,
			Operator::F32Sqrt => Self::Sqrt_F32,
			Operator::F64Abs => Self::Abs_F64,
			Operator::F64Neg => Self::Neg_F64,
			Operator::F64Ceil => Self::Ceil_F64,
			Operator::F64Floor => Self::Floor_F64,
			Operator::F64Trunc => Self::Truncate_F64,
			Operator::F64Nearest => Self::Nearest_F64,
			Operator::F64Sqrt => Self::Sqrt_F64,
			Operator::I32WrapI64 => Self::Wrap_I32_I64,
			Operator::I32TruncF32S => Self::Truncate_I32_F32,
			Operator::I32TruncF64S => Self::Truncate_I32_F64,
			Operator::I32TruncF32U => Self::Truncate_U32_F32,
			Operator::I32TruncF64U => Self::Truncate_U32_F64,
			Operator::I64TruncF32S => Self::Truncate_I64_F32,
			Operator::I64TruncF64S => Self::Truncate_I64_F64,
			Operator::I64TruncF32U => Self::Truncate_U64_F32,
			Operator::I64TruncF64U => Self::Truncate_U64_F64,
			Operator::I32TruncSatF32S => Self::Saturate_I32_F32,
			Operator::I32TruncSatF64S => Self::Saturate_I32_F64,
			Operator::I32TruncSatF32U => Self::Saturate_U32_F32,
			Operator::I32TruncSatF64U => Self::Saturate_U32_F64,
			Operator::I64TruncSatF32S => Self::Saturate_I64_F32,
			Operator::I64TruncSatF64S => Self::Saturate_I64_F64,
			Operator::I64TruncSatF32U => Self::Saturate_U64_F32,
			Operator::I64TruncSatF64U => Self::Saturate_U64_F64,
			Operator::I32Extend8S => Self::Extend_I32_N8,
			Operator::I32Extend16S => Self::Extend_I32_N16,
			Operator::I64Extend8S => Self::Extend_I64_N8,
			Operator::I64Extend16S => Self::Extend_I64_N16,
			Operator::I64Extend32S => Self::Extend_I64_N32,
			Operator::I64ExtendI32S => Self::Extend_I64_I32,
			Operator::I64ExtendI32U => Self::Extend_I64_U32,
			Operator::F32ConvertI32S => Self::Convert_F32_I32,
			Operator::F32ConvertI32U => Self::Convert_F32_U32,
			Operator::F32ConvertI64S => Self::Convert_F32_I64,
			Operator::F32ConvertI64U => Self::Convert_F32_U64,
			Operator::F32DemoteF64 => Self::Demote_F32_F64,
			Operator::F64ConvertI32S => Self::Convert_F64_I32,
			Operator::F64ConvertI32U => Self::Convert_F64_U32,
			Operator::F64ConvertI64S => Self::Convert_F64_I64,
			Operator::F64ConvertI64U => Self::Convert_F64_U64,
			Operator::F64PromoteF32 => Self::Promote_F64_F32,
			Operator::I32ReinterpretF32 => Self::Reinterpret_I32_F32,
			Operator::I64ReinterpretF64 => Self::Reinterpret_I64_F64,
			Operator::F32ReinterpretI32 => Self::Reinterpret_F32_I32,
			Operator::F64ReinterpretI64 => Self::Reinterpret_F64_I64,
			_ => return Err(()),
		};

		Ok(result)
	}
}

#[allow(non_camel_case_types)]
#[derive(Clone, Copy)]
pub enum BinOpType {
	Add_I32,
	Sub_I32,
	Mul_I32,
	DivS_I32,
	DivU_I32,
	RemS_I32,
	RemU_I32,
	And_I32,
	Or_I32,
	Xor_I32,
	Shl_I32,
	ShrS_I32,
	ShrU_I32,
	Rotl_I32,
	Rotr_I32,
	Add_I64,
	Sub_I64,
	Mul_I64,
	DivS_I64,
	DivU_I64,
	RemS_I64,
	RemU_I64,
	And_I64,
	Or_I64,
	Xor_I64,
	Shl_I64,
	ShrS_I64,
	ShrU_I64,
	Rotl_I64,
	Rotr_I64,
	Add_F32,
	Sub_F32,
	Mul_F32,
	Div_F32,
	Min_F32,
	Max_F32,
	Copysign_F32,
	Add_F64,
	Sub_F64,
	Mul_F64,
	Div_F64,
	Min_F64,
	Max_F64,
	Copysign_F64,
}

impl TryFrom<&Operator<'_>> for BinOpType {
	type Error = ();

	fn try_from(inst: &Operator) -> Result<Self, Self::Error> {
		let result = match inst {
			Operator::I32Add => Self::Add_I32,
			Operator::I32Sub => Self::Sub_I32,
			Operator::I32Mul => Self::Mul_I32,
			Operator::I32DivS => Self::DivS_I32,
			Operator::I32DivU => Self::DivU_I32,
			Operator::I32RemS => Self::RemS_I32,
			Operator::I32RemU => Self::RemU_I32,
			Operator::I32And => Self::And_I32,
			Operator::I32Or => Self::Or_I32,
			Operator::I32Xor => Self::Xor_I32,
			Operator::I32Shl => Self::Shl_I32,
			Operator::I32ShrS => Self::ShrS_I32,
			Operator::I32ShrU => Self::ShrU_I32,
			Operator::I32Rotl => Self::Rotl_I32,
			Operator::I32Rotr => Self::Rotr_I32,
			Operator::I64Add => Self::Add_I64,
			Operator::I64Sub => Self::Sub_I64,
			Operator::I64Mul => Self::Mul_I64,
			Operator::I64DivS => Self::DivS_I64,
			Operator::I64DivU => Self::DivU_I64,
			Operator::I64RemS => Self::RemS_I64,
			Operator::I64RemU => Self::RemU_I64,
			Operator::I64And => Self::And_I64,
			Operator::I64Or => Self::Or_I64,
			Operator::I64Xor => Self::Xor_I64,
			Operator::I64Shl => Self::Shl_I64,
			Operator::I64ShrS => Self::ShrS_I64,
			Operator::I64ShrU => Self::ShrU_I64,
			Operator::I64Rotl => Self::Rotl_I64,
			Operator::I64Rotr => Self::Rotr_I64,
			Operator::F32Add => Self::Add_F32,
			Operator::F32Sub => Self::Sub_F32,
			Operator::F32Mul => Self::Mul_F32,
			Operator::F32Div => Self::Div_F32,
			Operator::F32Min => Self::Min_F32,
			Operator::F32Max => Self::Max_F32,
			Operator::F32Copysign => Self::Copysign_F32,
			Operator::F64Add => Self::Add_F64,
			Operator::F64Sub => Self::Sub_F64,
			Operator::F64Mul => Self::Mul_F64,
			Operator::F64Div => Self::Div_F64,
			Operator::F64Min => Self::Min_F64,
			Operator::F64Max => Self::Max_F64,
			Operator::F64Copysign => Self::Copysign_F64,
			_ => {
				return Err(());
			}
		};

		Ok(result)
	}
}

#[allow(non_camel_case_types)]
#[derive(Clone, Copy)]
pub enum CmpOpType {
	Eq_I32,
	Ne_I32,
	LtS_I32,
	LtU_I32,
	GtS_I32,
	GtU_I32,
	LeS_I32,
	LeU_I32,
	GeS_I32,
	GeU_I32,
	Eq_I64,
	Ne_I64,
	LtS_I64,
	LtU_I64,
	GtS_I64,
	GtU_I64,
	LeS_I64,
	LeU_I64,
	GeS_I64,
	GeU_I64,
	Eq_F32,
	Ne_F32,
	Lt_F32,
	Gt_F32,
	Le_F32,
	Ge_F32,
	Eq_F64,
	Ne_F64,
	Lt_F64,
	Gt_F64,
	Le_F64,
	Ge_F64,
}

impl TryFrom<&Operator<'_>> for CmpOpType {
	type Error = ();

	fn try_from(inst: &Operator) -> Result<Self, Self::Error> {
		let result = match inst {
			Operator::I32Eq => Self::Eq_I32,
			Operator::I32Ne => Self::Ne_I32,
			Operator::I32LtS => Self::LtS_I32,
			Operator::I32LtU => Self::LtU_I32,
			Operator::I32GtS => Self::GtS_I32,
			Operator::I32GtU => Self::GtU_I32,
			Operator::I32LeS => Self::LeS_I32,
			Operator::I32LeU => Self::LeU_I32,
			Operator::I32GeS => Self::GeS_I32,
			Operator::I32GeU => Self::GeU_I32,
			Operator::I64Eq => Self::Eq_I64,
			Operator::I64Ne => Self::Ne_I64,
			Operator::I64LtS => Self::LtS_I64,
			Operator::I64LtU => Self::LtU_I64,
			Operator::I64GtS => Self::GtS_I64,
			Operator::I64GtU => Self::GtU_I64,
			Operator::I64LeS => Self::LeS_I64,
			Operator::I64LeU => Self::LeU_I64,
			Operator::I64GeS => Self::GeS_I64,
			Operator::I64GeU => Self::GeU_I64,
			Operator::F32Eq => Self::Eq_F32,
			Operator::F32Ne => Self::Ne_F32,
			Operator::F32Lt => Self::Lt_F32,
			Operator::F32Gt => Self::Gt_F32,
			Operator::F32Le => Self::Le_F32,
			Operator::F32Ge => Self::Ge_F32,
			Operator::F64Eq => Self::Eq_F64,
			Operator::F64Ne => Self::Ne_F64,
			Operator::F64Lt => Self::Lt_F64,
			Operator::F64Gt => Self::Gt_F64,
			Operator::F64Le => Self::Le_F64,
			Operator::F64Ge => Self::Ge_F64,
			_ => {
				return Err(());
			}
		};

		Ok(result)
	}
}

pub struct Select {
	pub(crate) condition: Box<Expression>,
	pub(crate) on_true: Box<Expression>,
	pub(crate) on_false: Box<Expression>,
}

impl Select {
	#[must_use]
	pub const fn condition(&self) -> &Expression {
		&self.condition
	}

	#[must_use]
	pub const fn on_true(&self) -> &Expression {
		&self.on_true
	}

	#[must_use]
	pub const fn on_false(&self) -> &Expression {
		&self.on_false
	}
}

#[derive(Clone, Copy)]
pub struct Temporary {
	pub(crate) var: usize,
}

impl Temporary {
	#[must_use]
	pub const fn var(self) -> usize {
		self.var
	}
}

#[derive(Clone, Copy)]
pub struct Local {
	pub(crate) var: usize,
}

impl Local {
	#[must_use]
	pub const fn var(self) -> usize {
		self.var
	}
}

#[derive(Clone, Copy)]
pub struct GetGlobal {
	pub(crate) var: usize,
}

impl GetGlobal {
	#[must_use]
	pub const fn var(self) -> usize {
		self.var
	}
}

pub struct LoadAt {
	pub(crate) load_type: LoadType,
	pub(crate) memory: usize,
	pub(crate) offset: u32,
	pub(crate) pointer: Box<Expression>,
}

impl LoadAt {
	#[must_use]
	pub const fn load_type(&self) -> LoadType {
		self.load_type
	}

	#[must_use]
	pub const fn memory(&self) -> usize {
		self.memory
	}

	#[must_use]
	pub const fn offset(&self) -> u32 {
		self.offset
	}

	#[must_use]
	pub const fn pointer(&self) -> &Expression {
		&self.pointer
	}
}

#[derive(Clone, Copy)]
pub struct MemorySize {
	pub(crate) memory: usize,
}

impl MemorySize {
	#[must_use]
	pub const fn memory(&self) -> usize {
		self.memory
	}
}

#[derive(Clone, Copy)]
pub enum Value {
	I32(i32),
	I64(i64),
	F32(f32),
	F64(f64),
}

impl From<i32> for Value {
	fn from(value: i32) -> Self {
		Self::I32(value)
	}
}

impl From<i64> for Value {
	fn from(value: i64) -> Self {
		Self::I64(value)
	}
}

impl From<u32> for Value {
	fn from(value: u32) -> Self {
		Self::F32(f32::from_bits(value))
	}
}

impl From<u64> for Value {
	fn from(value: u64) -> Self {
		Self::F64(f64::from_bits(value))
	}
}

pub struct UnOp {
	pub(crate) op_type: UnOpType,
	pub(crate) rhs: Box<Expression>,
}

impl UnOp {
	#[must_use]
	pub const fn op_type(&self) -> UnOpType {
		self.op_type
	}

	#[must_use]
	pub const fn rhs(&self) -> &Expression {
		&self.rhs
	}
}

pub struct BinOp {
	pub(crate) op_type: BinOpType,
	pub(crate) lhs: Box<Expression>,
	pub(crate) rhs: Box<Expression>,
}

impl BinOp {
	#[must_use]
	pub const fn op_type(&self) -> BinOpType {
		self.op_type
	}

	#[must_use]
	pub const fn lhs(&self) -> &Expression {
		&self.lhs
	}

	#[must_use]
	pub const fn rhs(&self) -> &Expression {
		&self.rhs
	}
}

pub struct CmpOp {
	pub(crate) op_type: CmpOpType,
	pub(crate) lhs: Box<Expression>,
	pub(crate) rhs: Box<Expression>,
}

impl CmpOp {
	#[must_use]
	pub const fn op_type(&self) -> CmpOpType {
		self.op_type
	}

	#[must_use]
	pub const fn lhs(&self) -> &Expression {
		&self.lhs
	}

	#[must_use]
	pub const fn rhs(&self) -> &Expression {
		&self.rhs
	}
}

pub enum Expression {
	Select(Select),
	GetTemporary(Temporary),
	GetLocal(Local),
	GetGlobal(GetGlobal),
	LoadAt(LoadAt),
	MemorySize(MemorySize),
	Value(Value),
	UnOp(UnOp),
	BinOp(BinOp),
	CmpOp(CmpOp),
}

#[derive(Clone, Copy)]
pub struct ResultList {
	start: usize,
	end: usize,
}

impl ResultList {
	#[must_use]
	pub const fn new(start: usize, end: usize) -> Self {
		Self { start, end }
	}

	#[must_use]
	pub const fn is_empty(self) -> bool {
		self.start == self.end
	}

	pub fn iter(self) -> impl Iterator<Item = Temporary> {
		(self.start..self.end).map(|var| Temporary { var })
	}
}

#[derive(Clone, Copy)]
pub struct Align {
	pub(crate) new: usize,
	pub(crate) old: usize,
	pub(crate) length: usize,
}

impl Align {
	#[must_use]
	pub const fn is_aligned(self) -> bool {
		self.length == 0 || self.new == self.old
	}

	#[must_use]
	pub const fn new_range(self) -> ResultList {
		ResultList::new(self.new, self.new + self.length)
	}

	#[must_use]
	pub const fn old_range(self) -> ResultList {
		ResultList::new(self.old, self.old + self.length)
	}
}

#[derive(Clone, Copy)]
pub struct Br {
	pub(crate) target: usize,
	pub(crate) align: Align,
}

impl Br {
	#[must_use]
	pub const fn target(self) -> usize {
		self.target
	}

	#[must_use]
	pub const fn align(self) -> Align {
		self.align
	}
}

pub struct BrTable {
	pub(crate) condition: Box<Expression>,
	pub(crate) data: Vec<Br>,
	pub(crate) default: Br,
}

impl BrTable {
	#[must_use]
	pub const fn condition(&self) -> &Expression {
		&self.condition
	}

	#[must_use]
	pub fn data(&self) -> &[Br] {
		&self.data
	}

	#[must_use]
	pub const fn default(&self) -> Br {
		self.default
	}
}

#[derive(PartialEq, Eq, Clone, Copy)]
pub enum LabelType {
	Forward,
	Backward,
}

pub enum Terminator {
	Unreachable,
	Br(Br),
	BrTable(BrTable),
}

#[derive(Default)]
pub struct Block {
	pub(crate) label_type: Option<LabelType>,
	pub(crate) code: Vec<Statement>,
	pub(crate) last: Option<Box<Terminator>>,
}

impl Block {
	#[must_use]
	pub const fn label_type(&self) -> Option<LabelType> {
		self.label_type
	}

	#[must_use]
	pub fn code(&self) -> &[Statement] {
		&self.code
	}

	#[must_use]
	pub fn last(&self) -> Option<&Terminator> {
		self.last.as_deref()
	}
}

pub struct BrIf {
	pub(crate) condition: Box<Expression>,
	pub(crate) target: Br,
}

impl BrIf {
	#[must_use]
	pub const fn condition(&self) -> &Expression {
		&self.condition
	}

	#[must_use]
	pub const fn target(&self) -> Br {
		self.target
	}
}

pub struct If {
	pub(crate) condition: Box<Expression>,
	pub(crate) on_true: Box<Block>,
	pub(crate) on_false: Option<Box<Block>>,
}

impl If {
	#[must_use]
	pub const fn condition(&self) -> &Expression {
		&self.condition
	}

	#[must_use]
	pub const fn on_true(&self) -> &Block {
		&self.on_true
	}

	#[must_use]
	pub fn on_false(&self) -> Option<&Block> {
		self.on_false.as_deref()
	}
}

pub struct Call {
	pub(crate) function: usize,
	pub(crate) param_list: Vec<Expression>,
	pub(crate) result_list: ResultList,
}

impl Call {
	#[must_use]
	pub const fn function(&self) -> usize {
		self.function
	}

	#[must_use]
	pub fn param_list(&self) -> &[Expression] {
		&self.param_list
	}

	#[must_use]
	pub const fn result_list(&self) -> ResultList {
		self.result_list
	}
}

pub struct CallIndirect {
	pub(crate) table: usize,
	pub(crate) index: Box<Expression>,
	pub(crate) param_list: Vec<Expression>,
	pub(crate) result_list: ResultList,
}

impl CallIndirect {
	#[must_use]
	pub const fn table(&self) -> usize {
		self.table
	}

	#[must_use]
	pub const fn index(&self) -> &Expression {
		&self.index
	}

	#[must_use]
	pub fn param_list(&self) -> &[Expression] {
		&self.param_list
	}

	#[must_use]
	pub const fn result_list(&self) -> ResultList {
		self.result_list
	}
}

pub struct SetTemporary {
	pub(crate) var: Temporary,
	pub(crate) value: Box<Expression>,
}

impl SetTemporary {
	#[must_use]
	pub const fn var(&self) -> Temporary {
		self.var
	}

	#[must_use]
	pub const fn value(&self) -> &Expression {
		&self.value
	}
}

pub struct SetLocal {
	pub(crate) var: Local,
	pub(crate) value: Box<Expression>,
}

impl SetLocal {
	#[must_use]
	pub const fn var(&self) -> Local {
		self.var
	}

	#[must_use]
	pub const fn value(&self) -> &Expression {
		&self.value
	}
}

pub struct SetGlobal {
	pub(crate) var: usize,
	pub(crate) value: Box<Expression>,
}

impl SetGlobal {
	#[must_use]
	pub const fn var(&self) -> usize {
		self.var
	}

	#[must_use]
	pub const fn value(&self) -> &Expression {
		&self.value
	}
}

pub struct StoreAt {
	pub(crate) store_type: StoreType,
	pub(crate) memory: usize,
	pub(crate) offset: u32,
	pub(crate) pointer: Box<Expression>,
	pub(crate) value: Box<Expression>,
}

impl StoreAt {
	#[must_use]
	pub const fn store_type(&self) -> StoreType {
		self.store_type
	}

	#[must_use]
	pub const fn memory(&self) -> usize {
		self.memory
	}

	#[must_use]
	pub const fn offset(&self) -> u32 {
		self.offset
	}

	#[must_use]
	pub const fn pointer(&self) -> &Expression {
		&self.pointer
	}

	#[must_use]
	pub const fn value(&self) -> &Expression {
		&self.value
	}
}

pub struct MemoryGrow {
	pub(crate) memory: usize,
	pub(crate) result: Temporary,
	pub(crate) size: Box<Expression>,
}

impl MemoryGrow {
	#[must_use]
	pub const fn memory(&self) -> usize {
		self.memory
	}

	#[must_use]
	pub const fn result(&self) -> Temporary {
		self.result
	}

	#[must_use]
	pub const fn size(&self) -> &Expression {
		&self.size
	}
}

pub struct MemoryArgument {
	pub(crate) memory: usize,
	pub(crate) pointer: Box<Expression>,
}

impl MemoryArgument {
	#[must_use]
	pub const fn memory(&self) -> usize {
		self.memory
	}

	#[must_use]
	pub const fn pointer(&self) -> &Expression {
		&self.pointer
	}
}

pub struct MemoryCopy {
	pub(crate) destination: MemoryArgument,
	pub(crate) source: MemoryArgument,
	pub(crate) size: Box<Expression>,
}

impl MemoryCopy {
	#[must_use]
	pub const fn destination(&self) -> &MemoryArgument {
		&self.destination
	}

	#[must_use]
	pub const fn source(&self) -> &MemoryArgument {
		&self.source
	}

	#[must_use]
	pub const fn size(&self) -> &Expression {
		&self.size
	}
}

pub struct MemoryFill {
	pub(crate) destination: MemoryArgument,
	pub(crate) size: Box<Expression>,
	pub(crate) value: Box<Expression>,
}

impl MemoryFill {
	#[must_use]
	pub const fn destination(&self) -> &MemoryArgument {
		&self.destination
	}

	#[must_use]
	pub const fn size(&self) -> &Expression {
		&self.size
	}

	#[must_use]
	pub const fn value(&self) -> &Expression {
		&self.value
	}
}

pub enum Statement {
	Block(Block),
	BrIf(BrIf),
	If(If),
	Call(Call),
	CallIndirect(CallIndirect),
	SetTemporary(SetTemporary),
	SetLocal(SetLocal),
	SetGlobal(SetGlobal),
	StoreAt(StoreAt),
	MemoryGrow(MemoryGrow),
	MemoryCopy(MemoryCopy),
	MemoryFill(MemoryFill),
}

pub struct FuncData {
	pub(crate) local_data: Vec<ValType>,
	pub(crate) num_result: usize,
	pub(crate) num_param: usize,
	pub(crate) num_stack: usize,
	pub(crate) code: Block,
}

impl FuncData {
	#[must_use]
	pub fn local_data(&self) -> &[ValType] {
		&self.local_data
	}

	#[must_use]
	pub const fn num_result(&self) -> usize {
		self.num_result
	}

	#[must_use]
	pub const fn num_param(&self) -> usize {
		self.num_param
	}

	#[must_use]
	pub const fn num_stack(&self) -> usize {
		self.num_stack
	}

	#[must_use]
	pub const fn code(&self) -> &Block {
		&self.code
	}
}
