#!/bin/bash

show_help() {
    echo "usage: log-analyzer list-failed-tasks <EXEC-PARAM> <PARSED-LOG>"
    echo "       log-analyzer list-executed-tasks <EXEC-PARAM> <PARSED-LOG>"
    echo "       log-analyzer list-successful-tasks <EXEC-PARAM> <PARSED-LOG>"
    echo "       log-analyzer list-aborted-tasks <EXEC-PARAM> <PARSED-LOG>"
    echo "       log-analyzer list-all-tasks <EXEC-PARAM>"
    echo "       log-analyzer list-reexecute-tasks <EXEC-PARAM> <PARSED-LOG>"
    echo ""
    echo "The log analyzer is an utility wchi provides useful information about a spread"
    echo "execution. The main functionality of the analyzer utility is to determine which tests"
    echo "have to be re-executed, being able to include the tests that have been aborted, even"
    echo "when those tests are not included in the test results."
    echo "The log analyzer uses as input the spread expression that was used to run the tests,"
    echo "this expression determines which are all the tests to be considered. The second input"
    echo "is the output of the log-parser utility, which generates a json file including all the"
    echo "information extracted from the raw spread log"
    echo ""
    echo "Available options:"
    echo "  -h --help   show this help message."
    echo ""
    echo "COMMANDS:"
    echo "  list-failed-tasks        list the tasks that failed during execute"
    echo "  list-executed-tasks      list the tasks that were executed"
    echo "  list-successful-tasks    list the successful tasks"
    echo "  list-aborted-tasks       list the aborted tasks (needs spread to be installed)"
    echo "  list-all-tasks           list all the tasks"
    echo "  list-reexecute-tasks     list the tasks to re-execute to complete (includes aborted and failed tasks)"
    echo ""
    echo "PARSED-LOG: This is the output generated by the log-parser tool"
    echo "EXEC-PARAM: this is the parameter used to run spread (something like this BACKEND:SYSTEM:SUITE)"
    echo ""
}

_check_log() {
    local log="$1"

    if [ -z "$log" ]; then
        echo "log.analyzer: the log file cannot be empty"
        exit 1
    elif [ ! -f "$log" ]; then
        echo "log.analyzer: the log file $log does not exist"
        exit 1
    fi
}

_list_failed() {
    local level="$1"
    local stage="$2"
    local log="$3"

    if [ -z "$level" ]; then
        echo "log.analyzer: the first parameter cannot be empty"
        exit 1
    elif [ ! "$level" = 'task' ] && [ ! "$level" = 'suite' ] && [ ! "$level" = 'project' ]; then
        echo "log.analyzer: the first parameter has to be: task, suite or project"
        exit 1
    fi

    if [ -z "$stage" ]; then
        echo "log.analyzer: the second parameter cannot be empty"
        exit 1
    elif [ ! "$stage" = 'prepare' ] && [ ! "$stage" = 'restore' ]; then
        echo "log.analyzer: the second parameter has to be: prepare or restore"
        exit 1
    fi
    _check_log "$log"

    jq -r ".[] | select( .type == \"result\") | select( .result_type == \"Failed\")  | select(.level == \"$level\") | select(.stage == \"$stage\")  | .detail.lines[]" "$log" | cut -d '-' -f2- | xargs
}

_merge_tasks_lists() {
    # Returns the list1 + the tasks in list2 which are not included in list1
    local list1="$1"
    local list2="$2"
    local merged_list="$1"
    local list1_file

    list1_file=$(mktemp list1.XXXXXX)
    for elem in $list1; do
        echo "$elem" >> "$list1_file"
    done

    # shellcheck disable=SC2086
    for elem2 in $list2; do
        if ! grep -Fxq "$elem2" "$list1_file"; then
            merged_list="$merged_list $elem2"
        fi
    done
    rm "$list1_file"
    echo "$merged_list"
}

_diff_tasks_lists() {
    # Returns the list1 - the tasks in list2
    local list1="$1"
    local list2="$2"
    local diff_list list2_file

    diff_list=""
    list2_file=$(mktemp list2.XXXXXX)
    for elem in $list2; do
        echo "$elem" >> "$list2_file"
    done

    # shellcheck disable=SC2086
    for elem1 in $list1; do
        if ! grep -Fxq "$elem1" "$list2_file"; then
            diff_list="$diff_list $elem1"
        fi
    done 
    rm "$list2_file"
    echo "$diff_list"
}

_intersection_tasks_lists() {
    # Returns the tasks in list1 which are also in the list2
    local list1="$1"
    local list2="$2"
    local both_list list2_file

    both_list=""
    list2_file=$(mktemp list2.XXXXXX)
    for elem in $list2; do
        echo "$elem" >> "$list2_file"
    done
    for elem in $list1; do
        # -F tells grep to look for fixed strings, not regexps
        if grep -Fxq "$elem" "$list2_file"; then
            both_list="$both_list $elem"
        fi
    done
    rm "$list2_file"
    echo "$both_list"
}

list_all_tasks() {
    local exec_exp="$1"
    exec_exp="$(echo "$exec_exp" | tr ',' ' ')"
    if ! command -v spread >/dev/null; then
        echo "log.analyzer: spread tool is not installed, exiting..."
        exit 1
    fi

    # shellcheck disable=SC2086
    spread -list $exec_exp
}

_list_executed_and_failed_tasks() {
    local exec_exp="$1"
    local log="$2"

    if ! command -v spread >/dev/null; then
        echo "log.analyzer: spread tool is not installed, exiting..."
        exit 1
    fi
    _check_log "$log"

    local failed_tasks failed_tasks_restore failed_tasks_prepare exec_and_failed_tasks
    failed_tasks="$(list_failed_tasks "$exec_exp" "$log")"
    failed_tasks_prepare="$(_list_failed task prepare "$log")"
    failed_tasks_restore="$(_list_failed task restore "$log")"

    exec_and_failed_tasks="$(_merge_tasks_lists "$failed_tasks" "$failed_tasks_restore")"
    _diff_tasks_lists "$exec_and_failed_tasks" "$failed_tasks_prepare"
}

list_failed_tasks() {
    local exec_exp="$1"
    local log="$2"

    if [ -z "$exec_exp" ]; then
        echo "log.analyzer: execution expression for spread cannot be empty"
        exit 1
    fi
    exec_exp="$(echo "$exec_exp" | tr ',' ' ')"
    _check_log "$log"

    local all_tasks failed_tasks
    all_tasks="$(list_all_tasks "$exec_exp")"
    failed_tasks="$(jq -r '.[] | select( .type == "result") | select( .result_type == "Failed")  | select(.level == "tasks") | .detail.lines[]' "$log" | cut -d '-' -f2- | xargs)"
    _intersection_tasks_lists "$failed_tasks" "$all_tasks"
}

list_reexecute_tasks() {
    local exec_exp="$1"
    local log="$2"

    if [ -z "$exec_exp" ]; then
        echo "log.analyzer: execution expression for spread cannot be empty"
        exit 1
    fi
    exec_exp="$(echo "$exec_exp" | tr ',' ' ')"
    _check_log "$log"

    local aborted_tasks exec_and_failed_tasks all_tasks reexec_tasks
    aborted_tasks="$(list_aborted_tasks "$exec_exp" "$log")"
    all_tasks="$(list_all_tasks "$exec_exp")"
    exec_and_failed_tasks="$(_list_executed_and_failed_tasks "$exec_exp" "$log")"

    # Remove the tasks which are not in the filter from the executed and failed
    exec_and_failed_tasks="$(_intersection_tasks_lists "$exec_and_failed_tasks" "$all_tasks")"
    reexec_tasks="$(_merge_tasks_lists "$aborted_tasks" "$exec_and_failed_tasks")"

    # In case all the tests are failed or aborted, then the execution expression is used to reexecute
    if [ "$(echo "$reexec_tasks" | wc -w)" = "$(echo "$all_tasks" | wc -w)" ]; then
        echo "$exec_exp"
        return 
    fi

    # When all the tests were successful, then no tests need to be reexecuted
    if [ "$(echo "$reexec_tasks" | wc -w)" = 0 ]; then
        return 
    fi
    echo "$reexec_tasks"
}

list_successful_tasks() {
    local exec_exp="$1"
    local log="$2"

    if [ -z "$exec_exp" ]; then
        echo "log.analyzer: execution expression for spread cannot be empty"
        exit 1
    fi
    exec_exp="$(echo "$exec_exp" | tr ',' ' ')"
    _check_log "$log"

    local all_tasks executed_tasks failed_tasks_restore failed_tasks
    all_tasks="$(list_all_tasks "$exec_exp")"
    executed_tasks="$(list_executed_tasks "$exec_exp" "$log")"
    executed_tasks="$(_intersection_tasks_lists "$executed_tasks" "$all_tasks")"
    failed_tasks="$(list_failed_tasks "$exec_exp" "$log")"
    failed_tasks_restore="$(_list_failed task restore "$log")"

    if [ -n "$failed_tasks_restore" ]; then
        failed_tasks="$(_merge_tasks_lists "$failed_tasks" "$failed_tasks_restore")"
    fi

    if [ -n "$failed_tasks" ]; then
        executed_tasks="$(_diff_tasks_lists "$executed_tasks" "$failed_tasks")"
    fi    
    
    echo "$executed_tasks"
}

list_executed_tasks() {
    local exec_exp="$1"
    local log="$2"

    if [ -z "$exec_exp" ]; then
        echo "log.analyzer: execution expression for spread cannot be empty"
        exit 1
    fi
    exec_exp="$(echo "$exec_exp" | tr ',' ' ')"
    _check_log "$log"

    local all_tasks executed_tasks
    all_tasks="$(list_all_tasks "$exec_exp")"
    executed_tasks="$(jq -r '.[] | select( .type == "action") | select( .verb == "Executing") | .task' "$log")"
    _intersection_tasks_lists "$executed_tasks" "$all_tasks"
}

list_aborted_tasks() {
    local exec_exp="$1"
    local log="$2"

    if [ -z "$exec_exp" ]; then
        echo "log.analyzer: execution expression for spread cannot be empty"
        exit 1
    fi
    exec_exp="$(echo "$exec_exp" | tr ',' ' ')"
    _check_log "$log"

    local all_tasks executed_tasks failed_tasks_prepare failed_tasks_restore failed_tasks
    all_tasks="$(list_all_tasks "$exec_exp")"
    executed_tasks="$(list_executed_tasks "$exec_exp" "$log")"
    failed_tasks="$(list_failed_tasks "$exec_exp" "$log")"
    failed_tasks_prepare="$(_list_failed task prepare "$log")"
    failed_tasks_restore="$(_list_failed task restore "$log")"

    # In case no tasks for the expression, the aborted list is empty
    if [ -z "$all_tasks" ]; then
        return
    fi

    # In case no tasks are successfully executed, all the tasks - the failed ones are the aborted
    if [ -z "$executed_tasks" ]; then
        exec_and_failed_tasks="$(_list_executed_and_failed_tasks "$exec_exp" "$log")"
        _diff_tasks_lists "$all_tasks" "$exec_and_failed_tasks"
        return
    fi
    
    # In other cases the aborted tasks are all the tasks - the executed - the that failed 
    _diff_tasks_lists "$all_tasks" "$executed_tasks"
}

main() {
    if [ $# -eq 0 ]; then
        show_help
        exit 0
    fi

    local subcommand="$1"
    local action=
    while [ $# -gt 0 ]; do
        case "$1" in
            -h|--help)
                show_help
                exit 0
                ;;
            *)
                action=$(echo "$subcommand" | tr '-' '_')
                shift
                break
                ;;
        esac
    done

    if [ -z "$(declare -f "$action")" ]; then
        echo "log.analyzer: no such command: $subcommand" >&2
        show_help
        exit 1
    fi

    "$action" "$@"
}

main "$@"
