#!/usr/bin/env sh

### Script: searchuri
##
## 検索結果の URI を表示する。
##
## Metadata:
##
##   id - 12c58c44-69eb-451f-890e-21224708bf33
##   author - <qq542vev at https://purl.org/meta/me/>
##   version - 2.1.2
##   date - 2022-10-12
##   since - 2020-05-26
##   copyright - Copyright (C) 2020-2022 qq542vev. Some rights reserved.
##   license - <CC-BY at https://creativecommons.org/licenses/by/4.0/>
##   package - w3mplus
##
## See Also:
##
##   * <Project homepage at https://github.com/qq542vev/w3mplus>
##   * <Bug report at https://github.com/qq542vev/w3mplus/issues>
##
## Help Output:
##
## ------ Text ------
## Usage:
##   searchuri [OPTION]... [KEYWORD]...
##
## Options:
##   -c,     --config FILE       設定ファイルを指定する
##   -d,     --designator disable | event | all 
##                               イベント指示子を有効にする
##   -e,     --engine NAME_LIST  検索エンジンを指定する
##   -H,     --history FILE      検索履歴をログファイルを指定する
##   -s,     --size UNSIGNED_INTEGER 
##                               記録する検索履歴数を指定する
##   -h,     --help              このヘルプを表示して終了する
##   -v,     --version           バージョン情報を表示して終了する
##
## Exit Status:
##     0 - successful termination
##     1 - search engine not found
##    64 - command line usage error
##    65 - data format error
##    66 - cannot open input
##    67 - addressee unknown
##    68 - host name unknown
##    69 - service unavailable
##    70 - internal software error
##    71 - system error (e.g., can't fork)
##    72 - critical OS file missing
##    73 - can't create (user) output file
##    74 - input/output error
##    75 - temp failure; user is invited to retry
##    76 - remote error in protocol
##    77 - permission denied
##    78 - configuration error
##   129 - received SIGHUP
##   130 - received SIGINT
##   131 - received SIGQUIT
##   143 - received SIGTERM
## ------------------

readonly 'VERSION=searchuri 2.1.2'

. 'initialize.sh'
. 'option_error.sh'
. 'regex_match.sh'
. 'awkv_escape.sh'

# @getoptions
parser_definition() {
	setup REST abbr:true error:option_error plus:true no:0 help:usage \
		-- 'Usage:' "  ${2##*/} [OPTION]... [KEYWORD]..." \
		'' 'Options:'

	param config  -c --config  init:'config="${HOME}/.w3mplus/search-config"' var:FILE -- '設定ファイルを指定する'
	param edFlag  -d --designator init:='disable' pattern:'disable | event | all' var:'disable | event | all' -- 'イベント指示子を有効にする'
	param engine  -e --engine  init:='google' validate:'regex_match "${OPTARG}" "^[0-9A-Za-z_.-]+(,[0-9A-Za-z_.-]+)*$"' var:NAME_LIST -- '検索エンジンを指定する'
	param history -H --history init:'history="${HOME}/.w3mplus/search-history"' var:FILE -- '検索履歴をログファイルを指定する'
	param size    -s --size    init:='50000' validate:'regex_match "${OPTARG}" "^(0|[1-9][0-9]*)$"' var:UNSIGNED_INTEGER -- '記録する検索履歴数を指定する'
	disp  :usage  -h --help    -- 'このヘルプを表示して終了する'
	disp  VERSION -v --version -- 'バージョン情報を表示して終了する'

	msg -- '' 'Exit Status:' \
		'    0 - successful termination' \
		'    1 - search engine not found' \
		'   64 - command line usage error' \
		'   65 - data format error' \
		'   66 - cannot open input' \
		'   67 - addressee unknown' \
		'   68 - host name unknown' \
		'   69 - service unavailable' \
		'   70 - internal software error' \
		"   71 - system error (e.g., can't fork)" \
		'   72 - critical OS file missing' \
		"   73 - can't create (user) output file" \
		'   74 - input/output error' \
		'   75 - temp failure; user is invited to retry' \
		'   76 - remote error in protocol' \
		'   77 - permission denied' \
		'   78 - configuration error' \
		'  129 - received SIGHUP' \
		'  130 - received SIGINT' \
		'  131 - received SIGQUIT' \
		'  143 - received SIGTERM'
}
# @end

# @gengetoptions parser -i parser_definition parse "${1}"
# @end

eval "$(getoptions parser_definition parse "${0}")"
parse ${@+"${@}"}
eval "set -- ${REST}"

tmpDir=$(mktemp -d)

case "${config}" in
	'') config="${tmpDir}/config"
esac

if [ '!' -e "${config}" ]; then
	configDir=$(dirname -- "${config}"; printf '_')
	mkdir -p -- "${configDir%?_}"

	: >"${config}"
fi

if [ '!' -f "${config}" ]; then
	printf "%s: '%s' は通常ファイルではありません。\\n" "${0##*/}" "${config}" >&2
	printf "詳細については '%s' を実行してください。\\n" "${0##*/} --help" >&2

	end_call "${EX_DATAERR}"
elif [ '!' -r "${config}" ]; then
	printf "%s: '%s' の読み込み許可がありません。\\n" "${0##*/}" "${config}" >&2
	printf "詳細については '%s' を実行してください。\\n" "${0##*/} --help" >&2

	end_call "${EX_NOINPUT}"
fi

if [ '!' -s "${config}" ]; then
	cat <<-'__EOF__' >"${config}"
		bing	https://www.bing.com/search?q={searchTerms}
		google	https://www.google.com/search?ie=UTF-8&oe=UTF-8&q={searchTerms}
	__EOF__
fi

case "${history}" in
	'') history="${tmpDir}/history"
esac

if [ '!' -e "${history}" ]; then
	historyDir=$(dirname -- "${history}"; printf '_')
	mkdir -p -- "${historyDir%?_}"

	: >"${history}"
fi

if [ '!' -f "${history}" ]; then
	printf "%s: '%s' は通常ファイルではありません。\\n" "${0##*/}" "${history}" >&2
	printf "詳細については '%s' を実行してください。\\n" "${0##*/} --help" >&2

	end_call "${EX_DATAERR}"
elif [ '!' -r "${history}" ]; then
	printf "%s: '%s' の読み込み許可がありません。\\n" "${0##*/}" "${history}" >&2
	printf "詳細については '%s' を実行してください。\\n" "${0##*/} --help" >&2

	end_call "${EX_NOINPUT}"
fi

case "${#}" in '0')
	set -- "$(cat; printf '_')"
	set -- "${1%_}"
esac

awkScript=$(
	cat <<-'__EOF__'
	@include "url_encode.awk"
	@include "shell_argument.awk"
	@include "array_print.awk"
	@include "sysexits.awk"

	BEGIN {
		reverse = sprintf("sort -k '1,1nr' -t '\t' -- %s", shell_argument(history))
		dPattern = "(:(\\$|[1-9][0-9]*(-\\$|-[1-9][0-9]*)?|-(\\$|-[1-9][0-9]*)))?"
		output = ""

		for(i = 1; i < ARGC; i++) {
			arg = ARGV[i]
			keyword = ""

			if((edFlag == "event" && event(arg, "^!#$", result)) || (edFlag == "all" && event(arg, "^!#" dPattern "$", result))) {
				keyword = designators(substr(output, 4), result["designators"])
			} else if((edFlag == "event" && event(arg, "^![1-9][0-9]*$", result)) || (edFlag == "all" && event(arg, "^![1-9][0-9]*" dPattern "$", result))) {
				number = substr(result["event"], 2)

				while(0 < (getline < history)) {
					if($1 == number) {
						keyword = designators($2, result["designators"])
						break
					}
				}

				close(history)
			} else if((edFlag == "event" && event(arg, "^!(!|-[1-9][0-9]*)$", result)) || (edFlag == "all" && event(arg, "^!(!|-[1-9][0-9]*)" dPattern "$", result))) {
				if(result["event"] ~ /^!!/) {
					number = 1
				} else {
					number = substr(result["event"], 3)
				}

				for(nr = 1; 0 < (reverse | getline); nr++) {
					if(nr == number) {
						keyword = designators($2, result["designators"])
						break
					}
				}

				close(reverse)
			} else if((edFlag == "event" && event(arg, "^!\\?[^?]+\\$", result)) || (edFlag == "all" && event(arg, "^!\\?[^?]+\\?" dPattern "$", result))) {
				search = url_encode(substr(result["event"], 3, length(result["event"]) - 3))

				while(0 < (reverse | getline)) {
					if($2 ~ search) {
						keyword = designators($2, result["designators"])
						break
					}
				}

				close(reverse)
			} else if((edFlag == "event" && event(arg, "^![^\t\n\r :]+$", result)) || (edFlag == "all" && event(arg, "^![^\t\n\r :]+" dPattern "$", result))) {
				search = url_encode(substr(result["event"], 2))

				while(0 < (reverse | getline)) {
					if($2 ~ "^" search) {
						keyword = designators($2, result["designators"])
						break
					}
				}

				close(reverse)
			} else {
				keyword = url_encode(arg)
			}

			if(keyword != "") {
				output = output "%20" keyword
			} else if(result["event"] != "") {
				printf("event '%s' not found.\n", arg) | "cat >&2"

				exit sysexits("EX_USAGE")
			}
		}

		printf("%s", substr(output, 4))

		exit
	}

	function event(string, pattern, result,  position) {
		split("", result)

		if(string ~ pattern) {
			if(position = index(string, ":")) {
				result["event"] = substr(string, 1, position - 1)
				result["designators"] = substr(string, position)
			} else {
				result["event"] = string
				result["designators"] = ""
			}

			return 1
		}

		return 0
	}

	function designators(string, des,  words,count,position) {
		if(match(des, /^:(\$|[1-9][0-9]*(-$|-[1-9][0-9]*)?|-($|-[1-9][0-9]*))$/)) {
			count = split(string, words, /(%09|%0A|%0a|%0D|%0d|%20)+/)
			position = index(des, "-")

			if(position) {
				start = substr(des, 2, position - 2)
				end = substr(des, position + 1)
			} else {
				start = end = substr(des, 2)
			}

			if(start == "") {
				start = 1
			} else if(start == "$") {
				start = count
			}

			if(end == "" || end == "$") {
				end = count
			}

			return array_print(words, start, end, "%20")
		}

		return string
	}
	__EOF__
)

awkv_escape 'awkvHistory' "${history}"
encoded=$(
	awk \
		-v "history=${awkvHistory}" \
		-v "edFlag=${edFlag}" \
		"${awkScript}" ${@+"${@}"}
) || end_call "${?}"
newNumber=$(
	cat <<-'__EOF__'
	BEGIN {
		number = 1
	}

	{
		number = $1 + 1
	}

	END {
		printf("%d", number)
	}
	__EOF__
)

case "${encoded}" in ?*)
	{
		cat -- "${history}"
		printf '%d\t%s\t%s\t%s\n' "$(tail -n 1 -- "${history}" | awk -- "${newNumber}")" "${encoded}" "${engine}" "$(date -u -- '+%Y-%m-%dT%H:%M:%SZ')"
	} | tail -n "${size}" >"${tmpDir}/history"

	cat -- "${tmpDir}/history" >"${history}"
esac

awkScript=$(
	cat <<-'__EOF__'
	@include "sysexits.awk"

	BEGIN {
		FS = "\t"
		exitStatus = 0

		count = split(engine, engines, ",")
		split("", template)
	}

	{
		template[$1] = $2
	}

	END {
		for(i = 1; i <= count; i++) {
			if(engines[i] in template) {
				url = template[engines[i]]
				gsub(/\{searchTerms\}/, terms, url)

				printf("%s\n", url)
			} else {
				printf("search engine '%s' not found.\n", engines[i]) | "cat >&2"

				exitStatus = sysexits("EX_UNAVAILABLE")
			}
		}

		exit exitStatus
	}
	__EOF__
)

awk \
	-v "terms=${encoded}" \
	-v "engine=${engine}" \
	-- "${awkScript}" \
	"${config}" \
|| end_call "${?}"
