#!/bin/bash
#
# Copyright (C) 2018 Jolla Ltd.
# Contact: Ville Nummela <ville.nummela@jolla.com>
#
# This file is part of the SailfishOS SDK
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

set -o nounset

usage() {
    cat <<EOF
Run a basic quality criteria check for the given RPM file.

Usage: 
    $0 -t <target> [OPTIONS] <rpm-file>
    $0 [-t <target>] --list-suites
    
Options:
    -t|--target <target> The OS version to validate for. 
                         Accepts the same argument as 'sb2 -t' does.
    -l|--list-suites     Lists available suites
    --suites <suite1,suite2,...> Use specified suites.
                         If omitted, all suites are used.
EOF
}

CONFIG_DIRS=({/usr/share,/etc}/rpmvalidation/suites)

SUITES=()

US=$'\x1F'
write() ( IFS=$US; printf '%s\n' "$*"; )

# Write an error message in a format that Qt Creator understands.
# See merrpmvalidationparser.cpp for details
error() {
    local section=$1
    local filename=$2
    shift 2
    echo "$section"
    echo "${section//?/=}"
    echo "ERROR [$filename] $*"
    echo "FAILED"
    exit 1
}

lacks_element() {
    local match=$1
    shift
    for element in "$@"; do
        if [[ $match == "$element" ]]; then
            return 1
        fi
    done
    return 0
}

populate_suites() {
    local target=$1
    local suite_inis=()
    local unique_inis=()
    SUITES=()
    local query=$(printf 'cd %q 2>/dev/null \
            && find -maxdepth 1 -name "*.ini" -type f -print\n' "${CONFIG_DIRS[@]}")
    suite_inis=($(sb2 -t "$target" sh -c "$query" | sed 's,^.*/,,'))
    unique_inis=($(printf '%s\n' ${suite_inis[@]:+"${suite_inis[@]}"} | sort -u))
    SUITES=("${unique_inis[@]%.ini}")
    if [[ ${OPT_SUITES:-} ]]; then
        for suite in "${OPT_SUITES[@]}"; do
            if lacks_element $suite ${SUITES[@]:+"${SUITES[@]}"}; then
                error Target "$target" "Target does not contain suite $suite"
            fi
        done
        SUITES=("${OPT_SUITES[@]}")
    else
        local essential_suites=()
        for suite in ${SUITES[@]:+"${SUITES[@]}"}; do
            local essential=$(suite_boolean "$target" "$suite" Essential)
            if [[ $OPT_LIST ]] || [[ $essential == True ]]; then
                essential_suites+=("$suite")
            fi
        done
        SUITES=(${essential_suites[@]:+"${essential_suites[@]}"})
    fi
}

suite_value() {
    local target=$1
    local suite=$2
    local key=$3
    local type=$4
    local ini_files=("${CONFIG_DIRS[@]/%//$suite.ini}")
    local python=
    read -d '' -r python <<'EOF'
import sys, configparser;
config = configparser.RawConfigParser();
config.read(sys.argv[3:]);
sec, val = sys.argv[1].split(".");
type = sys.argv[2];
if type == 'boolean':
    print(config.getboolean(sec, val));
else:
    print(config[sec][val]);
EOF
    sb2 -t "$OPT_TARGET" python3 -c "$python" "RpmValidationSuite.$key" "$type" "${ini_files[@]}"
}

suite_boolean() {
    suite_value "$1" "$2" "$3" boolean
}

suite_raw() {
    suite_value "$1" "$2" "$3" raw
}
 
list_target() {
    local target=$1
    local display_target=${2:-}
    local suite=
    for suite in ${SUITES[@]:+"${SUITES[@]}"}; do
        local essential=$(suite_boolean "$target" "$suite" Essential)
        if [[ $essential == True ]]; then
            essential="Essential"
        else
            essential="Optional"
        fi
        local website=$(suite_raw "$target" "$suite" Website)
        local name=$(suite_raw "$target" "$suite" Name)
        write ${display_target:+"$target"} "$suite" "$essential" "$website" "$name"
    done
}

list_all() {
    local t=
    for t in $(sdk-manage target list); do
        populate_suites $t
        list_target $t 1
    done
}

run_suite() {
    local suite=$1
    suite_exec=$(suite_raw "$OPT_TARGET" "$suite" Exec)
    suite_cmd=${suite_exec//%f/$OPT_RPM_FILE}

    sb2 -t "$OPT_TARGET" "$suite_cmd"
}

run_suites() {
    echo "Using validation suites: ${SUITES[@]}"
    local suite=
    for suite in ${SUITES[@]:+"${SUITES[@]}"}; do
        run_suite "$suite"
    done
}

set_defaults()
{
    OPT_TARGET=
    OPT_RPM_FILE=
    OPT_USAGE=
    OPT_LIST=
    OPT_SUITES=()
}

parse_options() {
    if [[ $0 == *.sh ]]; then
        echo "Warning: The name '$0' is deprecated. Use '${0%.sh}' instead" >&2
    fi

    while [[ $# -gt 0 ]]; do
        case $1 in
            -h | --help)
                OPT_USAGE=1
                return
                ;;
            -t | --target)
                shift
                OPT_TARGET=$1
                ;;
            -l | --list-suites)
                OPT_LIST=1
                ;;
            --suites )
                shift
                IFS=, read -a OPT_SUITES <<<"$1"
                ;;
            *)
                [[ $OPT_RPM_FILE ]] && error "RPM File" "$1" "Only one RPM file can be specified"
                OPT_RPM_FILE=$1
                ;;
        esac
        shift
    done

    if [[ ! $OPT_LIST ]]; then
        if [[ ! $OPT_RPM_FILE ]]; then
            OPT_USAGE=1
        fi
    fi
}

main() {
    set_defaults

    parse_options "$@" || return

    if [[ $OPT_USAGE ]]; then
        usage
        return
    fi

    if [[ $OPT_TARGET ]]; then
        populate_suites "$OPT_TARGET"
        if [[ "${#SUITES[@]}" -lt 1 && ! $OPT_LIST ]]; then
            error Target "$OPT_TARGET" "Target does not contain validation suites. Use a more recent target."
        fi
    fi

    if [[ $OPT_LIST ]]; then
        if [[ $OPT_TARGET ]]; then
            list_target "$OPT_TARGET" | column -t --separator "$US"
        else
            list_all | column -t --separator "$US"
        fi
        return
    fi

    if [[ ! $OPT_TARGET ]]; then
        error Target "" "Target not specified"
    fi

    echo "Validating for target $OPT_TARGET"
    run_suites
}

main "$@"
