#!/bin/sh
#
# Copyright (C) 2017-2018, 2020  Etersoft
# Copyright (C) 2017-2018, 2020  Vitaly Lipatov <lav@etersoft.ru>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#

load_helper epm-sh-altlinux
load_helper epm-assure
load_helper epm-status

[ -n "$EPM_REPACK_SCRIPTS_DIR" ] || EPM_REPACK_SCRIPTS_DIR="$CONFIGDIR/repack.d"

# Run alien with dependency checks, fakeroot and verbose handling
# Includes cpio < 2.15 workaround for --make-directories bug:
# https://bugs.debian.org/946267
# https://git.savannah.gnu.org/cgit/cpio.git/commit/?id=376d663340a
# Usage: __run_alien [--fakeroot] <pkg> [alien-args...]
__run_alien()
{
    # Note: install epm-repack for static (package based) dependencies
    assure_exists cpio
    assure_exists alien
    assure_exists fakeroot
    assure_exists rpm

    local use_fakeroot=''
    if [ "$1" = "--fakeroot" ] ; then
        ! is_root && is_command fakeroot && use_fakeroot='fakeroot'
        shift
    fi

    local alpkg="$1"
    shift

    # cpio < 2.15: --make-directories doesn't create parent dirs for symlinks
    # workaround: inject a cpio wrapper that pre-creates dirs from RPM file list
    local cpio_ver
    cpio_ver="$(cpio --version 2>&1 | head -n1 | sed 's/.* //')"
    if [ -n "$cpio_ver" ] && [ "$(epm print compare version "$cpio_ver" "2.15")" = "-1" ] ; then
        local wrapdir
        wrapdir="$(mktemp -d)"
        remove_on_exit "$wrapdir"
        local real_cpio
        real_cpio="$(print_command_path cpio)"
        local abs_alpkg
        abs_alpkg="$(realpath "$alpkg")"
        cat > "$wrapdir/cpio" <<CPIOWRAPPER
#!/bin/sh
case "\$*" in
    *--extract*)
        rpm -qpl "$abs_alpkg" 2>/dev/null | while IFS= read -r f; do
            mkdir -p ".\$(dirname "\$f")" 2>/dev/null
        done
        ;;
esac
exec $real_cpio "\$@"
CPIOWRAPPER
        chmod +x "$wrapdir/cpio"
        PATH="$wrapdir:$PATH"
    fi

    local alien_verbose="$verbose"
    [ -n "$debug" ] && alien_verbose="--veryverbose"

    if [ -n "$verbose" ] || [ -n "$debug" ] ; then
        docmd $use_fakeroot alien $alien_verbose "$@" "$alpkg"
    else
        showcmd $use_fakeroot alien "$@" "$alpkg"
        $use_fakeroot alien "$@" "$alpkg" >/dev/null
    fi
}

__epm_have_repack_rule()
{
    # FIXME: use real way (for any archive)
    local pkgname="$(epm print name for package "$1" 2>/dev/null)"
    local repackcode="$EPM_REPACK_SCRIPTS_DIR/$pkgname.sh"
    [ -s "$repackcode" ]
}

__epm_check_repack_rule()
{
    # skip repacking on non ALT systems
    [ "$BASEDISTRNAME" = "alt" ] || return 1

    local i
    for i in $* ; do
        # skip for packages built with repack
        epm_status_repacked "$i" && return 1

        __epm_have_repack_rule "$i" || return 1
    done
    return 0
}

__epm_check_if_needed_repack()
{
    __epm_check_repack_rule "$@" || return
    local pkgname="$(epm print name for package "$1")"
    warning 'There is repack rule for $pkgname package. It is better install this package via epm install --repack or epm play.'
}

# make light and independent copy to target
# note: target file can be read only
# args: abs_source target
__epm_repack_copy()
{
    local abspkg="$1"
    local target="$2"

    # skip if source and target are the same file
    [ "$abspkg" -ef "$target" ] && return 0

    # if source file is not writable, try CoW or fallback to ordinal copy
    #if [ ! -w "$abspkg" ] ; then
    #    cp --reflink=auto $verbose $abspkg $target 2>/dev/null && return
    #    return
    #fi

    # If we can make symlink, we don't need CoW and hardlink
    ## firstly try CoW
    #cp --reflink=always $verbose $abspkg $target 2>/dev/null && return
    # after failed CoW remained empty file
    #rm $target
    ## next try create hardlink
    #cp -l $verbose $abspkg $target 2>/dev/null && return
    # next try create symlink
    cp -s $verbose $abspkg $target 2>/dev/null && return
    # just make copy as fallback
    cp $verbose $abspkg $target && return
    fatal "Can't copy $abspkg to $target"
}


# arg: rpm or deb
# fills split_replaced_pkgs with packages of that type
__epm_split_by_pkg_type()
{
    local type="$1"
    shift

    split_replaced_pkgs=''

    for pkg in "$@" ; do
        [ "$(get_package_type "$pkg")" = "$type" ] || return 1
        [ -e "$pkg" ] || fatal "Can't read $pkg"
        split_replaced_pkgs="$split_replaced_pkgs $pkg"
    done

    [ -n "$split_replaced_pkgs" ]
}


__check_stoplist()
{
    local pkg="$1"
    local alf="$CONFIGDIR/repackstoplist.list"
    [ -s "$alf" ] || return 1
    [ -n "$pkg" ] || return 1
    grep -E -q "^$1$" $alf
}

__convert_packrule_to_regexp()
{
    local tmpalf
    tmpalf="$(mktemp)" || fatal
    # copied from eget's filter_glob
    # check man glob
    # remove commentы and translate glob to regexp
    grep -v "^[[:space:]]*#" "$1" | grep -v "^[[:space:]]*$" | sed -e "s|\*|.*|g" -e "s|?|.|g" >$tmpalf
    echo "$tmpalf"
}

# fill __PACKRULE if $1 in packrules.list
__check_packrule()
{
    local pkg="$1"
    local alf="$CONFIGDIR/packrules.list"
    [ -s "$alf" ] || return 1
    [ -n "$pkg" ] || return 1

    local tmpalf=$(__convert_packrule_to_regexp "$alf")
    remove_on_exit $tmpalf
    __PACKRULE="$(awk -v s="$pkg" 'BEGIN{FS=" "} s ~ $2  {print $1}' "$tmpalf")"
    rm $tmpalf
    [ -n "${__PACKRULE}" ]
    return
}

# arg: <package file>
# sets:
#   alpkg      - resulted package file name in the current dir
#   SUBGENERIC - name of generic file's extension
__prepare_source_package()
{
    local pkg="$1"

    alpkg=$(basename $pkg)
    pkgversion="$2"

    # TODO: use func for get name from deb pkg
    # TODO: epm print name from deb package
    local pkgname="$(echo $alpkg | sed -e "s|_.*||")"

    # TODO: use stoplist only for deb?
    [ -z "$force" ] && __check_stoplist $pkgname && fatal 'Please use official package instead of $alpkg repacking (It is not recommended to use --force to skip this checking.'

    SUBGENERIC=''

    if rhas "$alpkg" "\.(rpm|deb)$" ; then
        # skip packing for supported directly: rpm and deb
        return
    fi

    # convert tarballs to tar (for alien)
    load_helper epm-pack

    # they will fill $returntarname

    if __check_packrule "$alpkg" ; then
        __epm_pack_run_handler ${__PACKRULE} "$pkg" "$pkgversion" "$pkg_urls_downloaded"
    elif rihas "$alpkg" "\.AppImage$" ; then
        # big hack with $pkg_urls_downloaded (it can be a list, not a single url)
        __epm_pack_run_handler generic-appimage "$pkg" "$pkgversion" "$pkg_urls_downloaded"
        SUBGENERIC='appimage'
    elif rhas "$alpkg" "\.snap$" ; then
        __epm_pack_run_handler generic-snap "$pkg" "$pkgversion"
        SUBGENERIC='snap'
    else
        __epm_pack_run_handler generic-tar "$pkg" "$pkgversion"
        SUBGENERIC='tar'
    fi

    # it is possible there are a few files, we don't support it
    [ -s "$returntarname" ] || fatal 'Can'\''t read result from pack: $returntarname is not a readable file.'

    alpkg=$(basename $returntarname)
    # FIXME: looks like a hack with current dir
    if ! [ "$returntarname" -ef "$alpkg" ] ; then
        cp $verbose $returntarname $alpkg
        [ -r "$returntarname.eepm.yaml" ] && cp $verbose $returntarname.eepm.yaml $alpkg.eepm.yaml
    fi
}



# used in epm install
__epm_repack_single()
{
    local pkg="$1"
    local packversion="$2"
    local packrelease="$3"
    case $PKGFORMAT in
        rpm)
            load_helper epm-repack-rpm
            if [ "$BASEDISTRNAME" = "alt" ] ; then
                if epm status --original "$pkg" ; then
                    fatal_warning "Repacking package $pkg from ALT repository is dangerous and forbidden (see https://bugzilla.altlinux.org/56355)."
                fi
                if epm status --repacked "$pkg" ; then
                    fatal_warning "Repacking already repacked package $pkg is uselessly."
                fi
            fi
            __epm_repack_to_rpm "$pkg" "$packversion" "$packrelease" || return
            ;;
        deb)
            # repack via rpm if source is not deb or we have rpm rule for the package
            if __epm_have_repack_rule "$pkg" || ! rhas "$pkg" "\.deb$" ; then
                # we have repack rules only for rpm, so use rpm step in any case
                load_helper epm-repack-rpm
                load_helper epm-repack-deb
                # save original depends before deb→rpm conversion
                local orig_deb_depends=""
                local orig_pkg_type="$(get_package_type "$pkg")"
                if [ "$orig_pkg_type" = "deb" ] ; then
                    orig_deb_depends="$(epm requires "$pkg" 2>/dev/null)"
                elif [ "$orig_pkg_type" = "rpm" ] ; then
                    # extract non-library package-name Requires from RPM
                    orig_deb_depends="$(epm requires --debian "$pkg" 2>/dev/null)"
                fi
                __epm_repack_to_rpm "$pkg" "$packversion" "$packrelease" || return
                [ -n "$repacked_pkg" ] || return
                __epm_repack_to_deb $repacked_pkg "$orig_deb_depends" "$orig_pkg_type"
            else
                load_helper epm-repack-deb
                __epm_repack_to_deb "$pkg" || return
            fi
            ;;
        pkg.tar.xz|pkg.tar.zst)
            load_helper epm-repack-arch
            __epm_repack_to_arch "$pkg" || return
            ;;
        *)
            fatal '$PKGFORMAT is not supported for repack yet'
            ;;
    esac

    return 0
}

# fill repacked_pkgs
__epm_repack()
{
    local pkg
    repacked_pkgs=''
    local packversion="${EPM_REPACK_VERSION:-}"
    local packrelease="${EPM_REPACK_RELEASE:-}"
    [ -n "$pkg_urls_downloaded" ] || pkg_urls_downloaded="${EPM_REPACK_URL:-}"

    print_sha256sum $pkg_files

    for pkg in $* ; do
        __epm_repack_single "$pkg" "$packversion" "$packrelease" || fatal 'Error with $pkg repacking.'
        [ -n "$repacked_pkgs" ] && repacked_pkgs="$repacked_pkgs $repacked_pkg" || repacked_pkgs="$repacked_pkg"
    done
}


epm_repack_help()
{
    message "epm repack - repack packages to native format"
    message "Usage: epm repack [options] <package files>"
    message ""
    message "Options:"
    message "  --install              install repacked packages"
    message "  --check-repack-rule    warn if there is a repack rule for the package"
}

epm_repack()
{
    local opt
    for opt in $pkg_options ; do
        case "$opt" in
            -h|--help)
                epm_repack_help
                return
                ;;
            --check-repack-rule)
                __epm_check_if_needed_repack $pkg_files
                return
                ;;
        esac
    done

    # if possible, it will put pkg_urls into pkg_files and reconstruct pkg_filenames
    if [ -n "$pkg_urls" ] ; then
        load_helper epm-download
        __download_pkg_urls
        pkg_urls=
    fi

    [ -n "$pkg_names" ] && warning 'Can'\''t find $pkg_names files'
    [ -z "$pkg_files" ] && info "Empty repack list was skipped" && return 22
    if __epm_repack $pkg_files && [ -n "$repacked_pkgs" ] ; then
        if [ -n "$install" ] ; then
            [ "$allow_repack_install" = "0" ] && fatal "Installing repacked packages is forbidden by allow_repack_install=0 in $CONFIGDIR/eepm.conf"
            epm install $repacked_pkgs
            return
        fi

        for i in $repacked_pkgs ; do
            [ "$i" -ef "$EPMCURDIR/$(basename "$i")" ] || cp $i "$EPMCURDIR"
        done
        if [ -z "$quiet" ] ; then
            echo
            message "Adapted packages:"
            for i in $repacked_pkgs ; do
                echo "    $EPMCURDIR/$(basename "$i")"
            done
        fi
    fi

}
