#!/usr/bin/env bash
#
# @license Apache-2.0
#
# Copyright (c) 2017 The Stdlib Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# A Git hook called by `git commit`. If this scripts exits with a non-zero status, the commit will be aborted.
#
# This hook is called with no arguments.

# shellcheck disable=SC2181


# VARIABLES #

# Determine root directory:
root=$(git rev-parse --show-toplevel)

# Define the path to a utility for linting filenames:
lint_filenames="${root}/lib/node_modules/@stdlib/_tools/lint/filenames/bin/cli"

# Define the path to a utility for linting package.json files:
lint_package_json="${root}/lib/node_modules/@stdlib/_tools/lint/pkg-json/bin/cli"

# Define the path to a utility for linting REPL help files:
lint_repl_help="${root}/lib/node_modules/@stdlib/_tools/lint/repl-txt/bin/cli"

# Define the path to ESLint configuration file for linting examples:
eslint_examples_conf="${root}/etc/eslint/.eslintrc.examples.js"

# Define the path to ESLint configuration file for linting tests:
eslint_tests_conf="${root}/etc/eslint/.eslintrc.tests.js"

# Define the path to ESLint configuration file for linting benchmarks:
eslint_benchmarks_conf="${root}/etc/eslint/.eslintrc.benchmarks.js"

# Define the path to cppcheck configuration file for linting examples:
cppcheck_examples_suppressions_list="${root}/etc/cppcheck/suppressions.examples.txt"

# Define the path to cppcheck configuration file for linting test fixtures:
cppcheck_tests_fixtures_suppressions_list="${root}/etc/cppcheck/suppressions.tests_fixtures.txt"

# Define the path to cppcheck configuration file for linting benchmarks:
cppcheck_benchmarks_suppressions_list="${root}/etc/cppcheck/suppressions.benchmarks.txt"


# FUNCTIONS #

# Defines an error handler.
#
# $1 - error status
on_error() {
	cleanup
	exit "$1"
}

# Runs clean-up tasks.
cleanup() {
	echo '' >&2
}

# Runs initialization tasks.
init() {
	return 0
}

# Checks for non-ASCII filenames (to ensure cross platform portability).
check_filenames() {
	local num_files
	local against
	local commit

	commit=$(git rev-parse --verify HEAD)
	if [[ -z "${commit}" ]]; then
		# This is the initial commit, so we diff against an empty tree object:
		against='4b825dc642cb6eb9a060e54bf8d69288fbee4904'
	else
		against='HEAD'
	fi
	# We exploit the fact that the printable range starts with the space character and ends with the tilde. Note that the use of brackets around a `tr` range is okay here, (for portability to Solaris 10's /usr/bin/tr, it's even required), since the square bracket bytes happen to fall in the designated range.
	num_files=$(git diff --cached --name-only --diff-filter=ACR -z "${against}" | LC_ALL=C tr -d '[ -~]\0' | wc -c)

	if [[ "${num_files}" -ne 0 ]]; then
		echo 'Error: Attempting to add a non-ASCII filename. Non-ASCII filenames limit cross-platform portability. Please rename offending files before committing.' >&2
		return 1
	fi
	return 0
}

# Lints staged files.
run_lint() {
	local changed_files
	local files

	# Get the set of changed files (added, copied, modified, and renamed):
	changed_files=$(git diff --name-only --cached --diff-filter ACMR)

	# Lint filenames:
	echo "${changed_files}" | "${lint_filenames}"
	if [[ "$?" -ne 0 ]]; then
		echo '' >&2
		echo 'Filename lint errors.' >&2
		return 1
	fi

	# Lint Markdown files...
	files=$(echo "${changed_files}" | grep '\.md$' | tr '\n' ' ')
	if [[ -n "${files}" ]]; then
		make FILES="${files}" lint-markdown-files > /dev/null >&2
		if [[ "$?" -ne 0 ]]; then
			echo '' >&2
			echo 'Markdown lint errors.' >&2
			return 1
		fi
	fi

	# Lint package.json files...
	files=$(echo "${changed_files}" | grep 'package\.json$' | grep -v 'datapackage\.json$' )
	if [[ -n "${files}" ]]; then
		echo "${files}" | "${lint_package_json}" >&2
		if [[ "$?" -ne 0 ]]; then
			echo '' >&2
			echo 'Package.json lint errors.' >&2
			return 1
		fi
	fi

	# Lint REPL help files...
	files=$(echo "${changed_files}" | grep 'repl\.txt$' )
	if [[ -n "${files}" ]]; then
		echo "${files}" | "${lint_repl_help}" >&2
		if [[ "$?" -ne 0 ]]; then
			echo '' >&2
			echo 'REPL help lint errors.' >&2
			return 1
		fi
	fi

	# Lint JavaScript source files...
	files=$(echo "${changed_files}" | grep '\.js$' | grep -v -e '/examples' -e '/test' -e '/benchmark' -e '^dist/' | tr '\n' ' ')
	if [[ -n "${files}" ]]; then
		make FILES="${files}" lint-javascript-files > /dev/null >&2
		if [[ "$?" -ne 0 ]]; then
			echo '' >&2
			echo 'JavaScript lint errors for source files.' >&2
			return 1
		fi
	fi

	# Lint JavaScript command-line interfaces...
	files=$(echo "${changed_files}" | grep '/bin/cli$' | tr '\n' ' ')
	if [[ -n "${files}" ]]; then
		make FILES="${files}" lint-javascript-files > /dev/null >&2
		if [[ "$?" -ne 0 ]]; then
			echo '' >&2
			echo 'JavaScript lint errors for command-line interface files.' >&2
			return 1
		fi
	fi

	# Lint JavaScript examples files...
	files=$(echo "${changed_files}" | grep '/examples/.*\.js$' | tr '\n' ' ')
	if [[ -n "${files}" ]]; then
		make JAVASCRIPT_LINTER=eslint ESLINT_CONF="${eslint_examples_conf}" FILES="${files}" lint-javascript-files > /dev/null >&2
		if [[ "$?" -ne 0 ]]; then
			echo '' >&2
			echo 'JavaScript lint errors for example files.' >&2
			return 1
		fi
	fi

	# Lint JavaScript test files...
	files=$(echo "${changed_files}" | grep '/test/.*\.js$' | tr '\n' ' ')
	if [[ -n "${files}" ]]; then
		make JAVASCRIPT_LINTER=eslint ESLINT_CONF="${eslint_tests_conf}" FILES="${files}" lint-javascript-files > /dev/null >&2
		if [[ "$?" -ne 0 ]]; then
			echo '' >&2
			echo 'JavaScript lint errors for test files.' >&2
			return 1
		fi
	fi

	# Lint JavaScript benchmark files...
	files=$(echo "${changed_files}" | grep '/benchmark/.*\.js$' | tr '\n' ' ')
	if [[ -n "${files}" ]]; then
		make JAVASCRIPT_LINTER=eslint ESLINT_CONF="${eslint_benchmarks_conf}" FILES="${files}" lint-javascript-files > /dev/null >&2
		if [[ "$?" -ne 0 ]]; then
			echo '' >&2
			echo 'JavaScript lint errors for benchmark files.' >&2
			return 1
		fi
	fi

	# Lint Python files...
	files=$(echo "${changed_files}" | grep '\.py$' | tr '\n' ' ')
	if [[ -n "${files}" ]]; then
		make check-python-linters > /dev/null >&2
		if [[ "$?" -ne 0 ]]; then
			echo '' >&2
			echo 'Unable to lint Python files. Ensure that linters are installed.' >&2
		else
			make FILES="${files}" lint-python-files > /dev/null >&2
			if [[ "$?" -ne 0 ]]; then
				echo '' >&2
				echo 'Python lint errors.' >&2
				return 1
			fi
		fi
	fi

	# Lint R files...
	files=$(echo "${changed_files}" | grep '\.R$' | tr '\n' ' ')
	if [[ -n "${files}" ]]; then
		make FILES="${files}" lint-r-files > /dev/null >&2
		if [[ "$?" -ne 0 ]]; then
			echo '' >&2
			echo 'R lint errors.' >&2
			return 1
		fi
	fi

	# Lint C source files...
	files=$(echo "${changed_files}" | grep '\.c$' | grep -v -e '/examples' -e '/test' -e '/benchmark' | tr '\n' ' ')
	if [[ -n "${files}" ]]; then
		make check-c-linters > /dev/null >&2
		if [[ "$?" -ne 0 ]]; then
			echo '' >&2
			echo 'Unable to lint C files. Ensure that linters are installed.' >&2
		else
			make FILES="${files}" lint-c-files > /dev/null >&2
			if [[ "$?" -ne 0 ]]; then
				echo '' >&2
				echo 'C lint errors for source files.' >&2
				return 1
			fi
		fi
	fi

	# Lint C examples files...
	files=$(echo "${changed_files}" | grep '/examples/.*\.c$' | tr '\n' ' ')
	if [[ -n "${files}" ]]; then
		make check-c-linters > /dev/null >&2
		if [[ "$?" -ne 0 ]]; then
			echo '' >&2
			echo 'Unable to lint C files. Ensure that linters are installed.' >&2
		else
			make C_LINTER=cppcheck CPPCHECK_SUPPRESSIONS_LIST="${cppcheck_examples_suppressions_list}" FILES="${files}" lint-c-files > /dev/null >&2
			if [[ "$?" -ne 0 ]]; then
				echo '' >&2
				echo 'C lint errors for examples files.' >&2
				return 1
			fi
		fi
	fi

	# Lint C benchmark files...
	files=$(echo "${changed_files}" | grep '/benchmark/.*\.c$' | tr '\n' ' ')
	if [[ -n "${files}" ]]; then
		make check-c-linters > /dev/null >&2
		if [[ "$?" -ne 0 ]]; then
			echo '' >&2
			echo 'Unable to lint C files. Ensure that linters are installed.' >&2
		else
			make C_LINTER=cppcheck CPPCHECK_SUPPRESSIONS_LIST="${cppcheck_benchmarks_suppressions_list}" FILES="${files}" lint-c-files > /dev/null >&2
			if [[ "$?" -ne 0 ]]; then
				echo '' >&2
				echo 'C lint errors for benchmark files.' >&2
				return 1
			fi
		fi
	fi

	# Lint C test fixtures files...
	files=$(echo "${changed_files}" | grep '/test/fixtures/.*\.c$' | tr '\n' ' ')
	if [[ -n "${files}" ]]; then
		make check-c-linters > /dev/null >&2
		if [[ "$?" -ne 0 ]]; then
			echo '' >&2
			echo 'Unable to lint C files. Ensure that linters are installed.' >&2
		else
			make C_LINTER=cppcheck CPPCHECK_SUPPRESSIONS_LIST="${cppcheck_tests_fixtures_suppressions_list}" FILES="${files}" lint-c-files > /dev/null >&2
			if [[ "$?" -ne 0 ]]; then
				echo '' >&2
				echo 'C lint errors for test fixtures files.' >&2
				return 1
			fi
		fi
	fi

	# Lint shell script files...
	files=$(echo "${changed_files}" | while read -r file; do head -n1 "$file" | grep -q '^\#\!/usr/bin/env bash' && echo "$file"; done | tr '\n' ' ')
	if [[ -n "${files}" ]]; then
		make check-shell-linters > /dev/null >&2
		if [[ "$?" -ne 0 ]]; then
			echo '' >&2
			echo 'Unable to lint shell script files. Ensure that linters are installed.' >&2
		else
			make FILES="${files}" lint-shell-files > /dev/null >&2
			if [[ "$?" -ne 0 ]]; then
				echo '' >&2
				echo 'Shell script lint errors.' >&2
				return 1
			fi
		fi
	fi

	# Lint TypeScript declaration files...
	files=$(echo "${changed_files}" | grep '\.d\.ts$' | tr '\n' ' ')
	if [[ -n "${files}" ]]; then
		make TYPESCRIPT_DECLARATIONS_LINTER=dtslint FILES="${files}" lint-typescript-declarations-files > /dev/null >&2
		if [[ "$?" -ne 0 ]]; then
			echo '' >&2
			echo 'TypeScript declaration file lint errors.' >&2
			return 1
		fi
	fi

	# Lint license headers...
	files=$(echo "${changed_files}" | tr '\n' ' ')
	if [[ -n "${files}" ]]; then
		make FILES="${files}" lint-license-headers-files > /dev/null >&2
		if [[ "$?" -ne 0 ]]; then
			echo '' >&2
			echo 'License header lint errors.' >&2
			return 1
		fi
	fi

	# TODO: if datapackage.json, validate via schema

	return 0
}

# Main execution sequence.
main() {
	init
	if [[ "$?" -ne 0 ]]; then
		on_error 1
	fi
	check_filenames
	if [[ "$?" -ne 0 ]]; then
		on_error 1
	fi
	run_lint
	if [[ "$?" -ne 0 ]]; then
		on_error 1
	fi
	cleanup
	exit 0
}

# Run main:
main
