module transformer

struct KeyVal {
	key   string
	value int
}

@[if debug_bounds_checking ?]
fn debug_bounds_checking(str string) {
	println(str)
}

// IndexState is used to track the index analysis performed when parsing the code
// `IndexExpr` nodes are annotated with `is_direct`, indicating that the array index can be safely directly accessed.

// The c_gen code check will handle this annotation and perform this direct memory access. The following cases are considered valid for this optimisation:
// 1. the array size is known and has a `len` larger than the index requested
// 2. the array was previously accessed with a higher value which would have reported the issue already
// 3. the array was created from a range expression a := range[10..13] and the offset'ed indexes are safe

// Current limitations:
//  * any function using break/continue or goto/label stopped from being optimised as soon as the relevant AST nodes are found as the code can not be ensured to be sequential
//  * `enum` and `const` indexes are not optimised (they could probably be looked up)
//  * for loops with multiple var in their init and/or inc are not analysed
//  * mut array are not analysed as their size can be reduced, but self-assignment in a single line

pub struct IndexState {
mut:
	// max_index has the biggest array index accessed for then named array
	// so if a[2] was set or read, it will be 2
	// A new array with no .len will recorded as -1 (accessing a[0] would be invalid)
	// the value -2 is used to indicate that the array should not be analysed
	// this is used for a mut array
	max_index map[string]int
	// We need to snapshot when entering `if` and `for` blocks and restore on exit
	// as the statements may not be run. This is managed by indent() & unindent().
	saved_disabled []bool
	saved_key_vals [][]KeyVal
pub mut:
	// on encountering goto/break/continue statements we stop any analysis
	// for the current function (as the code is not linear anymore)
	disabled bool
	level    int
}

// we are remembering the last array accessed and checking if the value is safe
// the node is updated with this information which can then be used by the code generators
fn (mut i IndexState) safe_access(key string, new int) bool {
	$if no_bounds_checking {
		return false
	}
	if i.disabled {
		return false
	}
	old := i.max_index[key] or {
		debug_bounds_checking('${i.level} ${key}.len = ${new}')
		i.max_index[key] = new
		return false
	}
	if new > old {
		if old < -1 {
			debug_bounds_checking('${i.level} ${key}[${new}] unsafe (mut array)')
			return false
		}
		debug_bounds_checking('${i.level} ${key}[${new}] unsafe (index was ${old})')
		i.max_index[key] = new
		return false
	}
	debug_bounds_checking('${i.level} ${key}[${new}] safe (index is ${old})')
	return true
}

// safe_offset returns for a previous array what was the highest
// offset we ever accessed for that identifier
fn (mut i IndexState) safe_offset(key string) int {
	$if no_bounds_checking {
		return -2
	}
	if i.disabled {
		return -2
	}
	return i.max_index[key] or { -1 }
}

// indent is used for when encountering new code blocks (if, for and functions)
// The code analysis needs to take into consideration blocks of code which
// may not run at runtime (if/for) and therefore even if a new maximum for an
// index access is found on an if branch it can not be used within the parent
// code. The same is true with for blocks. indent() snapshot the current state,
// to allow restoration with unindent()
// Also within a function, analysis must be `disabled` when goto or break are
// encountered as the code flow is then not lineear, and only restart when a
// new function analysis is started.
@[if !no_bounds_checking]
fn (mut i IndexState) indent(is_function bool) {
	mut kvs := []KeyVal{cap: i.max_index.len}
	for k, v in i.max_index {
		kvs << KeyVal{k, v}
	}
	i.saved_disabled << i.disabled
	i.saved_key_vals << kvs
	if is_function {
		i.disabled = false
	}
	i.level += 1
}

// restoring the data as it was before the if/for/unsafe block
@[if !no_bounds_checking]
fn (mut i IndexState) unindent() {
	i.level -= 1
	mut keys := []string{cap: i.max_index.len}
	for k, _ in i.max_index {
		keys << k
	}
	for k in keys {
		i.max_index.delete(k)
	}
	for saved in i.saved_key_vals.pop() {
		i.max_index[saved.key] = saved.value
	}
	i.disabled = i.saved_disabled.pop()
}
