#!/bin/sh
# shellcheck disable=2094,2103,SC2181 source=/dev/null
#
# puke - package manager for kiss linux.

die() {
    printf '\033[31mERROR>\033[m %s\n' "$@" >&2
    exit 1
}

log() {
    printf '\033[32m=>\033[m %s\n' "$@"
}

clean() {
    rm -rf -- "$mak_dir" "$pkg_dir" \
              "$rep_dir/$name/.checksums" \
              "$rep_dir/$name/.manifest" 2>/dev/null
}

pkg_setup() {
    [ -z "$1" ]      && die "No package specified."
    cd "$rep_dir/$1" || die "Package '$1' not in repository."
    [ -f version ]   || die "Version file not found."
    [ -f sources ]   || die "Sources file not found."
    [ -f build ]     || die "Build file not found."

    read -r version release < version
    name=$1
    pkg=$name\#$version-$release

    mkdir -p "$src_dir" "$mak_dir" "$pkg_dir/$dbs_dir" \
             "$sys_dir" "$bin_dir" ||
             die "Couldn't create directories at '$PWD'".
}

pkg_depends() {
    [ -f depends ] && while read -r dependency; do
        pkg_list "$dependency" || missing="$missing $dependency"
    done < depends

    [ -n "$missing" ] && die "Missing dependencies:$missing"
}

source_type() {
    [ -f "$1" ]                && return 2
    [ -f "$src_dir/${1##*/}" ] && return 3
    [ -z "${1##git:*}" ]       && return 4
    [ -z "${1##*://*}" ]       && return 5
}

pkg_sources() {
    while read -r src; do
        source_type "$src"

        case $? in
            2|3) log "Found local ${src##*/}." ;;
            4) git clone "${src##git:}" "$mak_dir" ;;

            5) log "Downloading '$src'."
               wget -P "$src_dir" "$src" || die "Failed to download $src." ;;

            *) die "Source file '$src' not found." ;;
        esac
    done < sources
}

pkg_checksum() {
    while read -r src; do
        source_type "$src"

        case $? in
            2) src_path=$src ;;
            3) src_path=$src_dir/${src##*/} ;;
            4) continue ;;
        esac

        cd "${src_path%/*}" >/dev/null
        sha256sum -- "${src##*/}" || die "Failed to generate checksums."
        cd - >/dev/null
    done < sources > "${1-checksums}"
}

pkg_verify() {
    pkg_checksum .checksums

    diff .checksums checksums ||
        die "checksum of sources does not match checksum of package" \
            "run '$0 checksum $name' to update checksums"

    log "Checksums verified."
    rm .checksums
}

pkg_extract() {
    while read -r src; do
        source_type "$src"

        case $? in
            2) cp -f "$src" "$mak_dir" ;;
            4) continue ;;

            3) case $src in
                   *.tar|*.tar.??|*.tar.???|*.tar.????|*.tgz)
                       tar xf "$src_dir/${src##*/}" -C "$mak_dir" \
                           --strip-components 1 ||
                           die "Couldn't extract ${src##*/}"
                   ;;
               esac ;;

            *) die "${src##*/} not found."
        esac
    done < sources
}

pkg_build() {
    log "Building $pkg."

    (set -e errexit; cd "$mak_dir"; . "$OLDPWD/build") ||
        die "Build failed."

    cp -R "$rep_dir/$name" "$pkg_dir/$dbs_dir"
    touch "$pkg_dir/$dbs_dir/$name/manifest"

    log "Sucessfully built $pkg."
}

pkg_manifest() {
    cd "$pkg_dir"

    _() { find . -mindepth 1 "$@" | sed 's/^\.//'; }
    _ -not -type d  > "$OLDPWD/manifest"
    _ -type d | sort -r >> "$OLDPWD/manifest"

    cp "$OLDPWD/manifest" "$pkg_dir/$dbs_dir/$name"
    cd - >/dev/null
}

pkg_tar() {
    cd "$bin_dir"

    tar pcvf "$pkg.tar.gz" -C "$pkg_dir" . ||
        die "Failed to create package."

    log "Package is at $bin_dir/$pkg.tar.gz."
}

pkg_install() {
    [ -f "$bin_dir/$pkg.tar.gz" ] || die "Package must be built first."

    pkg_remove "$name"
    tar pxvf "$bin_dir/$pkg.tar.gz" -k -C "$sys_dir/" 2>/dev/null

    [ -f "$sys_dir/$dbs_dir/$name/post-install" ] &&
        . "$sys_dir/$dbs_dir/$name/post-install"

    log "Installed $pkg"
}

pkg_remove() {
    pkg_list "$name" || return 1

    cp "$sys_dir/$dbs_dir/$name/manifest" .manifest

    while read -r file; do
        [ "${file%/*}" = "/etc" ] && continue

        path="${sys_dir}${file}"

        if [ -d "$path" ]; then
            rmdir "$path" 2>/dev/null
        else
            rm -- "$path" || log "Failed to remove $file."
        fi

        [ "$?" = 0 ] && log "Removed $file"
    done < .manifest

    return 0
}

pkg_updates() {
    git pull
    cd "$sys_dir/$dbs_dir"

    for pkg in *; do
        read -r db_version db_release < "$pkg/version"
        read -r re_version re_release < "$rep_dir/$pkg/version"

        [ "$db_version-$db_release" != "$re_version-$re_release" ] &&
            log "NEW $pkg $re_version-$re_release"
    done
}

pkg_list() {
    [ "$1" ] && {
        [ -d "$sys_dir/$dbs_dir/$1" ] || return 1 && return 0
    }

    for pkg in "$sys_dir/$dbs_dir/"*; do
        [ -d "$pkg" ] || continue
        read -r version release < "$pkg/version"
        log "${pkg##*/} $version-$release"
    done
}

args() {
    case $1 in b*|c*|d*|i*|r*) pkg_setup "$2"; esac
    case $1 in
        b*)
            [ -f checksums ] || die "Checksums missing, run '$0 checksum $name'"

            pkg_depends
            pkg_sources
            pkg_verify
            pkg_extract
            pkg_build
            pkg_manifest
            pkg_tar
        ;;

        c*)
            pkg_sources
            pkg_checksum

            log "Generated checksums."
        ;;

        d*) [ -f depends ] && cat depends ;;
        i*) pkg_install ;;
        l*) pkg_list "$2" ;;
        r*) pkg_remove || die "Package '$name' not installed" ;;
        u*) pkg_updates ;;

        *)  log "$0 [b|c|d|i|l|r|u] [pkg]" \
                "build:     Build a package." \
                "checksum:  Generate checksums." \
                "depends:   See package dependencies." \
                "install:   Install a package." \
                "list:      List packages." \
                "remove:    Remove a package." \
                "update:    Check for updates."
    esac
}

main() {
    trap clean EXIT INT
    clean

    src_dir=$PWD/sources
    mak_dir=$PWD/build
    pkg_dir=$PWD/pkg
    sys_dir=$PWD/sys
    bin_dir=$PWD/bin
    rep_dir=$PWD/repo
    dbs_dir=var/db/puke

    args "$@"
}

main "$@"