#!/bin/bash

directory=$(dirname "${BASH_SOURCE[0]}")
source "$directory/utils.bash"


# Constants
lint_try_regex="^(run[[:space:]]+)?[[:space:]]*try[[:space:]]+(.*)$"
lint_verify_regex="^(run[[:space:]]+)?[[:space:]]*verify[[:space:]]+(.*)$"

# Global variables
errors_count=0
verified_entries_count=0


# Verifies the syntax of DETIK queries.
# @param {string} A file path
# @return
#	Any integer above 0: the number of found errors
#	0 Everything is fine
lint() {

	# Verify the file exists
	if [ ! -f "$1" ]; then
		handle_error "'$1' does not exist or is not a regular file."
		return 1
	fi

	# Make the regular expression case-insensitive
	shopt -s nocasematch;

	current_line=""
	current_line_number=0
	user_line_number=0
	multi_line=1
	was_multi_line=1
	while IFS='' read -r line || [[ -n "$line" ]]; do

		# Increase the line number
		current_line_number=$((current_line_number + 1))

		# Debug
		detik_debug "Read line $current_line_number: $line"

		# Skip empty lines and comments
		if [[ -z "$line" ]] || [[ "$line" =~ ^[[:space:]]*#.* ]]; then
			if [[ "$multi_line" == "0" ]]; then
				handle_error "Incomplete multi-line statement at $current_line_number."
				current_line=""
			fi
			continue
		fi

		# Is this line a part of a multi-line statement?
		was_multi_line="$multi_line"
		[[ "$line" =~ ^.*\\$ ]]
		multi_line="$?"

		# Do we need to update the user line number?
		if [[ "$was_multi_line" != "0" ]]; then
			user_line_number="$current_line_number"
		fi

		# Is this the continuation of a previous line?
		if [[ "$multi_line" == "0" ]]; then
			current_line="$current_line ${line::-1}"
		elif [[ "$was_multi_line" == "0" ]]; then
			current_line="$current_line $line"
		else
			current_line="$line"
		fi

		# When we have a complete line...
		if [[ "$multi_line" != "0" ]]; then
			check_line "$current_line" "$user_line_number"
			current_line=""
		fi
		line=""
	done < "$1"

	# Output
	if [[ "$verified_entries_count" == "1" ]]; then
		echo "1 DETIK query was verified."
	else
		echo "$verified_entries_count DETIK queries were verified."
	fi

	if [[ "$errors_count" == "1" ]]; then
		echo "1 DETIK query was found to be invalid or malformed."
	else
		echo "$errors_count DETIK queries were found to be invalid or malformed."
	fi

	# Prepare the result
	res="$errors_count"

	# Reset global variables
	errors_count=0
	verified_entries_count=0

	return "$res"
}


# Verifies the correctness of a read line.
# @param {string} The line to verify
# @param {integer} The line number
# @return 0
check_line() {

	# Make the regular expression case-insensitive
	shopt -s nocasematch;

	# Get parameters and prepare the line
	line="$1"
	line_number="$2"
	context="Current line: $line"

	line=$(echo "$line" | sed -e 's/"[[:space:]]*"//g')
	line=$(trim "$line")
	context="$context\nPurged line:  $line"

	# Basic case: "run try", "run verify", "try", "verify" alone
	if [[ "$line" =~ ^(run[[:space:]]+)?try$ ]] || [[ "$line" =~ ^(run[[:space:]]+)?verify$ ]]; then
		verified_entries_count=$((verified_entries_count + 1))
		handle_error "Empty statement at line $line_number." "$context"

	# We have "try" or "run try" followed by something
	elif [[ "$line" =~ $lint_try_regex ]]; then
		verified_entries_count=$((verified_entries_count + 1))

		part=$(clean_regex_part "${BASH_REMATCH[2]}")
		context="$context\nRegex part:  $part"

		verify_against_pattern "$part" "$try_regex_verify_is"
		p_verify_is="$?"

		verify_against_pattern "$part" "$try_regex_verify_matches"
		p_verify_matches="$?"

		verify_against_pattern "$part" "$try_regex_find_being"
		p_find_being="$?"

		verify_against_pattern "$part" "$try_regex_find_matching"
		p_find_matching="$?"

		# detik_debug "p_verify=$p_verify, p_find=$p_find, part=$part"
		if [[ "$p_verify_is" != "0" ]] && \
		   [[ "$p_verify_matches" != "0" ]] && \
		   [[ "$p_find_being" != "0" ]] && \
		   [[ "$p_find_matching" != "0" ]]; then
				handle_error "Invalid TRY statement at line $line_number." "$context"
		fi

	# We have "verify" or "run verify" followed by something
	elif [[ "$line" =~ $lint_verify_regex ]]; then
		verified_entries_count=$((verified_entries_count + 1))

		part=$(clean_regex_part "${BASH_REMATCH[2]}")
		context="$context\nRegex part:  $part"

		verify_against_pattern "$part" "$verify_regex_count_is"
		p_is="$?"

		verify_against_pattern "$part" "$verify_regex_count_are"
		p_are="$?"

		verify_against_pattern "$part" "$verify_regex_property_is"
		p_prop="$?"

		verify_against_pattern "$part" "$verify_regex_property_matches"
		p_matches="$?"

		# detik_debug "p_is=$p_is, p_are=$p_are, p_prop=$p_prop, part=$part"
		if [[ "$p_is" != "0" ]] && \
		   [[ "$p_are" != "0" ]]  && \
		   [[ "$p_prop" != "0" ]] && \
		   [[ "$p_matches" != "0" ]] ; then
				handle_error "Invalid VERIFY statement at line $line_number." "$context"
		fi
	fi
}


# Cleans a string before being checked by a regexp.
# @param {string} The string to clean
# @return 0
clean_regex_part() {

	part=$(trim "$1")
	part=$(remove_surrounding_quotes "$part")
	part=$(trim "$part")
	echo "$part"
}


# Removes surrounding quotes.
# @param {string} The string to clean
# @return 0
remove_surrounding_quotes() {

	# Starting and ending with a quote? Remove them.
	if [[ "$1" =~ ^\"(.*)\"$ ]]; then
		echo "${BASH_REMATCH[1]}"

	# Otherwise, ignore it
	else
		echo "$1"
	fi

	return 0
}


# Verifies an assertion part against a regular expression.
# Given that assertions can skip double quotes around the whole
# assertion, we try the given regular expression and an altered one.
#
# Example: run try at most 5 times ... "'nginx'" ...
#
# Here, "'nginx'" is not part of the default regular expression.
# So, we update it to allow this kind of assertions.
#
# @param {string} The line to verify
# @param {string} The pattern
# @return
#	0 if everything went fine
#	not-zero in case of error
verify_against_pattern() {

	# Make the regular expression case-insensitive
	shopt -s nocasematch;

	line="$1"
	pattern="$2"
	code=0
	if ! [[ "$line" =~ $pattern ]]; then
		line=${line//\"\'/\'}
		line=${line//\'\"/\'}
		[[ "$line" =~ $pattern ]]
		code="$?"
	fi

	return "$code"
}


# Handles an error by printing it and updating the error count.
# @param {string} The error message
# @param2 {string} The error context
# @return 0
handle_error() {

	detik_debug "$2"
	detik_debug "Error: $1"

	echo "$1"
	errors_count=$((errors_count + 1))
}
