#!/usr/bin/env sh

### Script: psrelative
##
## 関連するプロセスを表示する。
##
## Metadata:
##
##   id - 0a129580-e323-46e5-a4aa-15300e7caf7a
##   author - <qq542vev at https://purl.org/meta/me/>
##   version - 1.0.0
##   date - 2022-07-12
##   since - 2022-06-28
##   copyright - Copyright (C) 2022-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:
##   psrelative [OPTION]... [AXES]... [PID]...
##
## Options:
##   -o FORMAT                   ps コマンドの出力カラムを指定する
##   -h,     --help              このヘルプを表示して終了する
##   -v,     --version           バージョン情報を表示して終了する
##
## Exit Status:
##     0 - successful termination
##    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=psrelative 1.0.0'

. 'initialize.sh'
. 'option_error.sh'

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

	param output  -o           init:='pid,ppid,tty,time,comm' var:FORMAT -- 'ps コマンドの出力カラムを指定する'
	disp  :usage  -h --help    -- 'このヘルプを表示して終了する'
	disp  VERSION -v --version -- 'バージョン情報を表示して終了する'

	msg -- '' 'Exit Status:' \
		'    0 - successful termination' \
		'   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}"

case "${#}" in '0')
	ps -A -o "${output}"
	exit
esac

axes="${1}"
shift

case "${axes}" in
	'ancestor' | 'ancestor-or-self' | 'child' | 'descendant' | 'descendant-or-self' | 'parent' | 'sibling') ;;
	*)
		printf "%s: '%s' -- AXES の値が不正です。\\n" "${0##*/}" "${axes}" >&2
		printf "詳細については '%s' を実行してください。\\n" "${0##*/} --help" >&2

		end_call "${EX_USAGE}"
		;;
esac

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

	BEGIN {
		argCount = split("", argPids)
		split("", ppidList)
		split("", output)

		for(i = 1; i < ARGC; i++) {
			argPids[++argCount] = ARGV[i]
			ARGV[i] = ""
		}
	}

	NR != 1{
		ppidList[$1] = $2
		output[$1] = $0
		gsub(/^ *[^ ]+ *[^ ]+ /, "", output[$1])
	}

	END {
		for(i = 1; i <= argCount; i++) {
			if(i != 1) {
				printf("---\n")
			}

			if(argPids[i] in ppidList) {
				if(axes == "ancestor-or-self" || axes == "descendant-or-self") {
					printf("%s\n", output[argPids[i]])

					gsub("-or-self", "", axes)
				}

				if(axes == "parent") {
					ancestor(argPids[i], ppidList, result, 1)
				} else if(axes == "ancestor") {
					ancestor(argPids[i], ppidList, result)
				} else if(axes == "child") {
					descendant(argPids[i], ppidList, result, 1)
				} else if(axes == "descendant") {
					descendant(argPids[i], ppidList, result)
				} else if(axes == "sibling") {
					descendant(ppidList[argPids[i]], ppidList, result, 1)

					for(key in result) {
						if(result[key] == argPids[i]) {
							delete result[key]
						}
					}
				}

				len = array_length(result)

				for(key = 1; i <= len; key++) {
					printf("%s\n", output[result[key]])
				}
			}
		}
	}

	function ancestor(pid, ppidList, result, count,  i) {
		split("", result)

		if(count == "") {
			count = -1
		}

		for(i = 1; (count != 0) && (pid in ppidList); i++) {
			result[i] = ppidList[pid]
			pid = ppidList[pid]
			count--
		}
	}

	function descendant(pid, ppidList, result, count,  i,key,_result) {
		i = split("", result)

		if(count == "") {
			count = -1
		}

		for(key in ppidList) {
			if((count != 0) && (ppidList[key] == pid)) {
				result[++i] = key
				descendant(key, ppidList, _result, count - 1)

				for(key in _result) {
					result[++i] = _result[key]
				}
			}
		}
	}
	__EOF__
)

ps -A -o 'pid=PID,ppid=PPID' -o "${output}" \
| awk \
	-v "axes=${axes}" \
	-- "${awkScript}" ${@+"${@}"}
