#! /bin/bash
# vim: shiftwidth=4 tabstop=4 expandtab

# shellcheck source=/dev/null
source "$LIB_DIR"/stats

subcommand__help() {
    if [[ -z "$2" ]] || [[ "$2" == "info" ]]; then
        echo "$COMMAND stats HYP [status] [--json]"
        if [[ "$1" == "details" ]] && [[ "$2" == "info" ]]; then
            echo "  Show hypervisor stats"
            echo "    HYP              Hypervisor name (optional, default: local one)"
            echo "    status           Filter on VM status (optional)"
            echo "                     Possible values:"
            echo "                     $( implode ", " "${HYP_STATS_STATUS[@]}" )"
            echo "    --json           JSON output"
        fi
    fi
    if [[ -z "$2" ]] || [[ "$2" == "candidates" ]]; then
        echo "$COMMAND candidates vCPU RAM [--best]"
        if [[ "$1" == "details" ]] && [[ "$2" == "candidates" ]]; then
            echo "  Find the candidate hypervisor(s) to deploy a new VM"
            echo "    [vCPU]             VM allocated vCPU (optional)"
            echo "    [RAM]              VM allocated memory (in Mo, optional)"
            echo "    --best             Only show the best candidate hypervisor"
            echo "  Note: use -F/--force to force identify a candidate even if no hypervisor"
            echo "  could normally host this VM."
        fi
    fi
}

subcommand__stats() {
    local hyp status status_arg json=0 stats
    for opt in "${@:1}"; do
        case $opt in
            --json)
                json=1
                ;;
            *)
                if in_array "$opt" "${HYP_STATS_STATUS[@]}"; then
                    status=$opt
                elif [[ -z "$hyp" ]]; then
                    hyp=$opt
                else
                    usage "Unknown parameter '$opt'"
                fi
                ;;
        esac
    done
    [[ -z "$hyp" ]] && hyp=$(hostname -s)
    check_hyp "$hyp" || usage "Invalid hypervisor '$hyp'"

    [[ "${status:-active}" != "all" ]] && status_arg="--list-${status:-active}"

    stats=$( get_hyp_stats "$hyp" "${status_arg:-}" )
    if [[ $json -eq 1 ]]; then
        jq <<< "$stats"
        exit 0
    fi

    jq -r "$JQ_FORMAT_SIZE"'
        "Physical CPU core: \(.cpu)\n"
        + "vCPUs: "
            + "reserved=\(.vcpu_reserved) / "
            + "allocatable=\(.vcpu_allocatable) / "
            + "allocated=\(.vcpu_allocated) (\(.vcpu_allocated_percent)%) / "
            + "max allocated=\(.vcpu_max_allocated) (\(.vcpu_max_allocated_percent)%) / "
            + "available=\(.vcpu_available) (\(.vcpu_available_percent)%)\n"
        + "Physical memory: \(.memory | format_size) / "
            + "reserved=\(.memory_reserved | format_size) / "
            + "used=\(.memory_used | format_size) (\(.memory_used_percent)%) / "
            + "cached=\(.memory_cached | format_size) (\(.memory_cached_percent)%) / "
            + "buffers=\(.memory_buffers | format_size) / "
            + "free=\(.memory_free | format_size) (\(.memory_free_percent)%)\n"
        + "SWAP : \(.swap | format_size) / "
            + "used=\(.swap_used | format_size) (\(.swap_used_percent)%) / "
            + "cached=\(.swap_cached | format_size) (\(.swap_cached_percent)%) / "
            + "free=\(.swap_free | format_size) (\(.swap_free_percent)%)\n"
        + "Total memory (RAM+SWAP): \(.total_memory | format_size) / "
            + "used=\(.total_memory_used | format_size) (\(.total_memory_used_percent)%) / "
            + "cached=\(.total_memory_cached | format_size) (\(.total_memory_cached_percent)%) / "
            + "free=\(.total_memory_free | format_size) (\(.total_memory_free_percent)%)\n"
        + "VMs memory: "
            + "allocatable=\(.memory_allocatable | format_size) / "
            + "allocated=\(.memory_allocated | format_size) (\(.memory_allocated_percent)%) / "
            + "max allocated=\(.memory_max_allocated | format_size) (\(.memory_max_allocated_percent)%) / "
            + "used=\(.memory_allocated_used | format_size) (\(.memory_allocated_used_percent)%) / "
            + "available=\(.memory_available | format_size) (\(.memory_available_percent)%)\n"
    ' <<< "$stats"

    if [[ "$( jq -r '.vms | length ' <<< "$stats" )" -eq 0 ]]; then
        [[ "$status" != "all" ]] && echo "No $status VMs."
        [[ "$status" == "all" ]] && echo "No VMs."
        exit 0
    fi

    jq -r "$JQ_FORMAT_SIZE"'
        . as $root |
        .vms |
        to_entries |
        sort_by(.value.status, .value.vcpu.current, .value.balloon.current, .value.vcpu.maximum, .value.balloon.maximum) |
        (
            ["VM", "Status", "Current vCPUs", "Max vCPUs", "Current memory", "Used memory", "Max memory"],
            ["--", "------", "-------------", "---------", "--------------", "-----------", "----------"],
            (
                .[] |
                [
                    .key,
                    .value.status,
                    .value.vcpu.current,
                    .value.vcpu.maximum,
                    (.value.balloon.current | format_size),
                    (.value.balloon.used | format_size),
                    (.value.balloon.maximum | format_size)
                ]
            ),
            ["------"],
            [
                "",
                "Total:",
                $root.vcpu_allocated,
                $root.vcpu_max_allocated,
                ($root.memory_allocated | format_size),
                ($root.memory_allocated_used | format_size),
                ($root.memory_max_allocated | format_size)
            ]
        ) |
        @tsv
    ' <<< "$stats" | column -t -s $'\t'
}

subcommand__candidates() {
    check_generate_script_not_supported
    local vcpu memory best=0
    for opt in "${@:1}"; do
        case $opt in
            --best)
                best=1
                ;;
            *)
                if [[ "${vcpu:-null}" == "null" ]]; then
                    check_int "$opt" 1 || usage "Invalid vCPUs value." \
                        "Must be a positive integer."
                    vcpu=$opt
                elif [[ "${memory:-null}" == "null" ]]; then
                    check_int "$opt" 1 || usage "Invalid memory value." \
                        "Must be a positive integer."
                    (( memory=$opt*1024*1024 ))
                else
                    usage "Unknown parameter '$opt'"
                fi
                ;;
        esac
    done
    [[ "${vcpu:-null}" == "null" ]] && usage "You must provide VM vCPUs & memory"
    [[ "${memory:-null}" == "null" ]] && usage "You must provide VM memory as second parameter"
    log INFO "Looking for a hypervisor to host a VM with $vcpu vCPU(s) and $(format_size $memory) of memory"
    local candidates='[]'
    local hyp stats vcpu_allocatable memory_allocatable vcpu_available memory_available \
        available_ratio
    for hyp in $HYPS; do
        stats=$(get_hyp_stats "$hyp" --list-active)
        vcpu_available=$(jq -r .vcpu_available <<< "$stats" )
        if [[ -n "$vcpu" ]] && [[ $vcpu_available -lt $vcpu ]]; then
            debug "$hyp: no enough available vCPUs ($vcpu_available < $vcpu)"
            [[ $FORCE -eq 0 ]] && continue
            debug "Force mode: keep $hyp as candidate"
        fi
        memory_available=$(jq -r .memory_available <<< "$stats" )
        if [[ -n "$memory" ]] && [[ $memory_available -lt $memory ]]; then
            debug "$hyp: no enough available memory ($memory_available < $memory)"
            [[ $FORCE -eq 0 ]] && continue
            debug "Force mode: keep $hyp as candidate"
        fi
        available_ratio=$(
            jq '(
                (.vcpu_available * 100 / .vcpu_allocatable)
                + (.memory_available * 100 / .memory_allocatable)
            ) * 10 | round / 10' <<< "$stats"
        )
        log INFO "$hyp: could host it ($vcpu_available vCPUs / $(format_size $memory_available) " \
            "memory available, ratio = $available_ratio)"
        candidates=$(jq ". += [$(jo name="$hyp" available_ratio=$available_ratio)]" <<< "$candidates")
    done
    candidates_names=$(jq -r "sort_by(.available_ratio) | reverse | .[] | .name" <<< "$candidates")
    if [[ -z "$candidates_names" ]]; then
        echo "No candidate hypervisor found"
        exit 1
    fi
    if [[ $best -eq 1 ]]; then
        echo -e "$candidates_names"|head -n1
    else
        echo -e "$candidates_names"
    fi
}

run_subcommand "${COMMAND_ARGS[@]}"
