module main

import os
import term
import regex
import os.cmdline

// Finder is entity that contains all the logic
struct Finder {
mut:
	symbol   Symbol
	visib    Visibility
	mutab    Mutability
	name     string
	modul    string
	receiver string
	dirs     []string
	matches  []Match
}

fn (mut fdr Finder) configure_from_arguments(args []string) {
	match args.len {
		1 {
			fdr.name = args[0]
		}
		else {
			fdr.symbol.set_from_str(args[0])
			if fdr.symbol == .method && !args[1].contains('.') {
				make_and_print_error('method require a special notation:', [
					'Receiver.method',
				], '${args[1]}')
			} else if fdr.symbol == .method {
				temp_args := args[1].split('.')
				fdr.receiver = temp_args[0]
				fdr.name = temp_args[1]
			} else {
				fdr.name = args[1]
			}
			if fdr.name.contains('-') {
				make_and_print_error('It seems you forgot positional arg name:', [], fdr.name)
			}
			fdr.visib.set_from_str(cmdline.option(args, '-vis', '${Visibility.all}'))
			if fdr.symbol == .var && fdr.visib != .all {
				make_and_print_error('-vis ${fdr.visib} just can be set with symbol_type:',
					['fn', 'method', 'const', 'struct', 'enum', 'interface', 'regexp'],
					'${fdr.symbol}')
			}
			fdr.mutab.set_from_str(cmdline.option(args, '-mut', '${Mutability.any}'))
			if fdr.symbol != .var && fdr.mutab != .any {
				make_and_print_error('-mut ${fdr.mutab} just can be set with symbol_type:',
					['var'], '${fdr.symbol}')
			}
			fdr.modul = cmdline.option(args, '-mod', '')
			fdr.dirs = cmdline.options(args, '-dir')
		}
	}
}

fn (mut fdr Finder) search_for_matches() {
	// Define where search
	mut recursive := true
	mut paths_to_search := []string{}
	if fdr.dirs.len == 0 && fdr.modul == '' {
		paths_to_search << [current_dir, vmod_dir]
		if vlib_dir !in paths_to_search && paths_to_search.all(!vlib_dir.starts_with(it)) {
			paths_to_search << vlib_dir
		}
		paths_to_search << vmod_paths
	} else if fdr.dirs.len == 0 && fdr.modul != '' {
		paths_to_search << if fdr.modul == 'main' { current_dir } else { resolve_module(fdr.modul) or {
				panic(err)} }
	} else if fdr.dirs.len != 0 && fdr.modul == '' {
		recursive = false
		paths_to_search << fdr.dirs.map(resolve_module(it) or { panic(err) })
	} else {
		recursive = false
		paths_to_search << if fdr.modul == 'main' { current_dir } else { resolve_module(fdr.modul) or {
				panic(err)} }
		paths_to_search << fdr.dirs.map(resolve_module(it) or { panic(err) })
	}
	// for p in paths_to_search {
	// 	println(p)
	// }
	mut files_to_search := []string{}
	for path in paths_to_search {
		files_to_search << collect_v_files(path, recursive) or { panic(err) }
	}
	// for f in files_to_search {
	// 	println(f)
	// }

	// Auxiliary rgx
	sp := r'\s*'
	op := r'\('
	cp := r'\)'

	// Build regex query
	sy := '${fdr.symbol}'
	st := if fdr.receiver != '' {
		'${sp}${op}${sp}[a-z].*${sp}${fdr.receiver}${cp}${sp}'
	} else {
		'.*'
	}
	na := '${fdr.name}'

	query := match fdr.symbol {
		.@fn {
			'.*${sy}${sp}${na}${sp}${op}.*${cp}.*'
		}
		.method {
			'.*fn${st}${na}${sp}${op}.*${cp}.*'
		}
		.var {
			'.*${na}${sp}:=.*'
		}
		.@const {
			'.*${na}${sp} = .*'
		}
		.regexp {
			'${na}'
		}
		else {
			'.*${sy}${sp}${na}${sp}.*' // for struct, enum and interface
		}
	}
	// println(query)
	for file in files_to_search {
		fdr.search_within_file(file, query)
	}
}

fn (mut fdr Finder) search_within_file(file string, query string) {
	mut re := regex.regex_opt(query) or { panic(err) }
	lines := os.read_lines(file) or { panic(err) }
	mut const_found := if fdr.symbol == .@const { false } else { true }
	mut n_line := 1
	for line in lines {
		match fdr.visib {
			.all {
				if line.contains('const (') {
					const_found = true
				}
			}
			.@pub {
				if line.contains('pub const (') {
					const_found = true
				}
			}
			.pri {
				if line.contains('const (') && !line.contains('pub') {
					const_found = true
				}
			}
		}
		if re.matches_string(line) && (const_found || line.contains('const')) {
			words := line.split(' ').filter(it != '').map(it.trim('\t'))
			match fdr.visib {
				.all {}
				.@pub {
					if 'pub' !in words && fdr.symbol != .@const {
						continue
					}
				}
				.pri {
					if 'pub' in words && fdr.symbol != .@const {
						continue
					}
				}
			}
			match fdr.mutab {
				.any {}
				.yes {
					if 'mut' !in words {
						continue
					}
				}
				.not {
					if 'mut' in words {
						continue
					}
				}
			}
			fdr.matches << Match{file, n_line, words.join(' ').trim(' {')}
		}
		if line.starts_with(')') && fdr.symbol == .@const {
			const_found = false
		}
		n_line++
	}
}

fn (fdr Finder) show_results() {
	if fdr.matches.len < 1 && (verbose || header) {
		print(fdr)
		println(maybe_color(term.bright_yellow, 'No Matches found'))
	} else if verbose || header {
		print(fdr)
		println(maybe_color(term.bright_green, '${fdr.matches.len} matches Found\n'))
		for result in fdr.matches {
			result.show()
		}
	} else {
		for result in fdr.matches {
			result.show()
		}
	}
}

fn (fdr Finder) str() string {
	v := maybe_color(term.bright_red, '${fdr.visib}')
	m := maybe_color(term.bright_red, '${fdr.mutab}')
	st := if fdr.receiver != '' { ' ( _ ${fdr.receiver})' } else { '' }
	s := maybe_color(term.bright_magenta, '${fdr.symbol}')
	n := maybe_color(term.bright_cyan, '${fdr.name}')

	mm := if fdr.modul != '' { maybe_color(term.blue, '${fdr.modul}') } else { '' }
	dd := if fdr.dirs.len != 0 {
		fdr.dirs.map(maybe_color(term.blue, it))
	} else {
		fdr.dirs
	}

	dm := if fdr.dirs.len == 0 && fdr.modul == '' {
		'all the project scope'
	} else if fdr.dirs.len == 0 && fdr.modul != '' {
		'module ${mm}'
	} else if fdr.dirs.len != 0 && fdr.modul == '' {
		'directories: ${dd}'
	} else {
		'module ${mm} searching within directories: ${dd}'
	}

	return '\nFind: ${s}${st} ${n} | visibility: ${v} mutability: ${m}\nwithin ${dm} '
}

// Match is one result of the search_for_matches() process
struct Match {
	path string @[required]
	line int    @[required]
	text string @[required]
}

fn (mtc Match) show() {
	path := maybe_color(term.bright_magenta, mtc.path)
	line := maybe_color(term.bright_yellow, '${mtc.line}')
	text := maybe_color(term.bright_green, '${mtc.text}')
	if verbose || format {
		println('${path}\n${line} : [ ${text} ]\n')
	} else {
		println('${path}:${line}: ${text}')
	}
}
