// Copyright (c) 2019 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.

module compiler

import (
	strings
)

// Returns type if used as expression
fn (p mut Parser) match_statement(is_expr bool) string {
	p.check(.key_match)
	p.fspace()
	typ, expr := p.tmp_expr()
	if typ.starts_with('array_') {
		p.error('arrays cannot be compared')
	}
	// is it safe to use p.cgen.insert_before ???
	tmp_var := p.get_tmp()
	p.cgen.insert_before('$typ $tmp_var = $expr;')

	p.fspace()
	p.check(.lcbr)
	mut i := 0
	mut all_cases_return := true

	// stores typ of resulting variable
	mut res_typ := ''
	defer {
		p.check(.rcbr)
	}
	for p.tok != .rcbr {
		if p.tok == .key_else {
			p.check(.key_else)
			if p.tok == .arrow {
				p.error(warn_match_arrow)
			}

			// unwrap match if there is only else
			if i == 0 {
				p.fspace()
				if is_expr {
					// statements are dissallowed (if match is expression) so user cant declare variables there and so on

					// allow braces is else
					got_brace := p.tok == .lcbr
					if got_brace {
						p.check(.lcbr)
					}

					p.gen('( ')

					res_typ = p.bool_expression()

					p.gen(' )')

					// allow braces in else
					if got_brace {
						p.check(.rcbr)
					}

					return res_typ
				} else {
					p.returns = false
					p.check(.lcbr)

					p.genln('{ ')
					p.statements()
					p.returns = all_cases_return && p.returns
					return ''
				}
			}

			if is_expr {
				// statements are dissallowed (if match is expression) so user cant declare variables there and so on
				p.gen(':(')

				// allow braces is else
				got_brace := p.tok == .lcbr
				if got_brace {
					p.fspace()
					p.check(.lcbr)
				}

				p.check_types(p.bool_expression(), res_typ)

				// allow braces in else
				if got_brace {
					p.check(.rcbr)
				}

				p.gen(strings.repeat(`)`, i+1))

				return res_typ
			} else {
				p.returns = false
				p.genln('else // default:')

				p.fspace()
				p.check(.lcbr)

				p.genln('{ ')
				p.statements()

				p.returns = all_cases_return && p.returns
				return ''
			}
		}

		if i > 0 {
			if is_expr {
				p.gen(': (')
			} else {
				p.gen('else ')
			}
		} else if is_expr {
			p.gen('(')
		}

		if is_expr {
			p.gen('(')
		} else {
			p.gen('if (')
		}

		ph := p.cgen.add_placeholder()

		// Multiple checks separated by comma
		mut got_comma := false

		for {
			if got_comma {
				p.gen(') || (')
			}

			mut got_string := false

			if typ == 'string' {
				got_string = true
				p.gen('string_eq($tmp_var, ')
			}
			else {
				p.gen('$tmp_var == ')
			}

			p.expected_type = typ
			p.check_types(p.bool_expression(), typ)
			p.expected_type = ''

			if got_string {
				p.gen(')')
			}

			if p.tok != .comma {
				if got_comma {
					p.gen(') ')
					p.cgen.set_placeholder(ph, '(')
				}
				break
			}
			p.check(.comma)
			got_comma = true
		}
		p.gen(')')

		if p.tok == .arrow {
			p.error(warn_match_arrow)
			p.check(.arrow)
		}

		// statements are dissallowed (if match is expression) so user cant declare variables there and so on
		if is_expr {
			p.gen('? (')

			// braces are required for now
			p.check(.lcbr)

			if i == 0 {
				// on the first iteration we set value of res_typ
				res_typ = p.bool_expression()
			} else {
				// later on we check that the value is of res_typ type
				p.check_types(p.bool_expression(), res_typ)
			}

			// braces are required for now
			p.check(.rcbr)

			p.gen(')')
		}
		else {
			p.returns = false
			p.fspace()
			p.check(.lcbr)

			p.genln('{ ')
			p.statements()

			all_cases_return = all_cases_return && p.returns
			// p.gen(')')
		}
		i++
		p.fgen_nl()
	}
	p.error('match must be exhaustive')
	//p.returns = false // only get here when no default, so return is not guaranteed
	return ''
}

fn (p mut Parser) switch_statement() {
	p.error('`switch` statement has been removed, use `match` instead:\n' +
		'https://vlang.io/docs#match')
}

fn (p mut Parser) if_statement(is_expr bool, elif_depth int) string {
	if is_expr {
		//if p.fileis('if_expr') {
			//println('IF EXPR')
		//}
		p.inside_if_expr = true
		p.gen('((')
	}
	else {
		p.gen('if (')
	}
	p.next()
	p.fspace()
	// `if a := opt() { }` syntax
	if p.tok == .name && p.peek() == .decl_assign {
		p.check_not_reserved()
		option_tmp := p.get_tmp()
		var_name := p.lit
		if p.known_var(var_name) {
			p.error('redefinition of `$var_name`')
		}
		p.open_scope()
		p.next()
		p.check(.decl_assign)
		p.is_var_decl = true
		option_type, expr := p.tmp_expr()// := p.bool_expression()
		if !option_type.starts_with('Option_') {
			p.error('`if x := opt() {` syntax requires a function that returns an optional value')
		}
		p.is_var_decl = false
		typ := option_type[7..]
		// Option_User tmp = get_user(1);
		// if (tmp.ok) {
		//   User user = *(User*)tmp.data;
		//   [statements]
		// }
		p.cgen.insert_before('$option_type $option_tmp = $expr; ')
		p.check(.lcbr)
		p.genln(option_tmp + '.ok) {')
		p.genln('$typ $var_name = *($typ*) $option_tmp . data;')
		p.register_var(Var {
			name: var_name
			typ: typ
			is_mut: false // TODO
			is_used: true // TODO
			//is_alloc: p.is_alloc || typ.starts_with('array_')
			//line_nr: p.tokens[ var_token_idx ].line_nr
			//token_idx: var_token_idx
		})
		p.statements()
		p.close_scope()
		p.returns = false
		return 'void'
	}	else {
		p.check_types(p.bool_expression(), 'bool')
	}
	if is_expr {
		p.gen(') ? (')
	}
	else {
		p.genln(') {')
	}
	p.fspace()
	p.check(.lcbr)
	mut typ := ''
	// if { if hack
	if p.tok == .key_if && p.inside_if_expr {
		typ = p.factor()
		p.next()
	}
	else {
		typ = p.statements()
	}
	if_returns := p.returns
	p.returns = false
	if p.tok == .key_else {
		if !p.inside_if_expr {
			p.fgen_nl()
		}
		p.check(.key_else)
		p.fspace()
		if p.tok == .key_if {
			if is_expr {
				p.gen(') : (')
				nested := p.if_statement(is_expr, elif_depth + 1)
				nested_returns := p.returns
				p.returns = if_returns && nested_returns
				return nested
			}
			else {
				p.gen(' else ')
				nested := p.if_statement(is_expr, 0)
				nested_returns := p.returns
				p.returns = if_returns && nested_returns
				return nested
			}
			// return ''
		}
		if is_expr {
			p.gen(') : (')
		}
		else {
			p.genln(' else { ')
		}
		p.check(.lcbr)
		// statements() returns the type of the last statement
		first_typ := typ
		typ = p.statements()
		p.inside_if_expr = false
		if is_expr {
			p.check_types(first_typ, typ)
			p.gen(strings.repeat(`)`, 2 * (elif_depth + 1)))
		}
		else_returns := p.returns
		p.returns = if_returns && else_returns
		return typ
	}
	p.inside_if_expr = false
	if p.fileis('test_test') {
		println('if ret typ="$typ" line=$p.scanner.line_nr')
	}
	return typ
}