module compiler

import (
	os
	term
)
// ////////////////////////////////////////////////////////////////////////////////////////////////
// NB: The code in this file is organized in layers (between the ///// lines).
// This allows for easier keeping in sync of error/warn functions.
// The functions in each of the layers, call the functions from the layers *below*.
// The functions in each of the layers, also have more details about the warn/error situation,
// so they can display more informative message, so please call the lowest level variant you can.
// ////////////////////////////////////////////////////////////////////////////////////////////////
// TLDR: If you have a token index, call:
// p.error_with_token_index(msg, token_index)
// ... not just :
// p.error(msg)
// ////////////////////////////////////////////////////////////////////////////////////////////////


fn (p mut Parser) error(s string) {
	// no positioning info, so just assume that the last token was the culprit:
	p.error_with_token_index(s, p.token_idx - 1)
}

fn (p mut Parser) warn_or_error(s string) {
	if p.pref.is_prod {
		p.error(s)
	} else {
		p.warn(s)
	}
}

fn (p mut Parser) warn(s string) {
	p.warn_with_token_index(s, p.token_idx - 1)
}

fn (p mut Parser) production_error_with_token_index(e string, tokenindex int) {
	if p.pref.is_prod {
		p.error_with_token_index(e, tokenindex)
	}
	else {
		p.warn_with_token_index(e, tokenindex)
	}
}

fn (p mut Parser) error_with_token_index(s string, tokenindex int) {
	p.error_with_position(s, p.scanner.get_scanner_pos_of_token(p.tokens[tokenindex]))
}

fn (p mut Parser) warn_with_token_index(s string, tokenindex int) {
	p.warn_with_position(s, p.scanner.get_scanner_pos_of_token(p.tokens[tokenindex]))
}

fn (p mut Parser) error_with_position(s string, sp ScannerPos) {
	p.print_error_context()
	e := normalized_error(s)
	p.scanner.goto_scanner_position(sp)
	p.scanner.error_with_col(e, sp.pos - sp.last_nl_pos)
}

fn (p mut Parser) warn_with_position(s string, sp ScannerPos) {
	if p.scanner.is_fmt {
		return
	}
	// on a warning, restore the scanner state after printing the warning:
	cpos := p.scanner.get_scanner_pos()
	e := normalized_error(s)
	p.scanner.goto_scanner_position(sp)
	p.scanner.warn_with_col(e, sp.pos - sp.last_nl_pos)
	p.scanner.goto_scanner_position(cpos)
}

fn (s &Scanner) error(msg string) {
	s.error_with_col(msg, 0)
}

fn (s &Scanner) warn(msg string) {
	s.warn_with_col(msg, 0)
}

fn (s &Scanner) warn_with_col(msg string, col int) {
	fullpath := s.get_error_filepath()
	color_on := s.is_color_output_on()
	final_message := if color_on { term.bold(term.bright_blue(msg)) } else { msg }
	eprintln('warning: ${fullpath}:${s.line_nr+1}:${col}: $final_message')
}

fn (s &Scanner) error_with_col(msg string, col int) {
	fullpath := s.get_error_filepath()
	color_on := s.is_color_output_on()
	final_message := if color_on { term.red(term.bold(msg)) } else { msg }
	// The filepath:line:col: format is the default C compiler
	// error output format. It allows editors and IDE's like
	// emacs to quickly find the errors in the output
	// and jump to their source with a keyboard shortcut.
	// NB: using only the filename may lead to inability of IDE/editors
	// to find the source file, when the IDE has a different working folder than v itself.
	eprintln('${fullpath}:${s.line_nr + 1}:${col}: $final_message')
	if s.print_line_on_error && s.nlines > 0 {
		context_start_line := imax(0, (s.line_nr - error_context_before))
		context_end_line := imin(s.nlines - 1, (s.line_nr + error_context_after + 1))
		for cline := context_start_line; cline < context_end_line; cline++ {
			line := '${(cline+1):5d}| ' + s.line(cline)
			coloredline := if cline == s.line_nr && color_on { term.red(line) } else { line }
			eprintln(coloredline)
			if cline != s.line_nr {
				continue
			}
			// The pointerline should have the same spaces/tabs as the offending
			// line, so that it prints the ^ character exactly on the *same spot*
			// where it is needed. That is the reason we can not just
			// use strings.repeat(` `, col) to form it.
			mut pointerline := []string
			for i, c in line {
				if i < col {
					x := if c.is_space() { c } else { ` ` }
					pointerline << x.str()
					continue
				}
				pointerline << if color_on { term.bold(term.blue('^')) } else { '^' }
				break
			}
			eprintln('      ' + pointerline.join(''))
		}
	}
	exit(1)
}

// ////////////////////////////////////////////////////////////////////////////////////////////////
// / Misc error helper functions, can be called by any of the functions above
[inline]
fn (p &Parser) cur_tok_index() int {
	return p.token_idx - 1
}

[inline]
fn imax(a, b int) int {
	return if a > b { a } else { b }
}

[inline]
fn imin(a, b int) int {
	return if a < b { a } else { b }
}

fn (s &Scanner) get_error_filepath() string {
	verror_paths_override := os.getenv('VERROR_PATHS')
	use_relative_paths := match verror_paths_override {
		'relative'{
			true
		}
		'absolute'{
			false
		}
		else {
			s.print_rel_paths_on_error}}
	if use_relative_paths {
		workdir := os.getwd() + os.path_separator
		if s.file_path.starts_with(workdir) {
			return s.file_path.replace(workdir, '')
		}
		return s.file_path
	}
	return os.realpath(s.file_path)
}

fn (s &Scanner) is_color_output_on() bool {
	return s.print_colored_error && term.can_show_color_on_stderr()
}

fn (p mut Parser) print_error_context() {
	// Dump all vars and types for debugging
	if p.pref.is_debug {
		// os.write_to_file('/var/tmp/lang.types', '')//pes(p.table.types))
		os.write_file('fns.txt', p.table.debug_fns())
	}
	if p.pref.is_verbose || p.pref.is_debug {
		println('pass=$p.pass fn=`$p.cur_fn.name`\n')
	}
	p.cgen.save()
	// V up hint
	cur_path := os.getwd()
	if !p.pref.is_repl && !p.pref.is_test && (p.file_path.contains('v/compiler') || cur_path.contains('v/compiler')) {
		println('\n=========================')
		println('It looks like you are building V. It is being frequently updated every day.')
		println("If you didn\'t modify V\'s code, most likely there was a change that ")
		println('lead to this error.')
		println('\nRun `v up`, that will most likely fix it.')
		// println('\nIf this doesn\'t help, re-install V from source or download a precompiled' + ' binary from\nhttps://vlang.io.')
		println("\nIf this doesn\'t help, please create a GitHub issue.")
		println('=========================\n')
	}
	if p.pref.is_debug {
		print_backtrace()
	}
	// p.scanner.debug_tokens()
}

fn ienv_default(ename string, idefault int) int {
	es := os.getenv(ename)
	if es.len == 0 { return idefault }
	return es.int()
}

// print_current_tokens/1 pretty prints the current token context, like this:
// // Your label: tokens[  32] = Token{ .line:   8, .pos:   93, .tok:  85 } = mut
// // Your label: tokens[> 33] = Token{ .line:   8, .pos:   95, .tok:   1 } = b
// // Your label: tokens[  34] = Token{ .line:   8, .pos:   98, .tok:  31 } = :=
// It is useful while debugging the v compiler itself. > marks p.token_idx
fn (p &Parser) print_current_tokens(label string){
	btokens := ienv_default('V_BTOKENS', 5)
	atokens := ienv_default('V_ATOKENS', 5)
	ctoken_idx := p.token_idx
	stoken_idx := imax(0, ctoken_idx - btokens)
	etoken_idx := imin( ctoken_idx + atokens + 1, p.tokens.len)
	for i := stoken_idx; i < etoken_idx; i++ {
		idx := if i == ctoken_idx {
			'>${i:3d}'
		} else {
			' ${i:3d}'
		}
		eprintln('$label: tokens[$idx] = ' + p.tokens[ i ].detailed_str())
	}
}

fn normalized_error(s string) string {
	mut res := s
	if !res.contains('__') {
		// `[]int` instead of `array_int`
		res = res.replace('array_', '[]')
	}
	res = res.replace('__', '.')
	res = res.replace('Option_', '?')
	res = res.replace('main.', '')
	res = res.replace('ptr_', '&')
	res = res.replace('_dot_', '.')
	if res.contains('_V_MulRet_') {
		res = res.replace('_V_MulRet_', '(')
		res = res.replace('_V_', ', ')
		res = res[..res.len - 1] + ')"' //"// quote balance comment. do not remove
	}
	return res
}

// ////////////////////////////////////////////////////////////////////////////////////////////////
// The goal of ScannerPos is to track the current scanning position,
// so that if there is an error found later, v could show a more accurate
// position about where the error initially was.
// NB: The fields of ScannerPos *should be kept synchronized* with the
// corresponding fields in Scanner.
struct ScannerPos {
mut:
	pos         int
	line_nr     int
	last_nl_pos int
}

pub fn (s ScannerPos) str() string {
	return 'ScannerPos{ ${s.pos:5d} , ${s.line_nr:5d} , ${s.last_nl_pos:5d} }'
}

fn (s &Scanner) get_scanner_pos() ScannerPos {
	return ScannerPos{
		pos: s.pos
		line_nr: s.line_nr
		last_nl_pos: s.last_nl_pos
	}
}

fn (s mut Scanner) goto_scanner_position(scp ScannerPos) {
	s.pos = scp.pos
	s.line_nr = scp.line_nr
	s.last_nl_pos = scp.last_nl_pos
}

fn (s &Scanner) get_last_nl_from_pos(_pos int) int {
	pos := if _pos >= s.text.len { s.text.len - 1 } else { _pos }
	for i := pos; i >= 0; i-- {
		if s.text[i] == `\n` {
			return i
		}
	}
	return 0
}

fn (s &Scanner) get_scanner_pos_of_token(tok &Token) ScannerPos {
	return ScannerPos{
		pos: tok.pos
		line_nr: tok.line_nr
		last_nl_pos: s.get_last_nl_from_pos(tok.pos)
	}
}

// /////////////////////////////
fn (p mut Parser) mutable_arg_error(i int, arg Var, f Fn) {
	mut dots_example := 'mut $p.lit'
	if i > 0 {
		dots_example = '.., ' + dots_example
	}
	if i < f.args.len - 1 {
		dots_example = dots_example + ',..'
	}
	p.error('`$arg.name` is a mutable argument, you need to provide `mut`: ' + '`$f.name ($dots_example)`')
}

const (
	warn_match_arrow = '=> is no longer needed in match statements, use\n' + 'match foo {
	1 { bar }
	2 { baz }
	else { ... }
}'
	// make_receiver_mutable =
	err_used_as_value = 'used as value'
	and_or_error = 'use `()` to make the boolean expression clear\n' + 'for example: `(a && b) || c` instead of `a && b || c`'
	err_modify_bitfield = 'to modify a bitfield flag use the methods: set, clear, toggle. and to check for flag use: has'
)