#!/bin/bash
# *******************************************************************
# **  @file eisman_repair.sh - repair installation database
# **
# **  Creation:     2016-06-09 dv
# **  Last update:  $Id$
# **
# **  Copyright (c) 2016-2017 the eisfair team, team(at)eisfair(dot)org
# **
# **  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.
# *******************************************************************

. /var/install/include/eislib
. /var/install/include/eismanlib

PACKIDX=1

# *******************************************************************
# **  show help on arguments and possible options
# **  @param  n/a
# **  @return n/a
# *******************************************************************

show_help()
{
cat <<EOF

Repair installation of local packages

usage:
    eisman repair [OPTIONS] [PACKAGE]

options:
    --ignore <package>  ignore package named <package>. May be given
                        multiple times
    --auto              do automatic installation of packages
    --help              show this help screen

EOF
}


# *******************************************************************
# **  show current progress in percent
# **  @param  $1 progress in percent
# **  @return n/a
# *******************************************************************

show_progress()
{
    echo -en "\033[7D"
    printf "[%3d%%] completed!" "${1}"
    echo -en "\033[10D"
}


# *******************************************************************
# **  use grep to perform a query on the outdated.db file.
# **  @param  $1  package
# **  @param  $2  field
# **  @return (on stdout) data found in outdated.db meeting the
# **          search criteria
# *******************************************************************

query_outdated_db()
{
    local package="${1}"
    local field="${2}"
    local header=$((${FIELDHEADER} + 1))

    # perform query
    query_package_db      \
        "outdated"        \
        ""                \
        "${package}"      \
        ""                \
        ""                \
        ""                \
        "${field}"        \
        "${EISMAN_PATH}/outdated.db" |
            cut  -c "${header}-"
}


# *******************************************************************
# **  add a package to the list of missing packages
# **  @param  $1  package name
# **  @param  $2  package version
# **  @return n/a
# *******************************************************************

add_package()
{
    local package="${1}"
    local version="${2}"
    local ignore="$(echo ${IRNORE_LIST} | grep ${package})"

    if [ -z "${ignore}" ]
    then
        eval "PACKAGE_"${PACKIDX}"_NAME"="${1}"
        eval "PACKAGE_"${PACKIDX}"_VERSION"="${2}"

        PACKIDX=$((${PACKIDX} + 1))
    fi
}


# *******************************************************************
# **  check one single requirement, if it is satisfied or not. If not
# **  add it to the list of missing packages
# **  @param  $1  package name
# **  @param  $2  package version
# **  @return n/a
# *******************************************************************

check_single_requirement()
{
    local package="${1}"
    local version="${2}"
    local state

    state=$(${EISMAN_BIN_PATH}/eisman_check_version.sh ${package} ${version}svn0)
    if [ "${state}" = "new" ] || [ "${state}" = "not-installed" ]
    then
        # check if package is outdated
        local outdated=$(query_outdated_db "${package}" "substitude")
        if [ -n "${outdated}" ]
        then
            if [ ! "${outdated}" = "dummy" ]
            then
                package=$(echo ${outdated} | cut -d ' ' -f 1)
                version=$(echo ${outdated} | cut -d ' ' -f 2)

                # check if version has been supplied
                if [ "${package}" = "${version}" ]
                then
                    version="0.0.0"
                fi

                state=$(${EISMAN_BIN_PATH}/eisman_check_version.sh ${package} ${version}svn0)
                if [ "${state}" = "new" -o "${state}" = "not-installed" ]
                then
                    add_package "${package}" "${version}"
                fi
            fi
        else
            add_package "${package}" "${version}"
        fi
    fi
}


# *******************************************************************
# **  run through package requirements to find unresolved package
# **  references
# **  @param  n/a
# **  @return n/a
# *******************************************************************

check_package_requirements()
{
    # get total number of lines in index
    local linetotal=$(wc -l < /tmp/query$$.txt)
    local lineidx=0
    local linecount=0
    local progress
    local package
    local version

    local lastpackage=""
    local lastversion

    echo -n "       "
    show_progress 0

    while read package version
    do
        lineidx=$((${lineidx} + 1))

        if [ -z "${version}" ]
        then
            version="0.0.0"
        fi

        # check if next require name equals the last one
        if [ -z "${lastpackage}" ] || [ "${package}" != "${lastpackage}" ]
        then
            # if the following requirement is different from the last one
            # process the last package and assign new one
            if [ -n "${lastpackage}" ]
            then
                # check package
                check_single_requirement "${lastpackage}" "${lastversion}"
            fi

            # assign new package
            lastpackage="${package}"
            lastversion="${version}"
        else
            # as long as the following requirement equals the last one,
            # check if the required version is higher as the last one.
            local res=$(diff_version ${version} ${lastversion})
            if [ "${res}" = "new" ]
            then
                lastversion="${version}"
            fi
        fi

        # every 25th loop update progress
        if [ ${lineidx} -eq 25 ]
        then
            linecount=$((${linecount} + 25))

            progress=$((${linecount} * 50 / ${linetotal}))
            show_progress "${progress}"
            lineidx=0
        fi

    done < /tmp/query$$.txt

    # process final entry (if any)
    if [ -n "${lastpackage}" ]
    then
        # check package
        check_single_requirement "${lastpackage}" "${lastversion}"
    fi

    show_progress 50
}


# *******************************************************************
# **  check one single link requirement, if it is satisfied or not.
# **  If not add it to the list of missing packages
# **  @param  $1  package name
# **  @param  $2  package version
# **  @return n/a
# *******************************************************************

check_single_link()
{
    local package="${1}"
    local version="${2}"
    local state

    state=$(${EISMAN_BIN_PATH}/eisman_check_version.sh ${package} ${version}svn0)
    if [ "${state}" = "new" ]
    then
        # check if package is outdated
        local outdated=$(query_outdated_db "${package}" "substitude")
        if [ -n "${outdated}" ]
        then
            if [ ! "${outdated}" = "dummy" ]
            then
                package=$(echo ${outdated} | cut -d ' ' -f 1)
                version=$(echo ${outdated} | cut -d ' ' -f 2)

                # check if version has been supplied
                if [ "${package}" = "${version}" ]
                then
                    version="0.0.0"
                fi

                state=$(${EISMAN_BIN_PATH}/eisman_check_version.sh ${package} ${version}svn0)
                if [ "${state}" = "new" -o "${state}" = "not-installed" ]
                then
                    add_package "${package}" "${version}"
                fi
            fi
        else
            add_package "${package}" "${version}"
        fi
    fi
}


# *******************************************************************
# **  run through package link requirements to find unresolved package
# **  references
# **  @param  n/a
# **  @return n/a
# *******************************************************************

check_package_links()
{
    # get total number of lines in index
    local linetotal=$(wc -l < /tmp/query$$.txt)
    local lineidx=0
    local linecount=0
    local progress
    local package
    local version

    local lastpackage=""
    local lastversion

    show_progress 50

    while read package version
    do
        lineidx=$((${lineidx} + 1))

        if [ -z "${version}" ]
        then
            version="0.0.0"
        fi

        # check if next require name equals the last one
        if [ -z "${lastpackage}" ] || [ "${package}" != "${lastpackage}" ]
        then
            # if the following requirement is different from the last one
            # process the last package and assign new one
            if [ -n "${lastpackage}" ]
            then
                # check package
                check_single_link "${lastpackage}" "${lastversion}"
            fi

            # assign new package
            lastpackage="${package}"
            lastversion="${version}"
        else
            # as long as the following requirement equals the last one,
            # check if the required version is higher as the last one.
            local res=$(diff_version ${version} ${lastversion})
            if [ "${res}" = "new" ]
            then
                lastversion="${version}"
            fi
        fi

        # every 25th loop update progress
        if [ ${lineidx} -eq 25 ]
        then
            linecount=$((${linecount} + 25))

            progress=$((${linecount} * 50 / ${linetotal} + 50))
            show_progress "${progress}"
            lineidx=0
        fi

    done < /tmp/query$$.txt

    # process final entry (if any)
    if [ -n "${lastpackage}" ]
    then
        # check package
        check_single_link "${lastpackage}" "${lastversion}"
    fi

    show_progress 100
}


# *******************************************************************
# **  find requirements of all affected packages
# **  @param  $1 package to find requirements for
# **  @return n/a
# *******************************************************************

find_affected_package_requirements()
{
    local package="${1}"
    local header=$((${FIELDHEADER} + 1))

    if [ "${package}" = "base" ]
    then
        return
    fi

    local result=$(query_package_db      \
        ""                \
        ""                \
        "${package}"      \
        ""                \
        ""                \
        ""                \
        "require"         \
        "${EISMAN_PATH}/installed.db" |
            sort -u                   |
            cut  -c "${header}-")

    echo "${result}" >> /tmp/affected$$.txt

    local IFS_BAK=${IFS}
    IFS="
"

    for requirement in ${result}
    do
        local name=$(echo "${requirement}" | cut -d ' ' -f 1)

        if [ -n "${name}" ]
        then
            find_affected_package_requirements "${name}"
        fi
    done

    IFS="${IFS_BAK}"
}


# *******************************************************************
# **  find link requirements of all affected packages
# **  @param  $1 package to find link requirements for
# **  @return n/a
# *******************************************************************

find_affected_package_links()
{
    local package="${1}"
    local header=$((${FIELDHEADER} + 1))

    if [ "${package}" = "base" ]
    then
        return
    fi

    local result=$(query_package_db      \
        ""                \
        ""                \
        "${package}"      \
        ""                \
        ""                \
        ""                \
        "link"            \
        "${EISMAN_PATH}/installed.db" |
            sort -u                   |
            cut  -c "${header}-")

    echo "${result}" >> /tmp/affected$$.txt

    local IFS_BAK=${IFS}
    IFS="
"

    for requirement in ${result}
    do
        local name=$(echo "${requirement}" | cut -d ' ' -f 1)

        if [ -n "${name}" ]
        then
            find_affected_package_links "${name}"
        fi
    done

    IFS="${IFS_BAK}"
}


# *******************************************************************
# **  find all requirements (searched over the whole installed.db table)
# **  @param  n/a
# **  @return n/a
# *******************************************************************

find_all_package_requirements()
{
    local header=$((${FIELDHEADER} + 1))

    query_package_db      \
        ""                \
        ""                \
        ""                \
        ""                \
        ""                \
        ""                \
        "require"         \
        "${EISMAN_PATH}/installed.db" |
            sort -u                   |
            cut  -c "${header}-"  > /tmp/query$$.txt
}


# *******************************************************************
# **  find all link requirements (searched over the whole installed.db
# **  table)
# **  @param  n/a
# **  @return n/a
# *******************************************************************

find_all_package_links()
{
    local header=$((${FIELDHEADER} + 1))

    query_package_db      \
        ""                \
        ""                \
        ""                \
        ""                \
        ""                \
        ""                \
        "link"            \
        "${EISMAN_PATH}/installed.db" |
            sort -u                   |
            cut  -c "${header}-"  > /tmp/query$$.txt
}


# *******************************************************************
# **  process the requirement check according to the given list of
# **  packages. If the package list is empty, the requirements for all
# **  packages are checked
# **  @param  $1  list of packages to process
# **  @return n/a
# *******************************************************************

process_requirements()
{
    local packagelist="${1}"

    # process all entries of package list to get the currentry
    # existing list of requirements
    if [ -n "${packagelist}" ]
    then
        echo > /tmp/affected$$.txt

        for package in ${packagelist}
        do
            find_affected_package_requirements "${package}"
        done

        cat /tmp/affected$$.txt | sort -u > /tmp/query$$.txt

        rm -f /tmp/affected$$.txt
    else
        # packagelist is empty -> process all packages
        find_all_package_requirements
    fi

    # find missing package requirements
    check_package_requirements

    # remove temporary file
    rm -rf /tmp/query$$.txt
}


# *******************************************************************
# **  process the link requirement check according to the given list of
# **  packages. If the package list is empty, the requirements for all
# **  packages are checked
# **  @param  $1  list of packages to process
# **  @return n/a
# *******************************************************************

process_links()
{
    local packagelist="${1}"

    # process all entries of package list to get the currentry
    # existing list of requirements
    if [ -n "${packagelist}" ]
    then
        echo > /tmp/affected$$.txt

        for package in ${packagelist}
        do
            find_affected_package_links "${package}"
        done

        cat /tmp/affected$$.txt | sort -u > /tmp/query$$.txt

        rm -f /tmp/affected$$.txt
    else
        # packagelist is empty -> process all packages
        find_all_package_links
    fi

    # find missing package requirements
    check_package_links

    # remove temporary file
    rm -rf /tmp/query$$.txt
}


# *******************************************************************
# **  main program
# *******************************************************************
main()
{
    local auto="no"

    while [ "${1:0:2}" = "--" ]
    do
        case $1 in
        --help)
            show_help
            exit 0
            ;;
        --ignore)
            IRNORE_LIST="${IRNORE_LIST}|${1}"
            shift
            ;;
        --ignore=*)
            IRNORE_LIST="${IRNORE_LIST}|${1:9}"
            shift
            ;;
        --auto)
            auto="yes"
            shift
            ;;
        *)
            echo "error: unknown option \"${1}\"!" >&2
            echo ""
            show_help
            exit 1
            ;;
        esac
    done

    IRNORE_LIST="${IRNORE_LIST}|"

    # read parameters
    while [ "$#" -ge 1 ]
    do
        packagelist="${packagelist} ${1}"
        shift
    done

    mecho --info "Searching for unresolved requirements..."

    # search for all reqirements and check if they are satisfied
    process_requirements "${packagelist}"

    # search for all link reqirements and check if they are satisfied
    process_links "${packagelist}"

    # final line break
    echo

    # start installation if missing packages have been found
    if [ "${PACKIDX}" -gt 1 ]
    then
        mecho --info "Done!"
        mecho
        mecho --info -n "Trying to install "
        mecho        -n "$((${PACKIDX} - 1)) "
        mecho --info "missing package(s):"

        idx=1
        while [ "${idx}" -lt "${PACKIDX}" ]
        do
            eval name='${PACKAGE_'${idx}'_NAME}'
            eval version='${PACKAGE_'${idx}'_VERSION}'

            echo "- ${name} (${version})"

            packlist="${packlist} ${name}=${version}"

            idx=$((${idx} + 1))
        done

        mecho
        if [ "${auto}" = "yes" ]
        then
            ${EISMAN_BIN_PATH}/eisman_install.sh --unstable --auto ${packlist}
        else
            ${EISMAN_BIN_PATH}/eisman_install.sh --unstable ${packlist}
        fi
    else
        mecho --info "Done!"
        mecho
        mecho '---------------------------'
        mecho 'No missing packages found!'
        mecho '---------------------------'
    fi
}

main "${@}"
