#!/bin/sh
# 2017, 2018, 2019, 2020, 2021 (c) Etersoft https://etersoft.ru
# Author: Vitaly Lipatov <lav@etersoft.ru>
# Public domain

# load common functions, compatible with local and installed script
. `dirname $0`/../share/eterbuild/functions/common
load_mod git

set_girar_host $1 && shift

if [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
	echo "gitask — ssh gear.alt task wrapper"
	echo "Use: gitask [GIRAR] [new|run|commit|add|deps|copy|find|log|show|ls]"
	echo "$HELP_GIRAR"
	echo "     --help - help"
	docmd ssh $GEARHOST task help | sed -e "s|abort|cancel|g"
	echo
	echo "Examples:"
	echo "     ls [-a] [-u USER] [-s STATE] [-r REPO] [-w [N]]  - list tasks (ls --help for details)"
	echo "     new    [branch]             - create new task on branch (Sisyphus by default)"
	echo "     run [--test-only] [NNNN] [NNNN2] [-m <message>] - run task NNNN"
	echo "     commit [NNNN] [NNNN2] [-m <message>]       - commit task(s) NNNN, [NNNN2]"
	echo "     add [NNNN] (repo|build) <gear repo> <gear tag> ... - add package build from repo command"
	echo "     add [NNNN] (repo|build) <gear repo>.git=<gear tag> ...  - add package build from repo command"
	echo "     add [NNNN] del package [package2...]  - add package remove command"
	echo "     add [NNNN] rebuild package [package2...]  - add package rebuild"
	echo "     add [NNNN] copy NNNN [from XX]  - copy package [from (p9|p10|Sisyphus)]"
	echo "     add --help                  - show task add help"
	echo "     Add <args>                  - add and run the task"
	echo "     task --help                 - show task help"
	echo "     share  NNNN [enable]        - share task NNNN"
	echo "     deps   NNNN add XXXX        - add deps from XXXX to task NNNN"
	echo "     copy   NNNN to (p8|p9) [from XX]  - copy package to (p8|p9|p10) [from (p9|p10|Sisyphus)]"
	echo "     delsub NNNN (package|subtask)  - remove subtask by number or by package name"
	echo "     find   PACKAGE              - do find-package of PACKAGE"
	echo "     log    NNNN                 - show build log for task NNNN"
	echo "     show   NNNN [-v]            - show subtask list for task NNNN (-v for full metadata)"
	echo "     quota                       - show quota on the remote servers"
	echo "     cancel NNNN                 - cancel task NNNN"
	echo "     approve NNNN [SUBTASK] [-m <message>] - approve subtask (all subtasks if SUBTASK is omitted)"
	echo "     acl [branch] package        - show acl for the package"
	echo "     acl [branch] package add|del <user> - add/del acl for the package"
	echo "     wait   [NNNN] [-q|--quiet]  - wait for task build to complete"
	echo "     rebuild --help              - rebuild package"
	exit 0
fi

# with support for #NUM
is_task_number()
{
	echo "$*" | estrlist filter_strip_spaces | grep -q "^#\?[0-9]\+$"
}

# clean from possible #
get_task_number()
{
	echo "$*" | estrlist filter_strip_spaces | sed -e "s|^#||"
}

get_last()
{
	local api_base user result
	if api_base=$(_get_api_base) && user=$(_get_girar_user) ; then
		result=$(curl -sf "$api_base/tasks?user=$user&brief=true" 2>/dev/null | \
			python3 -c 'import json,sys;t=json.load(sys.stdin);print(t[0] if t else "")' 2>/dev/null)
		[ -n "$result" ] && echo "$result" && return
	fi
	# fallback
	ssh $GEARHOST task ls | head -n1 | sed -e "s|^#\([0-9]*\) .*|\1|g"
}

get_test_status()
{
	ssh $GEARHOST task ls | grep "^#$1 " | grep "\[test-only\]"
}

# get task status (TESTED, FAILED, EPERM, NEW, AWAITING, BUILDING, PENDING, DONE)
get_task_status()
{
	local api_base result
	if api_base=$(_get_api_base) ; then
		result=$(curl -sf "$api_base/tasks/$1" 2>/dev/null | \
			python3 -c 'import json,sys;print(json.load(sys.stdin).get("state",""))' 2>/dev/null)
		[ -n "$result" ] && echo "$result" && return
	fi
	# fallback
	ssh $GEARHOST task ls | grep "^#$1 " | sed -e "s|^#[0-9]* \([A-Z]*\) .*|\1|"
}

# get subtask number from TASKNUMBER for PROJECTNAME
get_subtask()
{
	local api_base result
	if api_base=$(_get_api_base) ; then
		result=$(curl -sf "$api_base/tasks/$1" 2>/dev/null | \
			python3 -c '
import json, sys
task = json.load(sys.stdin)
pkg = sys.argv[1]
for num, st in task.get("subtasks", {}).items():
    if st.get("pkgname") == pkg or st.get("dir", "").endswith("/" + pkg + ".git"):
        print(num)
        break
' "$2" 2>/dev/null)
		[ -n "$result" ] && echo "$result" && return
	fi
	# fallback
	ssh $GEARHOST task show $1 | grep -E "(/$2.git|:package=$2$|:srpm=$2-.*src.rpm)" | sed -e "s|^ \([0-9]*\):.*|\1|g"
}

# get all subtask numbers from TASKNUMBER
get_all_subtasks()
{
	local api_base result
	if api_base=$(_get_api_base) ; then
		result=$(curl -sf "$api_base/tasks/$1" 2>/dev/null | \
			python3 -c '
import json, sys
task = json.load(sys.stdin)
for num in sorted(task.get("subtasks", {}).keys(), key=int):
    print(num)
' 2>/dev/null)
		[ -n "$result" ] && echo "$result" && return
	fi
	# fallback
	ssh $GEARHOST task show $1 | sed -n 's|^ \([0-9]*\):dir=.*|\1|p'
}

_list_git_package()
{
	while read path date other; do
		printf "%60s  " $GITHOST:$path
		[ -n "$date" ] || { echo "[Date is missed]" ; continue ; }
		date -d"@$date"
	done
}

get_last_from()
{
	local FROMSTR=''
	while [ -n "$1" ] ; do
		[ "$1" = "from" ] && FROMSTR="$2" && break
		shift
	done
	echo "$FROMSTR"
}

# Get API base URL for the current girar host; return 1 if API not available
_get_api_base()
{
	case "$GITHOST" in
		git.alt|gitery)
			echo "https://git.altlinux.org/tasks/api"
			;;
		*)
			return 1
			;;
	esac
}

# Get girar username from SSH config (strip alt_ prefix)
_get_girar_user()
{
	local ssh_user
	ssh_user=$(ssh -G "$GITHOST" 2>/dev/null | sed -n 's/^user //p')
	[ -n "$ssh_user" ] || return 1
	echo "${ssh_user#alt_}"
}

# Fetch approval info for tasks from git.altlinux.org filesystem
# stdin: JSON list of task IDs, stdout: JSON {task_id: {subtask: [users]}}
_fetch_approvals()
{
	python3 -c '
import json, sys, re
from concurrent.futures import ThreadPoolExecutor, as_completed
from urllib.request import urlopen, Request
from urllib.error import URLError

task_ids = json.load(sys.stdin)
base = "https://git.altlinux.org/tasks"
result = {}

def get_approvals(tid):
    """Fetch approval usernames for all subtasks of a task."""
    approvals = {}
    try:
        url = "{}/{}/acl/approved/".format(base, tid)
        with urlopen(Request(url), timeout=5) as resp:
            html = resp.read().decode()
        # extract subtask directories (100/, 200/, etc.)
        for m in re.finditer(r"href=\"(\d+)/\"", html):
            subtask = m.group(1)
            try:
                surl = "{}/{}/acl/approved/{}/".format(base, tid, subtask)
                with urlopen(Request(surl), timeout=5) as resp2:
                    shtml = resp2.read().decode()
                users = re.findall(r"href=\"([a-zA-Z][a-zA-Z0-9_.-]+)\"", shtml)
                if users:
                    approvals[subtask] = users
            except (URLError, OSError):
                pass
    except (URLError, OSError):
        pass
    return tid, approvals

with ThreadPoolExecutor(max_workers=10) as pool:
    futures = {pool.submit(get_approvals, tid): tid for tid in task_ids}
    for f in as_completed(futures):
        tid, approvals = f.result()
        if approvals:
            result[str(tid)] = approvals

json.dump(result, sys.stdout)
'
}

# Format task list from JSON (stdin)
# $1 - use_color (0/1), $2 - limit (0=unlimited)
_format_task_list()
{
	local use_color="$1" limit="$2"
	python3 -c '
import json, sys, re
from concurrent.futures import ThreadPoolExecutor, as_completed
from urllib.request import urlopen, Request
from urllib.error import URLError

use_color = sys.argv[1] == "1"
limit = int(sys.argv[2]) if len(sys.argv) > 2 else 0

colors = {
    "BUILDING": "\033[33m", "COMMITTING": "\033[33m",
    "AWAITING": "\033[36m", "POSTPONED": "\033[36m",
    "TESTED": "\033[32m",
    "FAILED": "\033[35m", "EPERM": "\033[92m",
}
reset = "\033[0m"

tasks = json.load(sys.stdin)

# Collect task IDs that need approval check (TESTED/EPERM only)
base = "https://git.altlinux.org/tasks"
check_ids = []
for i, t in enumerate(tasks):
    if limit and i >= limit:
        break
    if t["state"] in ("TESTED", "EPERM"):
        check_ids.append(t["id"])

# Fetch approvals in parallel
all_approvals = {}  # {task_id_str: {subtask: [users]}}
def get_approvals(tid):
    approvals = {}
    try:
        url = "{}/{}/acl/approved/".format(base, tid)
        with urlopen(Request(url), timeout=5) as resp:
            html = resp.read().decode()
        for m in re.finditer(r"href=\"(\d+)/\"", html):
            subtask = m.group(1)
            try:
                surl = "{}/{}/acl/approved/{}/".format(base, tid, subtask)
                with urlopen(Request(surl), timeout=5) as resp2:
                    shtml = resp2.read().decode()
                users = re.findall(r"href=\"([a-zA-Z][a-zA-Z0-9_.-]+)\"", shtml)
                if users:
                    approvals[subtask] = users
            except (URLError, OSError):
                pass
    except (URLError, OSError):
        pass
    return tid, approvals

if check_ids:
    with ThreadPoolExecutor(max_workers=10) as pool:
        futures = {pool.submit(get_approvals, tid): tid for tid in check_ids}
        for f in as_completed(futures):
            tid, approvals = f.result()
            if approvals:
                all_approvals[str(tid)] = approvals

for i, t in enumerate(tasks):
    if limit and i >= limit:
        break
    flags = []
    if t.get("test_only"): flags.append("[test-only]")
    # try.iter info
    ti = t.get("try", 0)
    it = t.get("iter", 0)
    tryiter = "#{}".format(ti) if it <= 1 else "#{}.{}".format(ti, it)
    # approval info
    task_appr = all_approvals.get(str(t["id"]), {})
    if task_appr:
        # collect unique approvers across all subtasks
        approvers = set()
        for users in task_appr.values():
            approvers.update(users)
        flags.append("[approved:{}]".format(",".join(sorted(approvers))))
    # package names from subtasks
    pkgs = []
    for num in sorted(t.get("subtasks", {}).keys(), key=lambda x: int(x)):
        st = t["subtasks"][num]
        pkg = st.get("pkgname", "")
        tag = st.get("tag_name", "")
        stype = st.get("type", "repo")
        # fallback: extract package name from dir path
        if not pkg:
            d = st.get("dir", "")
            if d:
                pkg = d.rstrip("/").rsplit("/", 1)[-1].removesuffix(".git")
        if stype == "delete":
            pkgs.append("(delete {})".format(pkg))
        elif pkg and tag:
            pkgs.append("{}.git={}".format(pkg, tag))
        elif pkg:
            pkgs.append(pkg)
    tid = str(t["id"])
    if use_color:
        url = "https://packages.altlinux.org/ru/tasks/{}/".format(tid)
        tid = "\033]8;;{}\033\\{}\033]8;;\033\\".format(url, tid)
    parts = [tid, t["state"], tryiter]
    if flags:
        parts.extend(flags)
    parts.append(t["repo"])
    if pkgs:
        parts.extend(pkgs)
    line = " ".join(parts)
    if use_color and t["state"] in colors:
        line = colors[t["state"]] + line + reset
    print(line)
' "$use_color" "$limit"
}

# Format task subtasks from JSON (stdin)
_format_task_subtasks()
{
	python3 -c '
import json, sys, re
from concurrent.futures import ThreadPoolExecutor, as_completed
from urllib.request import urlopen, Request
from urllib.error import URLError

task = json.load(sys.stdin)
flags = []
if task.get("test_only"): flags.append("[test-only]")
msg = task.get("message", "")
header = "#{} {} {} {}".format(task["taskid"], task["state"], task["repo"], task["owner"])
if flags: header += " " + " ".join(flags)
if msg: header += " " + msg
print(header)

# Fetch approvals for this task
base = "https://git.altlinux.org/tasks"
approvals = {}  # {subtask: [users]}
if task["state"] in ("TESTED", "EPERM", "DONE"):
    try:
        url = "{}/{}/acl/approved/".format(base, task["taskid"])
        with urlopen(Request(url), timeout=5) as resp:
            html = resp.read().decode()
        subtask_dirs = re.findall(r"href=\"(\d+)/\"", html)
        def fetch_sub(subtask):
            try:
                surl = "{}/{}/acl/approved/{}/".format(base, task["taskid"], subtask)
                with urlopen(Request(surl), timeout=5) as resp2:
                    shtml = resp2.read().decode()
                return subtask, re.findall(r"href=\"([a-zA-Z][a-zA-Z0-9_.-]+)\"", shtml)
            except (URLError, OSError):
                return subtask, []
        with ThreadPoolExecutor(max_workers=5) as pool:
            for subtask, users in pool.map(lambda s: fetch_sub(s), subtask_dirs):
                if users:
                    approvals[subtask] = users
    except (URLError, OSError):
        pass

for num in sorted(task.get("subtasks", {}).keys(), key=int):
    st = task["subtasks"][num]
    stype = st.get("type", "repo")
    pkg = st.get("pkgname", "")
    if stype == "delete":
        line = " {}: delete {}".format(num, pkg)
    elif stype == "copy":
        copy_from = st.get("copy_repo", "")
        line = " {}: copy {} from {}".format(num, pkg, copy_from)
    else:
        tag = st.get("tag_name", "")
        author = st.get("tag_author", "")
        dir_path = st.get("dir", "")
        line = " {}: {} = {} ({})".format(num, dir_path, tag, author)
    if num in approvals:
        line += " [approved:{}]".format(",".join(approvals[num]))
    print(line)
'
}

do_task_list()
{
	if [ "$1" = "-h" ] || [ "$1" = "--help" ] ; then
		echo "ls - list tasks"
		echo "Usage: gita ls [options] [TASK_NUMBER]"
		echo "  -a, --all                 - list all users, all states"
		echo "  -u, --user USER           - list tasks for USER"
		echo "  -s, --state STATE[,STATE]  - filter by state"
		echo "      states: NEW, AWAITING, PENDING, BUILDING, COMMITTING,"
		echo "              TESTED, FAILED, DONE, EPERM, POSTPONED, ALL"
		echo "  -r, --repo REPO[,REPO]    - filter by repo (e.g. sisyphus, p11)"
		echo "  -w [N]                    - watch mode, refresh every N seconds (default 10)"
		echo "  TASK_NUMBER               - show build log for task"
		return
	fi

	# parse options
	local OPT_USER="" OPT_STATE="" OPT_REPO="" OPT_ALL=""
	while [ -n "$1" ] ; do
		case "$1" in
			--all|-a)
				OPT_ALL=1
				shift
				;;
			--user|-u)
				OPT_USER="$2"
				shift 2
				;;
			--state|-s)
				OPT_STATE="$2"
				shift 2
				;;
			--repo|-r)
				OPT_REPO="$2"
				shift 2
				;;
			-w)
				local WN="$2"
				[ -z "$WN" ] && WN=10
				watch -c -n $WN $0 ls
				return
				;;
			*)
				break
				;;
		esac
	done

	# task number argument → show log (API doesn't provide build logs)
	if [ -n "$1" ] ; then
		showcmd "$GEARHOST>" girar-show "$@"
		GIT_ALT=$GEARHOST girar-show "$@" | stripcolors
		return
	fi

	# build API query
	local api_base user json api_params=""
	if api_base=$(_get_api_base) ; then
		if [ -n "$OPT_ALL" ] ; then
			api_params="state=ALL"
		else
			if [ -n "$OPT_USER" ] ; then
				api_params="user=$OPT_USER"
			else
				user=$(_get_girar_user) || { _do_task_list_ssh "$OPT_ALL" "$OPT_USER" "$OPT_STATE" ; return ; }
				api_params="user=$user"
			fi
			[ -n "$OPT_STATE" ] && api_params="$api_params&state=$OPT_STATE"
		fi
		[ -n "$OPT_REPO" ] && api_params="$api_params&repo=$OPT_REPO"

		json=$(curl -sf "$api_base/tasks?$api_params" 2>/dev/null)
		if [ -n "$json" ] ; then
			local use_color=0 limit=0
			isatty && use_color=1
			# limit only for default listing (no filters)
			[ -z "$OPT_ALL" ] && [ -z "$OPT_STATE" ] && [ -z "$OPT_REPO" ] && [ -z "$OPT_USER" ] && isatty && limit=20
			echo "$json" | _format_task_list "$use_color" "$limit"
			[ "$limit" -gt 0 ] 2>/dev/null && isatty && echo "(end of head -n$limit output)"
			return
		fi
	fi

	# fallback to SSH/girar-show
	_do_task_list_ssh "$OPT_ALL" "$OPT_USER" "$OPT_STATE"
}

# SSH fallback for do_task_list
_do_task_list_ssh()
{
	local OPT_ALL="$1" OPT_USER="$2" OPT_STATE="$3"
	if [ -n "$OPT_ALL" ] ; then
		docmd ssh $GEARHOST task ls --user=ALL --state=ALL
		return
	fi
	if [ -n "$OPT_USER" ] ; then
		docmd ssh $GEARHOST task ls --user=$OPT_USER
		return
	fi
	# default: own tasks via girar-show
	if ! isatty ; then
		showcmd "$GEARHOST>" girar-show
		GIT_ALT=$GEARHOST girar-show | stripcolors
	else
		showcmd "$GEARHOST>" 'girar-show | head -n20'
		GIT_ALT=$GEARHOST girar-show | head -n20
		echo "(end of head -n20 output)"
	fi
}

do_task_show()
{
	local TASK="$1"
	local VERBOSE="$2"
	local api_base json
	if api_base=$(_get_api_base) ; then
		json=$(curl -sf "$api_base/tasks/$TASK" 2>/dev/null)
		if [ -n "$json" ] ; then
			if [ -n "$VERBOSE" ] ; then
				echo "$json" | python3 -m json.tool
			else
				echo "$json" | _format_task_subtasks
			fi
			return
		fi
	fi
	# fallback
	if [ -n "$VERBOSE" ] ; then
		docmd ssh $GEARHOST task show "$TASK"
	else
		showcmd "$GEARHOST>" girar-show "${TASK}@"
		GIT_ALT=$GEARHOST girar-show "${TASK}@"
	fi
}

do_task_log()
{
	local TASK="$1"
	showcmd "$GEARHOST>" girar-show "$TASK"
	GIT_ALT=$GEARHOST girar-show "$TASK"
}

NEXTCOMMAND=''

$EPMCMD assure girar-show girar-utils

if [ "$1" = "get" ] ; then
	if [ "$1 $2" = "get subtask" ] ; then
		[ -n "$3" ] || fatal "get subtask TASK PROJECTNAME"
		[ -n "$4" ] || fatal "get subtask $3 PROJECTNAME"
		get_subtask "$(get_task_number $3)" "$4"
		exit
	elif [ "$1 $2 $3" = "get last " ] || [ "$1 $2 $3" = "get last task" ] ; then
		get_last
		exit
	else
		fatal "Unknown command $1 $2"
	fi
fi

eval lastarg=\${$#}

if [ "$1" = "find" ] ; then
	shift
	showcmd ssh $GITHOST find-package "$@"
	ssh $GITHOST find-package "$@" | _list_git_package
	exit
fi

set_if_matched()
{
    local i
    local m="$1"
    shift
    for i in $* ; do
        echo "$m" | grep "^$i$" && return
    done
    echo "Sisyphus"
    return 1
}

# acl [p9] package [add|del user]
# CLI: package first, then command
# API: acl BRANCH COMMAND PACKAGE [USER]
if [ "$1" = "acl" ] ; then
	if [ "$lastarg" = "--help" ] ; then
		docmd ssh $GEARHOST "$@"
		exit
	fi
	shift
	if [ "$1" = "-h" ] || [ -z "$1" ] ; then
		echo "Usage: gita acl [BRANCH] PACKAGE [COMMAND [USER]]"
		echo "  BRANCH: Sisyphus (default), p10, p11, c10f2, etc."
		echo "  COMMAND: show (default), add, del, leader"
		echo "Examples:"
		echo "  gita acl PACKAGE [show]       - show ACL for Sisyphus"
		echo "  gita acl p10 PACKAGE [show]   - show ACL for p10"
		echo "  gita acl PACKAGE add USER     - add user to ACL"
		echo "  gita acl PACKAGE del USER     - remove user from ACL"
		echo "  gita acl PACKAGE leader USER  - set package leader"
		exit
	fi
	BINARYREPO=$(set_if_matched $1 "Sisyphus [ptc][0-9] [ptc][0-9][0-9] [ptc][0-9][0-9]f[0-9]") && shift
	PROJECTNAME="$1"
	COMMAND="$2"
	[ -z "$COMMAND" ] && COMMAND="show"
	OPERAND="$3"
	# API: acl REPOSITORY PACKAGE COMMAND [USER]
	echo "$GEARHOST:$BINARYREPO ACL for $PROJECTNAME:"
	if [ "$COMMAND" = "show" ] ; then
		showcmd ssh $GEARHOST acl $BINARYREPO $PROJECTNAME show
		ssh $GEARHOST acl $BINARYREPO $PROJECTNAME show | sed -e "s|^$PROJECTNAME||"
	else
		showcmd ssh $GEARHOST acl $BINARYREPO $PROJECTNAME $COMMAND $OPERAND
		ssh $GEARHOST acl $BINARYREPO $PROJECTNAME $COMMAND $OPERAND
	fi
	exit
fi

if [ "$1" = "rebuild" ] ; then
	shift
	showcmd "$GEARHOST>" build rebuild "$@"
	docmd ssh $GEARHOST build rebuild "$@"
	exit
fi

if [ "$1" = "log" ] ; then
	shift
	TASK="$(get_task_number $1)"
	[ -n "$TASK" ] || TASK="$(get_last)" || fatal
	do_task_log "$TASK"
	exit
fi

if [ "$1" = "quota" ] ; then
	shift
	docmd ssh $GIRARHOST quota "$@"
	docmd ssh $GEARHOST quota "$@"
	exit
fi

if [ "$1 $2" = "cancel --help" ] ; then
	echo "cancel a task"
	echo "Usage: $ gita cancel <task id>"
	exit
fi

if [ "$1 $2" = "wait --help" ] || [ "$1 $2" = "wait -h" ] ; then
	echo "wait - wait for task build to complete"
	echo "Usage: gita wait [NNNN] [-q|--quiet]"
	echo "  NNNN     - task number (default: last task)"
	echo "  -q       - quiet mode, no spinner"
	exit 0
fi

# ls before generic --help handler (ls has its own --help)
if [ "$1" = "ls" ] ; then
	shift
	do_task_list "$@"
	exit
fi

if [ "$lastarg" = "--help" ] ; then
	docmd ssh $GEARHOST task "$@"
	exit
fi

# task command below

if [ "$1" = "delsub" ] ; then
	shift 
	TASK="$(get_task_number $1)"
	[ -n "$TASK" ] || TASK="$(get_last)" || fatal
	shift
	while [ -n "$1" ] ; do
		SUBTASK="$1"
		shift
		if isnumber $SUBTASK ; then
			NUMTASK="$SUBTASK"
		else
			NUMTASK="$(get_subtask $TASK $SUBTASK)"
			info "$SUBTASK -> $NUMTASK"
		fi
		[ -z "$NUMTASK" ] && warning "can't find subtask for $SUBTASK, skipped" && continue
		docmd ssh $GEARHOST task delsub "$TASK" "$NUMTASK"
	done
	exit
fi

if [ "$1" = "copy" ] ; then
	shift
	PACKAGELIST=''
	while [ -n "$1" ] ; do
		[ "$1" = "to" ] && break
		PACKAGELIST="$PACKAGELIST $1"
		shift
	done

	[ "$1" = "to" ] || fatal "missed 'to' in your command"
	shift # to
	TARGET="$1"
	shift

	FROMSTR=''
	[ "$1" = "from" ] && FROMSTR="$2"

	[ -n "$PACKAGELIST" ] || fatal "no packages"
	showcmd ssh $GEARHOST task new $TARGET
	# Note: | head hides status
	TASK="$(ssh $GEARHOST task new $TARGET | head -n1)"
	[ -n "$TASK" ] || fatal "Empty task"
	for PACKAGE in $PACKAGELIST ; do
		docmd ssh $GEARHOST task add $TASK copy $PACKAGE $FROMSTR || fatal
	done
	docmd ssh $GEARHOST task run --test-only $TASK
	exit
fi

if [ "$1" = "add" ] || [ "$1" = "Add" ] ; then
	[ "$1" = "Add" ] && NEXTCOMMAND="run"
	SUBTASK=''
	ADDCMD=''
	ADDCMDLIST="del copy repo build rebuild"
	# add TASKNUMBER package
	if is_task_number "$2" && estrlist has "$3" $ADDCMDLIST ; then
		TASK="$(get_task_number $2)"
		ADDCMD="$3"
		shift 3
	elif is_task_number "$2" && [ "$3" = "before" ] && isnumber "$4" && estrlist has "$5" $ADDCMDLIST ; then
		TASK="$(get_task_number $2)"
		SUBTASK="$4"
		ADDCMD="$5"
		shift 5
	elif is_task_number "$2" && isnumber "$3" && estrlist has "$4" $ADDCMDLIST ; then
		TASK="$(get_task_number $2)"
		SUBTASK="$3"
		ADDCMD="$4"
		shift 4
	elif estrlist has "$2" $ADDCMDLIST ; then
		TASK="$(get_last)"
		ADDCMD="$2"
		shift 2
	else
		fatal "don't support $1 $2 command"
	fi

	[ -n "$1" ] || fatal "no packages"

	[ "$ADDCMD" = "build" ] && ADDCMD="repo"

	OADDCMD=$ADDCMD

	[ "$ADDCMD" = "copy" ] && FROMSTR="$(get_last_from "$@")"

	while [ -n "$1" ] ; do
		[ "$1" = "from" ] && [ "$ADDCMD" = "copy" ] && break
		PACKAGE="$1"
		if [ "$OADDCMD" = "repo" ] ; then
			#<gear repo>.git=<gear tag>
			if rhas "$1" "\.git=" ; then
				ADDCMD=''
			else
				PACKAGE="$1 $2" && shift
			fi
		fi
		shift
		ST="$(get_subtask $TASK $PACKAGE)"
		if [ -n "$ST" ] ; then
			info "$PACKAGE already present in the task $TASK as subtask $ST, replacing ..."
			docmd ssh $GEARHOST task delsub $TASK $ST
			[ -n "$SUBTASK" ] || SUBTASK="$ST"
			# too much re add to the same place
			[ "$SUBTASK" = "1" ] && SUBTASK=""
		fi
		docmd ssh $GEARHOST task add $TASK $SUBTASK $ADDCMD $PACKAGE $FROMSTR || fatal
	done
	if [ -n "$NEXTCOMMAND" ] ; then
		sleep 2
		$0 $NEXTCOMMAND
		exit
	fi
	exit 0
fi

if [ "$1" = "show" ] ; then
	shift
	VERBOSE=''
	if [ "$1" = "-v" ] || [ "$1" = "--verbose" ] ; then
		VERBOSE=1
		shift
	fi
	TASK="$(get_task_number "$1")"
	if [ -z "$TASK" ] ; then
		TASK="$(get_last)" || fatal "Can't get last task"
	fi
	do_task_show "$TASK" "$VERBOSE"
	exit
fi

# run --commit, run --test-only, commit, test
if [ "$1" = "run" ] || [ "$1" = "commit" ] || [ "$1" = "test" ] ; then
	VERB="$1"
	COMMAND='--test-only'
	[ "$VERB" = "commit" ] && COMMAND="--commit"
	shift
	[ "$1" = "--test-only" ] && shift
	[ "$VERB" = "run" ] && [ "$1" = "--commit" ] && COMMAND="--commit" && shift

	TASKLIST=''
	MESSAGE=''
	MESSAGETEXT=''

	while [ -n "$1" ] ; do
		# TODO: check for number?
		if echo "$1" | grep -qv "^-" ; then
			TASK="$(get_task_number $1)"
			TASKLIST="$TASKLIST $TASK"
		elif [ "$1" = "-m" ] ; then
			#MESSAGE="$1 ${2// /_}"
			MESSAGE="-m -"
			MESSAGETEXT="$2"
			shift
		else
			fatal "Unknown param $1"
		fi
		shift
	done

	[ -n "$TASKLIST" ] || TASKLIST="$(get_last)" || fatal "Can't get last task"

	for TASK in $TASKLIST ; do
		echo "$MESSAGETEXT" | docmd ssh $GEARHOST task run $COMMAND $MESSAGE "$TASK"
	done
	exit
fi

if [ "$1" = "wait" ] ; then
	shift
	QUIET=''
	TASK=''
	while [ -n "$1" ] ; do
		if [ "$1" = "-q" ] || [ "$1" = "--quiet" ] ; then
			QUIET=1
		elif is_task_number "$1" ; then
			TASK="$(get_task_number $1)"
		else
			fatal "Unknown param $1"
		fi
		shift
	done
	[ -n "$TASK" ] || TASK="$(get_last)" || fatal "Can't get last task"
	SPINNER='|/-\'
	SPINPOS=0
	INTERVAL=10
	info "Waiting for task #$TASK to complete..."
	while true ; do
		STATUS="$(get_task_status $TASK)"
		[ -z "$STATUS" ] && info "Task #$TASK not found or already done" && exit 0
		case "$STATUS" in
			TESTED|FAILED|EPERM|DONE)
				# clear spinner
				[ -z "$QUIET" ] && isatty && printf "\r\033[K"
				info "Task #$TASK: $STATUS"
				[ "$STATUS" = "TESTED" ] || [ "$STATUS" = "DONE" ]
				exit $?
				;;
		esac
		if [ -z "$QUIET" ] && isatty ; then
			SPINCHAR="${SPINNER:$SPINPOS:1}"
			printf "\r[%s] #%s %s " "$SPINCHAR" "$TASK" "$STATUS"
			SPINPOS=$(( (SPINPOS + 1) % 4 ))
		fi
		sleep $INTERVAL
	done
	exit
fi

if [ "$1" = "cancel" ] ; then
	shift
	TASK="$(get_task_number $1)"
	[ -n "$TASK" ] || fatal "No task number"
	if [ "$GEARHOST" = "gyle.eter" ]  || [ "$GEARHOST" = "git.office" ] ; then
		docmd ssh $GEARHOST task cancel "$TASK"
	else
		docmd ssh $GEARHOST task abort "$TASK"
	fi
	exit
fi

if [ "$1" = "approve" ] ; then
	shift
	TASK=''
	SUBTASK=''
	MESSAGETEXT=''

	while [ -n "$1" ] ; do
		if [ "$1" = "-m" ] ; then
			MESSAGETEXT="$2"
			shift
		elif echo "$1" | grep -qv "^-" ; then
			if [ -z "$TASK" ] ; then
				TASK="$(get_task_number $1)"
			else
				SUBTASK="$1"
			fi
		else
			fatal "Unknown param $1"
		fi
		shift
	done

	[ -n "$TASK" ] || TASK="$(get_last)" || fatal "Can't get last task"

	if [ -n "$SUBTASK" ] ; then
		SUBTASKLIST="$SUBTASK"
	else
		SUBTASKLIST="$(get_all_subtasks "$TASK")"
		[ -n "$SUBTASKLIST" ] || fatal "No subtasks found in task $TASK"
	fi

	for ST in $SUBTASKLIST ; do
		showcmd ssh $GEARHOST task approve "$TASK" "$ST"
		if [ -n "$MESSAGETEXT" ] ; then
			echo "$MESSAGETEXT" | ssh $GEARHOST task approve "$TASK" "$ST"
		else
			ssh $GEARHOST task approve "$TASK" "$ST"
		fi
	done
	exit
fi

if [ "$1" = "share" ] && [ -n "$2" ] ; then
	shift
	TASK="$(get_task_number $1)"
	docmd ssh $GEARHOST task share "$TASK" enable
	exit
fi


if [ "$1" = "task" ] ; then
	# by default
	shift
fi

docmd ssh $GEARHOST task "$@"
