// Copyright (c) 2023 l-m.dev. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
module wasm

import v.ast

pub fn (mut g Gen) asm_call(node ast.AsmTemplate) {
	// call 'main.test'
	// call 'main.Struct.+'
	//
	// call 'wasi_unstable' 'proc_exit'
	// call 'console' 'log'

	if node.args.len !in [1, 2] {
		g.v_error('incorrect number of arguments to `${node.name}`', node.pos)
	}

	arg0 := node.args[0]
	sarg0 := if arg0 is string {
		arg0
	} else if node.args.len == 1 {
		g.v_error('`${node.name}` must accept a string to call', node.pos)
	} else {
		g.v_error('`${node.name}` must accept a namespace for call', node.pos)
	}

	if node.args.len == 1 {
		g.func.call(sarg0)
		return
	}

	arg1 := node.args[1]
	sarg1 := if arg1 is string {
		arg1
	} else {
		g.v_error('`${node.name}` must accept a string for call', node.pos)
	}

	g.func.call_import(sarg0, sarg1)
}

pub fn (mut g Gen) asm_local_get_set_or_tee(node ast.AsmTemplate, vars AsmVars) {
	if node.args.len != 1 {
		g.v_error('incorrect number of arguments to `${node.name}`', node.pos)
	}

	arg0 := node.args[0]
	alias := match arg0 {
		ast.AsmAlias {
			arg0
		}
		else {
			g.v_error('must reference local by identifier', node.pos)
		}
	}

	target_var := if alias.name == '__vbp' {
		Var{
			idx: g.bp()
		}
	} else {
		var := vars[alias.name] or { g.v_error('unknown identifier', alias.pos) }
		var
	}
	// -- doesn't work, cgen error
	// else if var := vars[alias.name] {
	//     var
	// }

	if target_var.is_global {
		g.v_error('`${alias.name}` is global, cannot use with this instruction', alias.pos)
	}

	match node.name {
		'local.get' {
			g.get(target_var)
		}
		'local.set' {
			g.set(target_var)
		}
		'local.tee' {
			g.tee(target_var)
		}
		else {
			panic('unreachable')
		}
	}
}

pub fn (mut g Gen) asm_global_get_or_set(node ast.AsmTemplate, vars AsmVars) {
	if node.args.len != 1 {
		g.v_error('incorrect number of arguments to `${node.name}`', node.pos)
	}

	arg0 := node.args[0]
	alias := match arg0 {
		ast.AsmAlias {
			arg0
		}
		else {
			g.v_error('must reference global by identifier', node.pos)
		}
	}

	target_var := if alias.name == '__vsp' {
		Var{
			g_idx: g.sp()
			is_global: true
		}
	} else if alias.name == '__heap_base' {
		Var{
			g_idx: g.hp()
			is_global: true
		}
	} else {
		var := vars[alias.name] or { g.v_error('unknown identifier', alias.pos) }
		var
	}

	if !target_var.is_global {
		g.v_error('`${alias.name}` is a local, cannot use with this instruction', alias.pos)
	}

	match node.name {
		'global.get' {
			g.get(target_var)
		}
		'global.set' {
			g.set(target_var)
		}
		else {
			panic('unreachable')
		}
	}
}

pub fn (mut g Gen) asm_literal_arg(node ast.AsmTemplate) {
	// i32.const
	// i64.const
	// f32.const
	// f64.const

	if node.args.len != 1 {
		g.v_error('incorrect number of arguments to `${node.name}`', node.pos)
	}

	is_float := node.name[0] == `f`
	arg := node.args[0]

	if is_float {
		literal := match arg {
			ast.FloatLiteral {
				arg.val
			}
			ast.IntegerLiteral {
				arg.val
			}
			else {
				g.v_error('must supply float value to `${node.name}`', node.pos)
			}
		}

		match node.name {
			'f32.const' {
				g.func.f32_const(literal.f32())
			}
			'f64.const' {
				g.func.f64_const(literal.f64())
			}
			else {
				panic('unreachable')
			}
		}
		return
	}

	literal := match arg {
		ast.BoolLiteral {
			if arg.val {
				'1'
			} else {
				'0'
			}
		}
		ast.CharLiteral {
			u32(arg.val.runes()[0]).str() // there is a better way.
		}
		ast.IntegerLiteral {
			arg.val
		}
		else {
			g.v_error('must supply integer-like value to `${node.name}`', node.pos)
		}
	}

	match node.name {
		'i32.const' {
			g.func.i32_const(i32(literal.int()))
		}
		'i64.const' {
			g.func.i64_const(literal.i64())
		}
		else {
			panic('unreachable')
		}
	}
}

pub fn (mut g Gen) asm_parse_align_offset(node ast.AsmTemplate) (int, int) {
	if node.args.len != 2 {
		g.v_error('incorrect number of arguments to `${node.name}`', node.pos)
	}

	arg0 := node.args[0]
	arg1 := node.args[1]

	align := match arg0 {
		ast.IntegerLiteral {
			arg0.val.int()
		}
		else {
			g.v_error('must supply integer value to align', node.pos)
		}
	}

	offset := match arg1 {
		ast.IntegerLiteral {
			arg1.val.int()
		}
		else {
			g.v_error('must supply integer value to offset', node.pos)
		}
	}

	return align, offset
}

pub fn (mut g Gen) asm_load_or_store(node ast.AsmTemplate) {
	align, offset := g.asm_parse_align_offset(node)

	match node.name {
		'i32.load' {
			g.func.load(.i32_t, align, offset)
		}
		'i64.load' {
			g.func.load(.i64_t, align, offset)
		}
		'f32.load' {
			g.func.load(.f32_t, align, offset)
		}
		'f64.load' {
			g.func.load(.f64_t, align, offset)
		}
		'i32.store' {
			g.func.store(.i32_t, align, offset)
		}
		'i64.store' {
			g.func.store(.i64_t, align, offset)
		}
		'f32.store' {
			g.func.store(.f32_t, align, offset)
		}
		'f64.store' {
			g.func.store(.f64_t, align, offset)
		}
		'i32.load8_s' {
			g.func.load8(.i32_t, true, align, offset)
		}
		'i64.load8_s' {
			g.func.load8(.i64_t, true, align, offset)
		}
		'i32.load8_u' {
			g.func.load8(.i32_t, false, align, offset)
		}
		'i64.load8_u' {
			g.func.load8(.i64_t, false, align, offset)
		}
		'i32.load16_s' {
			g.func.load16(.i32_t, true, align, offset)
		}
		'i64.load16_s' {
			g.func.load16(.i64_t, true, align, offset)
		}
		'i32.load16_u' {
			g.func.load16(.i32_t, false, align, offset)
		}
		'i64.load16_u' {
			g.func.load16(.i64_t, false, align, offset)
		}
		'i64.load32_s' {
			g.func.load32_i64(true, align, offset)
		}
		'i64.load32_u' {
			g.func.load32_i64(false, align, offset)
		}
		'i32.store8' {
			g.func.store8(.i32_t, align, offset)
		}
		'i64.store8' {
			g.func.store8(.i64_t, align, offset)
		}
		'i32.store16' {
			g.func.store16(.i32_t, align, offset)
		}
		'i64.store16' {
			g.func.store16(.i64_t, align, offset)
		}
		'i64.store32' {
			g.func.store32_i64(align, offset)
		}
		else {
			panic('unreachable')
		}
	}
}

pub fn (mut g Gen) asm_template(parent ast.AsmStmt, node ast.AsmTemplate, vars AsmVars) {
	if node.is_label || node.is_directive {
		g.v_error("`asm wasm` doesn't support labels or directives", node.pos)
	}

	match node.name {
		'unreachable' {
			g.func.unreachable()
		}
		'nop' {
			g.func.nop()
		}
		'drop' {
			g.func.drop()
		}
		'return' {
			g.func.c_return()
		}
		'select' {
			g.func.c_select()
		}
		'i32.const' {
			g.asm_literal_arg(node)
		}
		'i64.const' {
			g.asm_literal_arg(node)
		}
		'f32.const' {
			g.asm_literal_arg(node)
		}
		'f64.const' {
			g.asm_literal_arg(node)
		}
		'local.get' {
			g.asm_local_get_set_or_tee(node, vars)
		}
		'local.set' {
			g.asm_local_get_set_or_tee(node, vars)
		}
		'local.tee' {
			g.asm_local_get_set_or_tee(node, vars)
		}
		'global.get' {
			g.asm_global_get_or_set(node, vars)
		}
		'global.set' {
			g.asm_global_get_or_set(node, vars)
		}
		'i32.load' {
			g.asm_load_or_store(node)
		}
		'i64.load' {
			g.asm_load_or_store(node)
		}
		'f32.load' {
			g.asm_load_or_store(node)
		}
		'f64.load' {
			g.asm_load_or_store(node)
		}
		'i32.store' {
			g.asm_load_or_store(node)
		}
		'i64.store' {
			g.asm_load_or_store(node)
		}
		'f32.store' {
			g.asm_load_or_store(node)
		}
		'f64.store' {
			g.asm_load_or_store(node)
		}
		'i32.load8_s' {
			g.asm_load_or_store(node)
		}
		'i64.load8_s' {
			g.asm_load_or_store(node)
		}
		'i32.load8_u' {
			g.asm_load_or_store(node)
		}
		'i64.load8_u' {
			g.asm_load_or_store(node)
		}
		'i32.load16_s' {
			g.asm_load_or_store(node)
		}
		'i64.load16_s' {
			g.asm_load_or_store(node)
		}
		'i32.load16_u' {
			g.asm_load_or_store(node)
		}
		'i64.load16_u' {
			g.asm_load_or_store(node)
		}
		'i64.load32_s' {
			g.asm_load_or_store(node)
		}
		'i64.load32_u' {
			g.asm_load_or_store(node)
		}
		'i32.store8' {
			g.asm_load_or_store(node)
		}
		'i64.store8' {
			g.asm_load_or_store(node)
		}
		'i32.store16' {
			g.asm_load_or_store(node)
		}
		'i64.store16' {
			g.asm_load_or_store(node)
		}
		'i64.store32' {
			g.asm_load_or_store(node)
		}
		'call' {
			g.asm_call(node)
		}
		'i32.add' {
			g.func.add(.i32_t)
		}
		'i32.sub' {
			g.func.sub(.i32_t)
		}
		'i32.mul' {
			g.func.mul(.i32_t)
		}
		'i32.div_s' {
			g.func.div(.i32_t, true)
		}
		'i32.div_u' {
			g.func.div(.i32_t, false)
		}
		'i32.rem_s' {
			g.func.rem(.i32_t, true)
		}
		'i32.rem_u' {
			g.func.rem(.i32_t, false)
		}
		'i64.add' {
			g.func.add(.i64_t)
		}
		'i64.sub' {
			g.func.sub(.i64_t)
		}
		'i64.mul' {
			g.func.mul(.i64_t)
		}
		'i64.div_s' {
			g.func.div(.i64_t, true)
		}
		'i64.div_u' {
			g.func.div(.i64_t, false)
		}
		'i64.rem_s' {
			g.func.rem(.i64_t, true)
		}
		'i64.rem_u' {
			g.func.rem(.i64_t, false)
		}
		'f32.add' {
			g.func.add(.f32_t)
		}
		'f32.sub' {
			g.func.sub(.f32_t)
		}
		'f32.mul' {
			g.func.mul(.f32_t)
		}
		'f32.div' {
			g.func.div(.f32_t, true)
		}
		'f64.add' {
			g.func.add(.f64_t)
		}
		'f64.sub' {
			g.func.sub(.f64_t)
		}
		'f64.mul' {
			g.func.mul(.f64_t)
		}
		'f64.div' {
			g.func.div(.f64_t, true)
		}
		'i32.eqz' {
			g.func.eqz(.i32_t)
		}
		'i32.eq' {
			g.func.eq(.i32_t)
		}
		'i32.ne' {
			g.func.ne(.i32_t)
		}
		'i32.lt_s' {
			g.func.lt(.i32_t, true)
		}
		'i32.lt_u' {
			g.func.lt(.i32_t, false)
		}
		'i32.gt_s' {
			g.func.gt(.i32_t, true)
		}
		'i32.gt_u' {
			g.func.gt(.i32_t, false)
		}
		'i32.le_s' {
			g.func.le(.i32_t, true)
		}
		'i32.le_u' {
			g.func.le(.i32_t, false)
		}
		'i32.ge_s' {
			g.func.ge(.i32_t, true)
		}
		'i32.ge_u' {
			g.func.ge(.i32_t, false)
		}
		'i64.eqz' {
			g.func.eqz(.i64_t)
		}
		'i64.eq' {
			g.func.eq(.i64_t)
		}
		'i64.ne' {
			g.func.ne(.i64_t)
		}
		'i64.lt_s' {
			g.func.lt(.i64_t, true)
		}
		'i64.lt_u' {
			g.func.lt(.i64_t, false)
		}
		'i64.gt_s' {
			g.func.gt(.i64_t, true)
		}
		'i64.gt_u' {
			g.func.gt(.i64_t, false)
		}
		'i64.le_s' {
			g.func.le(.i64_t, true)
		}
		'i64.le_u' {
			g.func.le(.i64_t, false)
		}
		'i64.ge_s' {
			g.func.ge(.i64_t, true)
		}
		'i64.ge_u' {
			g.func.ge(.i64_t, false)
		}
		'f32.eq' {
			g.func.eq(.f32_t)
		}
		'f32.ne' {
			g.func.ne(.f32_t)
		}
		'f32.lt' {
			g.func.lt(.f32_t, true)
		}
		'f32.gt' {
			g.func.gt(.f32_t, true)
		}
		'f32.le' {
			g.func.le(.f32_t, true)
		}
		'f32.ge' {
			g.func.ge(.f32_t, true)
		}
		'f64.eq' {
			g.func.eq(.f64_t)
		}
		'f64.ne' {
			g.func.ne(.f64_t)
		}
		'f64.lt' {
			g.func.lt(.f64_t, true)
		}
		'f64.gt' {
			g.func.gt(.f64_t, true)
		}
		'f64.le' {
			g.func.le(.f64_t, true)
		}
		'f64.ge' {
			g.func.ge(.f64_t, true)
		}
		'i32.and' {
			g.func.b_and(.i32_t)
		}
		'i32.or' {
			g.func.b_or(.i32_t)
		}
		'i32.xor' {
			g.func.b_xor(.i32_t)
		}
		'i32.shl' {
			g.func.b_shl(.i32_t)
		}
		'i32.shr_s' {
			g.func.b_shr(.i32_t, true)
		}
		'i32.shr_u' {
			g.func.b_shr(.i32_t, true)
		}
		'i32.rotl' {
			g.func.rotl(.i32_t)
		}
		'i32.rotr' {
			g.func.rotr(.i32_t)
		}
		'i32.clz' {
			g.func.clz(.i32_t)
		}
		'i32.ctz' {
			g.func.ctz(.i32_t)
		}
		'i32.popcnt' {
			g.func.popcnt(.i32_t)
		}
		'i64.and' {
			g.func.b_and(.i64_t)
		}
		'i64.or' {
			g.func.b_or(.i64_t)
		}
		'i64.xor' {
			g.func.b_xor(.i64_t)
		}
		'i64.shl' {
			g.func.b_shl(.i64_t)
		}
		'i64.shr_s' {
			g.func.b_shr(.i64_t, true)
		}
		'i64.shr_u' {
			g.func.b_shr(.i64_t, false)
		}
		'i64.rotl' {
			g.func.rotl(.i64_t)
		}
		'i64.rotr' {
			g.func.rotr(.i64_t)
		}
		'i64.clz' {
			g.func.clz(.i64_t)
		}
		'i64.ctz' {
			g.func.ctz(.i64_t)
		}
		'i64.popcnt' {
			g.func.popcnt(.i64_t)
		}
		'f32.neg' {
			g.func.neg(.f32_t)
		}
		'f32.ceil' {
			g.func.ceil(.f32_t)
		}
		'f32.floor' {
			g.func.floor(.f32_t)
		}
		'f32.trunc' {
			g.func.trunc(.f32_t)
		}
		'f32.nearest' {
			g.func.nearest(.f32_t)
		}
		'f32.sqrt' {
			g.func.sqrt(.f32_t)
		}
		'f32.min' {
			g.func.min(.f32_t)
		}
		'f32.max' {
			g.func.max(.f32_t)
		}
		'f32.copysign' {
			g.func.copysign(.f32_t)
		}
		'f64.abs' {
			g.func.abs(.f64_t)
		}
		'f64.neg' {
			g.func.neg(.f64_t)
		}
		'f64.ceil' {
			g.func.ceil(.f64_t)
		}
		'f64.floor' {
			g.func.floor(.f64_t)
		}
		'f64.trunc' {
			g.func.trunc(.f64_t)
		}
		'f64.nearest' {
			g.func.nearest(.f64_t)
		}
		'f64.sqrt' {
			g.func.sqrt(.f64_t)
		}
		'f64.min' {
			g.func.min(.f64_t)
		}
		'f64.max' {
			g.func.max(.f64_t)
		}
		'f64.copysign' {
			g.func.copysign(.f64_t)
		}
		'i32.wrap_i64' {
			g.func.cast(.i64_t, false, .i32_t)
		}
		'i32.trunc_f32_s' {
			g.func.cast_trapping(.f32_t, true, .i32_t)
		}
		'i32.trunc_f32_u' {
			g.func.cast_trapping(.f32_t, false, .i32_t)
		}
		'i32.trunc_f64_s' {
			g.func.cast_trapping(.f64_t, true, .i32_t)
		}
		'i32.trunc_f64_u' {
			g.func.cast_trapping(.f64_t, false, .i32_t)
		}
		'i64.extend_i32_s' {
			g.func.cast(.i32_t, true, .i64_t)
		}
		'i64.extend_i32_u' {
			g.func.cast(.i32_t, false, .i64_t)
		}
		'i64.trunc_f32_s' {
			g.func.cast_trapping(.f32_t, true, .i64_t)
		}
		'i64.trunc_f32_u' {
			g.func.cast_trapping(.f32_t, false, .i64_t)
		}
		'i64.trunc_f64_s' {
			g.func.cast_trapping(.f64_t, true, .i64_t)
		}
		'i64.trunc_f64_u' {
			g.func.cast_trapping(.f64_t, false, .i64_t)
		}
		'f32.convert_i32_s' {
			g.func.cast(.i32_t, true, .f32_t)
		}
		'f32.convert_i32_u' {
			g.func.cast(.i32_t, false, .f32_t)
		}
		'f32.convert_i64_s' {
			g.func.cast(.i64_t, true, .f32_t)
		}
		'f32.convert_i64_u' {
			g.func.cast(.i64_t, false, .f32_t)
		}
		'f32.demote_f64' {
			g.func.cast(.f64_t, true, .f32_t)
		}
		'f64.convert_i32_s' {
			g.func.cast(.i32_t, true, .f64_t)
		}
		'f64.convert_i32_u' {
			g.func.cast(.i32_t, false, .f64_t)
		}
		'f64.convert_i64_s' {
			g.func.cast(.i64_t, true, .f64_t)
		}
		'f64.convert_i64_u' {
			g.func.cast(.i64_t, false, .f64_t)
		}
		'f64.promote_f32' {
			g.func.cast(.f32_t, true, .f64_t)
		}
		'i32.reinterpret_f32' {
			g.func.reinterpret(.f32_t)
		}
		'i64.reinterpret_f64' {
			g.func.reinterpret(.f64_t)
		}
		'f32.reinterpret_i32' {
			g.func.reinterpret(.i32_t)
		}
		'f64.reinterpret_i64' {
			g.func.reinterpret(.i64_t)
		}
		'i32.extend8_s' {
			g.func.sign_extend8(.i32_t)
		}
		'i32.extend16_s' {
			g.func.sign_extend16(.i32_t)
		}
		'i64.extend8_s' {
			g.func.sign_extend8(.i64_t)
		}
		'i64.extend16_s' {
			g.func.sign_extend16(.i64_t)
		}
		'i64.extend32_s' {
			g.func.sign_extend32()
		}
		'i32.trunc_sat_f32_s' {
			g.func.cast(.f32_t, true, .i32_t)
		}
		'i32.trunc_sat_f32_u' {
			g.func.cast(.f32_t, false, .i32_t)
		}
		'i32.trunc_sat_f64_s' {
			g.func.cast(.f64_t, true, .i32_t)
		}
		'i32.trunc_sat_f64_u' {
			g.func.cast(.f64_t, false, .i32_t)
		}
		'i64.trunc_sat_f32_s' {
			g.func.cast(.f32_t, true, .i64_t)
		}
		'i64.trunc_sat_f32_u' {
			g.func.cast(.f32_t, false, .i64_t)
		}
		'i64.trunc_sat_f64_s' {
			g.func.cast(.f64_t, true, .i64_t)
		}
		'i64.trunc_sat_f64_u' {
			g.func.cast(.f64_t, false, .i64_t)
		}
		'memory.size' {
			g.func.memory_size()
		}
		'memory.grow' {
			g.func.memory_grow()
		}
		'memory.copy' {
			g.func.memory_copy()
		}
		'memory.fill' {
			g.func.memory_fill()
		}
		// TODO: impl later
		/*
		'ref.null' {
			g.func.ref_null()
		}
		*/
		else {
			g.v_error('unknown opcode', node.pos)
		}
	}
}

type AsmVars = map[string]Var

pub fn (mut g Gen) asm_stmt(node ast.AsmStmt) {
	mut vars := AsmVars(map[string]Var{})

	for var_expr in node.output {
		vars[var_expr.alias] = g.get_var_or_make_from_expr(var_expr.expr, var_expr.typ)
	}
	for var_expr in node.input {
		vars[var_expr.alias] = g.get_var_or_make_from_expr(var_expr.expr, var_expr.typ)
	}

	if node.clobbered.len != 0 {
		g.v_error('wasm does not support clobber lists', node.pos)
	}
	if node.global_labels.len != 0 || node.local_labels.len != 0 {
		g.v_error('wasm does not support labels', node.pos)
	}

	for tmpl in node.templates {
		g.asm_template(node, tmpl, vars)
	}
}
