#!/bin/sh -e
#
# This is a simple package manager written in POSIX 'sh' for
# KISS Linux utlizing the core unix utilites where needed.
#
# The script runs with 'set -e' enabled. It will exit on any
# non-zero return code. This ensures that no function continues
# if it fails at any point.
#
# Keep in mind that this involves extra code in the case where
# an error is optional or required.
#
# Where possible the package manager should "error first".
# Check things first, die is necessary and continue if all is well.
#
# The code below conforms to shellcheck's rules. However, some
# lint errors *are* disabled as they relate to unexpected
# behavior (which we do expect).
#
# KISS is available under the MIT license.
#
# - Dylan Araps.

die() {
    # Print a message and exit with '1' (error).
    printf '\033[31m!>\033[m %s\n' "$@" >&2
    exit 1
}

log() {
    # Print a message with a colorful arrow to distinguish
    # from other output.
    printf '\033[32m=>\033[m %s\n' "$@"
}

pkg_search() {
    # Figure out which repository a package belongs to by
    # searching for directories matching the package name
    # in $KISS_PATH/*.
    [ "$KISS_PATH" ] || \
        die "\$KISS_PATH needs to be set." \
            "Example: KISS_PATH=/packages/core:/packages/extra:/packages/xorg" \
            "Repositories will be searched in the configured order." \
            "The variable should work just like \$PATH."

    # Disable globbing with 'set -f' to ensure that the unquoted
    # variable doesn't expand into anything nasty.
    # shellcheck disable=2086,2046
    {
        set -f
        set -- "$1" $(IFS=:; find $KISS_PATH -maxdepth 1 -name "$1")
        set +f
    }

    # A package may also not be found due to a repository not being
    # readable by the current user. Either way, we need to die here.
    [ -z "$2" ] && die "Package '$1' not in any repository."

    printf '%s\n' "$2"
}

pkg_list() {
    # List installed packages. As the format is files and
    # diectories, this just involves a simple for loop and
    # file read.

    # Changing directories is similar to storing the full
    # full path in a variable, only there is no variable as
    # you can access children relatively.
    cd "$KISS_ROOT/var/db/kiss" || \
        die "KISS database doesn't exist or is inaccessible."

    # Optional arguments can be passed to check for specific
    # packages. If no arguments are passed, list all. As we
    # loop over '$@', if there aren't any arguments we can
    # just set the directory contents to the argument list.
    [ "$1" ] || set -- *

    # Loop over each version file and warn if one doesn't exist.
    # Also warn if a package is missing its version file.
    for pkg; do
        [ -d "$pkg" ] || {
            log "Package '$pkg' is not installed."
            return 1
        }

        [ -f "$pkg/version" ] || {
            log "Warning: Package '$pkg' has no version file."
            return
        }

        read -r version release < "$pkg/version" &&
            printf '%s\n' "${pkg%/*} $version-$release"
    done
}

args() {
    # Parse script arguments manually. POSIX 'sh' has no 'getopts'
    # or equivalent built in. This is rather easy to do in our case
    # since the first argument is always an "action" and the arguments
    # that follow are all package names.

    # Actions can be abbreviated to their first letter. This saves
    # keystrokes once you memorize themand it also has the side-effect
    # of "correcting" spelling mistakes assuming the first letter is
    # right.
    while [ "$1" ]; do
        case $1 in
            # Build the list of packages.
            b*)

            ;;

            # List installed packages.
            l*)
                shift
                pkg_list "$@"
                exit
            ;;

            # Print version and exit.
            v*)
                log "$kiss 0.1.10"
                exit
            ;;

            # Catch all invalid arguments as well as
            # any help related flags (-h, --help, help).
            *)
                log "$kiss [b|c|i|l|r|u] [pkg]" \
                    "build:     Build a package." \
                    "checksum:  Generate checksums." \
                    "install:   Install a package (Runs build if needed)." \
                    "list:      List packages." \
                    "remove:    Remove a package." \
                    "update:    Check for updates."
                exit
            ;;
        esac
    done
}

main() {
    kiss=${0##*/}

    args "$@"
}

main "$@"