import strings
import os

const (
	max_params       = 16
	all_param_names  = []string{len: max_params, init: '${`a` + it}'}
	all_param_values = []string{len: max_params, init: '${it + 1}'}
)

struct ReturnType {
	name         string
	init         string
	assertion    string
	no_assert_kw bool
}

const return_types = [
	ReturnType{
		name: ''
		init: ''
		assertion: ''
		no_assert_kw: true
	},
	ReturnType{
		name: 'int'
		init: '-123'
		assertion: ' == -123'
	},
	ReturnType{
		name: 'u64'
		init: '123'
		assertion: ' == 123'
	},
	ReturnType{
		name: 'voidptr'
		init: 'voidptr(123)'
		assertion: ' == voidptr(123)'
	},
	ReturnType{
		name: 'string'
		init: "'hello'"
		assertion: " == 'hello'"
	},
	ReturnType{
		name: '?'
		init: "error('an error')"
		assertion: " or { assert err.msg == 'an error' return }\npanic('got no error')"
		no_assert_kw: true
	},
	ReturnType{
		name: '?string'
		init: "'hello'"
		assertion: "? == 'hello'"
	},
	ReturnType{
		name: 'BigStruct'
		init: 'BigStruct{ a3: 56, a27: 1234, a61: 5555 }'
		assertion: ' == BigStruct{ a3: 56, a27: 1234, a61: 5555 }'
	},
]

// test_closures_with_n_args generates a new V file containing closures of `i`
// and parameters of type `typ`, to makes sure that all combinations work correctly
fn test_closures_with_n_args() ? {
	mut v_code := strings.new_builder(1024)
	// NB: the type or value of the captured arg doesn't matter for this test,
	// as the entire closure context is always passed as one pointer anyways

	v_code.write_string('struct BigStruct {')
	for i in 0 .. 64 {
		v_code.write_string('\ta$i int ')
	}
	v_code.writeln('}')

	for typ in ['byte', 'u16', 'int', 'i64', 'voidptr', 'string'] {
		for i in 0 .. max_params {
			param_names := all_param_names[..i]
			params := param_names.map('$it $typ')

			mut values := all_param_values[..i]
			if typ == 'string' {
				values = values.map("'$it'")
			}
			values = values.map('${typ}($it)')

			mut expected_val := if typ == 'string' {
				s := all_param_values[..i].join('')
				"'127' + '$s'"
			} else {
				'127 + ${i * (i + 1) / 2}'
			}

			init_val, return_type := if typ != 'string' {
				'u64(127)', 'u64'
			} else {
				"'127'", 'string'
			}

			// NB: the captured arg doesn't matter for this test, as closures always receive
			// a pointer to the entire closure context as their last argument anyways
			v_code.writeln("
fn test_big_closure_${typ}_${i}() {
	println('test_big_closure_${typ}_$i')
	mut local := 123
	mut local_2 := 234
	mut z := $init_val
	c := fn [z] (${params.join(', ')}) $return_type {
		mut sum := z")
			for j in 0 .. i {
				v_code.writeln('\t\tsum += ${return_type}(${param_names[j]})')
			}
			v_code.writeln("
		return sum
	}
	assert c(${values.join(', ')}) == $expected_val
	// ensure stack wasn't overwritten:
	assert local == 123
	assert local_2 == 234
}")
		}
	}

	for return_type in return_types {
		typ := return_type.name
		styp := typ.replace('?', 'option_').to_lower()
		init := return_type.init
		assertion := return_type.assertion

		for i in 0 .. 10 {
			param_names := all_param_names[..i]
			params := param_names.map('$it int')
			values := all_param_values[..i]

			assert_line := if !return_type.no_assert_kw {
				'assert c(${values.join(', ')}) $assertion'
			} else {
				'c(${values.join(', ')}) $assertion'
			}
			// NB: the captured arg doesn't matter for this test, as closures always receive
			// a pointer to the entire closure context as their last argument anyways
			v_code.writeln("
fn test_closure_return_${styp}_${i}() ? {
	println('test_closure_return_${styp}_$i')
	mut local := 123
	mut local_2 := 234
	mut z := 1234
	c := fn [z] (${params.join(', ')}) $typ {
		return $init
	}
	$assert_line
	// ensure stack wasn't overwritten:
	assert local == 123
	assert local_2 == 234
}")
		}
	}

	code := v_code.str()
	println('Compiling V code (${code.count('\n')} lines) ...')
	wrkdir := os.join_path(os.temp_dir(), 'vtests', 'closures')
	os.mkdir_all(wrkdir) ?
	os.chdir(wrkdir) ?
	os.write_file('closure_return_test.v', code) ?
	vexe := os.getenv('VEXE')
	res := os.execute('"$vexe" -keepc -cg -showcc closure_return_test.v')
	if res.exit_code != 0 {
		eprintln(res.output)
		assert false
	}
	println('Process exited with code $res.exit_code')

	os.chdir(os.dir(vexe)) or {}
	os.rmdir_all(wrkdir) or {}
}
