#!/bin/sh
#
# Copyright (C) 2015, 2017, 2019, 2020, 2022  Etersoft
# Copyright (C) 2015, 2017, 2019, 2020, 2022  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-play-common

__check_installed_app()
{
    [ -s $epm_vardir/installed-app ] || return 1
    grep -q -- "^$1\$" $epm_vardir/installed-app
}

__save_installed_app()
{
    [ -d "$epm_vardir" ] || return 0
    __check_installed_app "$1" && return 0
    set_sudo
    echo "$1" | sudorun tee -a $epm_vardir/installed-app >/dev/null
}

__remove_installed_app()
{
    [ -s $epm_vardir/installed-app ] || return 0
    local i
    set_sudo
    for i in $* ; do
        sudorun sed -i "/^$i$/d" $epm_vardir/installed-app
    done
    return 0
}


__is_repo_play_app_installed()
{
    local app="$1"
    local list_file="$CONFIGDIR/repo-play-apps.list"
    [ -s "$list_file" ] || return 1
    grep -q "^$app$" "$list_file" || return 1
    # App is in the repo list — check if its package is actually installed
    local pkg
    pkg="$(__run_script "$app" --package-name </dev/null 2>/dev/null)"
    [ -n "$pkg" ] && epm status --installed "$pkg" </dev/null
}

__is_app_installed()
{
    __run_script "$1" --installed "$2" && return 0
    # Fallback: check if app is repo-installed (listed in repo-play-apps.list)
    __is_repo_play_app_installed "$1"
}


__get_app_package()
{
    if [ "$PKGFORMAT" = "deb" ] ; then
        local pkgname
        pkgname="$(grep -oP "^PKGNAME=[\"']*\K[^\"']+" "$psdir/$1.sh")" && echo "$pkgname" | tr "[:upper:]" "[:lower:]" && return
    else
        grep -oP "^PKGNAME=[\"']*\K[^\"']+" "$psdir/$1.sh" && return
    fi
    # fallback if PKGNAME is not set directly
    __run_script "$1" --package-name "$2" "$3" 2>/dev/null
}

# args: app pkglistfile
__get_resolved_app_package()
{
    local basepkgname
    local productalt
    local pkglist="$2"

    #if [ "$BASEDISTRNAME" != "alt" ] ; then
    #    __get_app_package "$1"
    #    return
    #fi

    basepkgname="$(grep -oP "^BASEPKGNAME=[\"']*\K[^\"']+" "$psdir/$1.sh")"
    # lithium construct PKGNAME
    [ -z "$basepkgname" ] && __get_app_package "$1" && return # || fatal "Missed both PKGNAME and BASEPKGNAME in the play script $1."

    # Check exact base package name first
    grep -o -m1 -E "^$basepkgname[ $]" $pkglist && return

    # If PRODUCTALT is defined, check only those specific alternatives
    productalt="$(grep -oP "^PRODUCTALT=[\"']*\K[^\"']+" "$psdir/$1.sh")"
    if [ -n "$productalt" ] ; then
        local alt
        for alt in $productalt ; do
            [ "$alt" = "''" ] && continue
            grep -o -m1 -E "^$basepkgname-$alt[ $]" $pkglist && return
        done
    else
        # Fallback: match any suffix (for scripts without PRODUCTALT)
        grep -o -m1 -E "^$basepkgname-[a-z0-9-]*[ $]" $pkglist && return
    fi
}

__list_all_packages()
{
    local name
    local arch="$SYSTEMARCH"

    for name in $(__get_fast_short_list_app $arch) ; do
        __get_app_package $name
    done
}

__print_targeted_packages()
{
    if [ "$PKGFORMAT" = "deb" ] ; then
        tr "[:upper:]" "[:lower:]" <"$1"
        return
    fi
    cat "$1"
}

# TODO: wrong, missed some packages (f.i., kubo
__list_app_packages_table()
{
    local pkglist="$1"
    local arch="$SYSTEMARCH"
    local IGNOREi586

    local tmplist
    tmplist="$(mktemp)" || fatal
    remove_on_exit $tmplist

    local tmplist1
    tmplist1="$(mktemp)" || fatal
    remove_on_exit $tmplist1

    [ "$arch" = "x86_64" ] && IGNOREi586='NoNo' || IGNOREi586='i586-'

    __get_fast_short_list_app $arch | LC_ALL=C sort -k1,1 >$tmplist
    find $psdir -maxdepth 1 -name '*.sh' ! -type l | xargs grep -l -E "^SUPPORTEDARCHES=(''|\"\"|.*\<$arch\>)" | xargs grep -oP "^PKGNAME=[\"']*\K[^\"']+"  | sed -e "s|.*/\(.*\).sh:|\1 |" | grep -v -E "(^$IGNOREi586|^common|#.*$)" | LC_ALL=C sort -k1,1 >$tmplist1
    # tmplist - app
    # tmplist1 - app package
    __print_targeted_packages $tmplist1 | LC_ALL=C join -j 1 -a 1 $tmplist - | while read -r app package ; do
        [ -n "$package" ] || package="$(__get_resolved_app_package $app $pkglist </dev/null)"
        [ -n "$package" ] || continue # fatal "Missed package for $app"
        echo "$package $app"
    done
}


# pkg app
__list_app_packages_table_old()
{
    local name
    local arch="$SYSTEMARCH"
    for name in $(__get_fast_short_list_app $arch) ; do
        local pkg
        for pkg in $(__get_app_package $name) ; do
            echo "$pkg $name"
            # check only first package
            break
        done
    done
}

__get_all_rpm_repacked_packages()
{
    FORMAT="%{Name} %{Version} %{Packager}\n"
    a= rpmquery --queryformat "$FORMAT" -a | grep "EPM <support@e"
}

__filter_by_repacked_rpm_packages()
{
    local i
    local tapt="$1"
    local pkglist="$2"

    LC_ALL=C join -11 -21 $tapt $pkglist | uniq

    # rpm on Fedora/CentOS no more print missed packages to stderr
    # get supported packages list and print lines with it
    #for i in $(epm query --short $(cat $tapt | cut -f1 -d" ") 2>/dev/null) ; do
    #    grep "^$i " $tapt
    #done
}

__filter_by_installed_packages()
{
    local i
    local tapt="$1"
    local pkglist="$2"

    # get intersect between full package list and available packages table
    LC_ALL=C join -11 -21 $tapt $pkglist | uniq | while read -r package app ; do
        if epm status --repacked "$package" </dev/null || epm status --thirdparty "$package" </dev/null  ; then
            echo "$package $app"
        fi
    done
}

__get_installed_table()
{
    local i
    local tapt
    tapt="$(mktemp)" || fatal
    remove_on_exit $tapt

    local pkglist
    pkglist="$(mktemp)" || fatal
    remove_on_exit $pkglist

    if [ "$PKGFORMAT" = "rpm" ] ; then
        # fast hack to get all repacked packages
        # it misses all thirdparty packages installed as is
        __get_all_rpm_repacked_packages | LC_ALL=C sort -u >$pkglist
        __list_app_packages_table $pkglist | LC_ALL=C sort -u >$tapt
        __filter_by_repacked_rpm_packages $tapt $pkglist
    else
        epm --short packages | LC_ALL=C sort -u >$pkglist
        __list_app_packages_table $pkglist | LC_ALL=C sort -u >$tapt
        __filter_by_installed_packages $tapt $pkglist
    fi

    rm -f $tapt
    rm -f $pkglist
}

__get_repo_installed_apps()
{
    local found_apps="$1"
    local list_file="$CONFIGDIR/repo-play-apps.list"
    [ -s "$list_file" ] || return
    local app
    while read -r app ; do
        [ -n "$app" ] || continue
        [ "${app#\#}" != "$app" ] && continue
        echo "$found_apps" | grep -q "^$app$" && continue
        __is_repo_play_app_installed "$app" </dev/null 2>/dev/null && echo "$app"
    done <"$list_file"
}

__list_installed_app()
{
    # get all installed packages and convert it to a apps list
    local apps
    apps="$(__get_installed_table | cut -f2 -d" ")"
    [ -n "$apps" ] && echo "$apps"
    __get_repo_installed_apps "$apps"
}

__list_installed_packages()
{
    # get all installed packages
    __get_installed_table | cut -f1 -d" "
}


__epm_play_list_installed()
{
    local i
    local arch="$SYSTEMARCH"
    if [ -n "$short" ] ; then
        for i in $(__list_installed_app) ; do
            # skip hidden apps
            local desc="$(__get_app_description $i $arch)"
            [ -n "$desc" ] || continue
            echo "$i"
        done
        exit
    fi
    [ -n "$quiet" ] || echo "Installed applications:"
    for i in $(__list_installed_app) ; do
        # skip hidden apps
        local desc="$(__get_app_description $i $arch)"
        [ -n "$desc" ] || continue
        [ -n "$quiet" ] || echo -n "  "
        printf "%-25s - %s\n" "$i" "$desc"
    done
}


epm_play_help()
{
    message '
Usage: epm play [options] [<app>]
Options:
    <app>                 - install <app>
    --remove <app>        - uninstall <app>
    --update [<app>|all]  - update <app> (or all installed apps) if there is new version
    --latest <app>        - forced to install the latest version of the application
    --print-url <app>     - print download URL for the app (do not install)
    --list                - list all installed apps
    --list-all            - list all available apps
    --search <pattern>    - search available apps by name or description
    --list-updates        - list installed apps that have available updates
    --list-scripts        - list all available scripts
    --short (with --list) - list names only
    --installed <app>     - check if the app is installed
    --ipfs <app>          - use IPFS for downloading
    --product-alternatives- list alternatives (use like epm play app=beta)

Extra options:
    --installed-version   - print installed version for the app
    --available-version   - print available version for the app
    --package-name        - print package name for the app
    --info                - print info about the app
    --list-installed-packages - print list of all packages installed via epm play
    --list-all-packages   - print list of all packages available via epm play

Examples:
    epm play --remove opera
    epm play yandex-browser = beta
    epm play telegram = beta
    epm play telegram = 4.7.1
    epm play --update all
'
}



__epm_play_update()
{
    local i RES
    local CMDUPDATE="$1"
    shift
    RES=0
    for i in $* ; do
        echo
        info 'Updating $i ...'
            if ! __is_app_installed "$i" ; then
                continue
            fi
            local pkgname="$(__get_app_package "$i")"
            if [ -n "$pkgname" ] && epm mark checkhold "$pkgname" 2>/dev/null ; then
                info "Skipping $i: package $pkgname is on hold"
                continue
            fi
        prescription="$i"
        if ! has_play_script $prescription ; then
            warning "Can't find executable play script for $prescription. Try epm play --remove $prescription if you don't need it anymore."
            RES=1
            continue
        fi
        __epm_play_run $prescription $CMDUPDATE || RES=$?
    done
    return $RES
}


# name argument
__epm_play_install_one()
{
    local prescription="$1"
    shift

    if has_play_script "$prescription" ; then
        #__is_app_installed "$prescription" && info "$$prescription is already installed (use --remove to remove)" && exit 1
        __epm_play_run "$prescription" --run "$@" && __save_installed_app "$prescription" || fatal "There was some error during install the application."
    else
        local opsdir=$psdir
        psdir=$prsdir
        if ! has_play_script "$prescription" ; then
            psdir=$opsdir
            echo "Unknown app '$prescription'." >&2
            [ -n "$verbose" ] && echo "Checked in $opsdir and $prsdir" >&2
            local selected
            selected="$(__epm_play_suggest_similar_apps "$prescription")"
            if [ -n "$selected" ] ; then
                info "Installing selected: $selected"
                __epm_play_install_one "$selected" "$@"
                return $?
            fi
            return 1
        fi
        __epm_play_run "$prescription" --run "$@" || fatal "There was some error during run $prescription script."
    fi
}

__download_versions_list()
{
    local target="$1"
    local epmver="$(epm --short --version)"
    # use baseversion (strip last .N)
    epmver=$(echo "$epmver" | sed -e 's|\.[0-9]*$||')

    local URL
    for URL in "https://eepm.ru/releases/$epmver/app-versions" "https://eepm.ru/app-versions" ; do
        eget -q -O "$target" "$URL/epm-play-list-full.txt" && return
    done
    return 1
}

# output: pkg app installed_version
__get_installed_table_with_versions()
{
    local tapt
    tapt="$(mktemp)" || fatal
    remove_on_exit $tapt

    local pkglist
    pkglist="$(mktemp)" || fatal
    remove_on_exit $pkglist

    if [ "$PKGFORMAT" = "rpm" ] ; then
        # pkglist: pkg version EPM <support@...>
        __get_all_rpm_repacked_packages | LC_ALL=C sort -u >$pkglist
        __list_app_packages_table $pkglist | LC_ALL=C sort -u >$tapt
        # join gives: pkg app version EPM <support@...>
        LC_ALL=C join -11 -21 $tapt $pkglist | uniq | awk '{print $1, $2, $3}'
    else
        local verlist
        verlist="$(mktemp)" || fatal
        remove_on_exit $verlist

        local instpkgs
        instpkgs="$(mktemp)" || fatal
        remove_on_exit $instpkgs

        epm --short packages | LC_ALL=C sort -u >$pkglist
        __list_app_packages_table $pkglist | LC_ALL=C sort -u >$tapt
        __filter_by_installed_packages $tapt $pkglist | LC_ALL=C sort -k1,1 >$instpkgs

        # get versions for all installed packages
        dpkg-query -W -f='${Package} ${Version}\n' 2>/dev/null | LC_ALL=C sort -k1,1 >$verlist

        # join to add versions: pkg app version
        LC_ALL=C join -11 -21 $instpkgs $verlist | awk '{print $1, $2, $3}'
    fi

    rm -f $tapt $pkglist
}

__compare_versions()
{
    local latest="$1"
    local installed="$2"

    case "$PKGFORMAT" in
        rpm)
            if is_command rpmevrcmp ; then
                a= rpmevrcmp "$latest" "$installed"
            else
                a= rpm --eval "%{lua:print(rpm.vercmp('$latest', '$installed'))}"
            fi
            ;;
        deb)
            a= dpkg --compare-versions "$latest" gt "$installed" && echo "1" && return
            echo "0"
            ;;
        *)
            epm print compare version "$latest" "$installed" 2>/dev/null
            ;;
    esac
}

__list_available_updates()
{
    local installed_file latest_file joined_file
    installed_file="$(mktemp)" || fatal
    remove_on_exit $installed_file
    latest_file="$(mktemp -u)" || fatal
    remove_on_exit $latest_file
    joined_file="$(mktemp)" || fatal
    remove_on_exit $joined_file

    # Step 1: get installed packages with versions: pkg app installed_version
    __get_installed_table_with_versions | LC_ALL=C sort -k1,1 >$installed_file

    [ -s "$installed_file" ] || return 0

    # Step 2: download bulk versions list: pkg latest_version [release]
    __download_versions_list "$latest_file" || { warning "Can't download versions list" ; return 1 ; }
    [ -s "$latest_file" ] || { warning "Empty versions list" ; return 1 ; }

    # for deb, lowercase the package name field for case-insensitive join
    if [ "$PKGFORMAT" = "deb" ] ; then
        awk '{$1=tolower($1); print}' "$latest_file" | LC_ALL=C sort -k1,1 -o "$latest_file"
    else
        LC_ALL=C sort -k1,1 "$latest_file" -o "$latest_file"
    fi

    # Step 3: join on package name
    # installed_file: pkg app installed_version
    # latest_file: pkg latest_version [release]
    # result: pkg app installed_version latest_version [release]
    LC_ALL=C join -11 -21 "$installed_file" "$latest_file" >"$joined_file"

    # Step 4: compare versions and output updates
    local updates_found=0
    local header_printed
    local pkg app installed latest

    while read -r pkg app installed latest _ ; do
        [ -n "$pkg" ] || continue
        [ -n "$installed" ] || continue
        [ -n "$latest" ] || continue

        local cmp
        cmp="$(__compare_versions "$latest" "$installed")"

        [ "$cmp" = "1" ] || continue

        updates_found=1

        if [ -z "$quiet" ] && [ -z "$short" ] && [ -z "$header_printed" ] ; then
            echo "Applications with available updates:"
            header_printed=1
        fi

        if [ -n "$short" ] ; then
            echo "$app"
            continue
        fi

        if [ -n "$quiet" ] ; then
            printf "%s %s %s\n" "$app" "$installed" "$latest"
        else
            printf "  %-25s %s -> %s\n" "$app" "$installed" "$latest"
        fi

    done < "$joined_file"

    if [ "$updates_found" = "0" ] && [ -z "$short" ] && [ -z "$quiet" ] ; then
        echo "All installed applications are up to date."
    fi

    rm -f "$installed_file" "$latest_file" "$joined_file"
}


__epm_play_install()
{
   local i RES
   RES=0

   # Handle --installed flag when passed after app name (e.g., "epm play app --installed")
   local has_installed=0
   for i in $* ; do
       [ "$i" = "--installed" ] && has_installed=1 && break
   done

   if [ "$has_installed" = "1" ] ; then
       local RES=0
       while [ -n "$1" ] ; do
           [ "$1" = "--installed" ] && break
           __is_app_installed "$1" || RES=1
           shift
       done
       return $RES
   fi

   load_helper epm-check_updated_repo

   update_repo_if_needed

   # get all options
   options=''
   for i in  $* ; do
       case "$i" in
           --*)
               options="$options $i"
               ;;
       esac
   done

   while [ -n "$1" ] ; do
       case "$1" in
           --*)
               shift
               continue
               ;;
       esac
       local p="$1"
       local v=''
       local r=''
       # drop spaces (disable glob to protect *)
       set -f
       n="$(echo $2)"
       set +f
       if [ "$n" = "=" ] ; then
           v="$3"
           # split version-release (last - separates release)
           if echo "$v" | grep -q '-' ; then
               r="${v##*-}"
               v="${v%-*}"
           fi
           shift 3
       else
           shift
       fi
       __epm_play_install_one "$p" "$v" "$r" $options || RES=1
   done

   return $RES
}

__epm_play_download_ipfs_db()
{
    local target="$1"
    # use short version (3.4.5)
    local epmver="$(epm --short --version)"
    # use baseversion
    epmver=$(echo "$epmver" | sed -e 's|\.[0-9]*$||')

    local URL
    for URL in "https://eepm.ru/releases/$epmver/app-versions" "https://eepm.ru/app-versions" ; do
        info "Updating cached IPFS DB in $target from $URL/eget-ipfs-db.txt"
        docmd eget --timestamping -q -O "$target" "$URL/eget-ipfs-db.txt" && return
    done
}


__epm_play_initialize_ipfs()
{
    if [ ! -d "$epm_vardir" ] ; then
        warning "ipfs db dir $epm_vardir does not exist, skipping IPFS mode"
        return 1
    fi

    local eget_ipfs_db_local=$eget_ipfs_db

    # ensure local writable db exists
    if [ ! -r "$eget_ipfs_db_local" ] ; then
        sudorun touch "$eget_ipfs_db_local" >&2
        sudorun chmod -v a+rw "$eget_ipfs_db_local" >&2
    fi

    if [ -n "$EPM_IPFS_DB_UPDATE_SKIPPING" ] ; then
        # skip remote db download (for the source server that generates the db)
        export EGET_IPFS_DB="$eget_ipfs_db_local"
        return
    fi

    local eget_ipfs_db_remote=$epm_vardir/eget-ipfs-db-remote.txt

    # ensure remote cached db is writable
    if [ ! -w "$eget_ipfs_db_remote" ] ; then
        sudorun touch "$eget_ipfs_db_remote" >&2
        sudorun chmod -v a+rw "$eget_ipfs_db_remote" >&2
    fi

    # download remote db with timestamping (skip if not changed)
    __epm_play_download_ipfs_db "$eget_ipfs_db_remote" || warning "Can't update IPFS DB"

    # one-time migration: remove entries already in remote from the local db
    if [ -s "$eget_ipfs_db_remote" ] && [ -s "$eget_ipfs_db_local" ] ; then
        local cleaned
        cleaned=$(grep -vxFf "$eget_ipfs_db_remote" "$eget_ipfs_db_local")
        if [ -n "$cleaned" ] ; then
            echo "$cleaned" > "$eget_ipfs_db_local"
        else
            : > "$eget_ipfs_db_local"
        fi
    fi

    # eget reads from all files, writes new entries to the last one
    export EGET_IPFS_DB="$eget_ipfs_db_remote $eget_ipfs_db_local"
}

epm_play()
{
[ "$EPMMODE" = "package" -o "$EPMMODE" = "git" ] || fatal "epm play is not supported in single file mode"
local psdir="$(realpath $CONFIGDIR/play.d)"
local prsdir="$(realpath $CONFIGDIR/prescription.d)"

if [ -z "$1" ] ; then
    [ -n "$short" ] || [ -n "$quiet" ] || echo "Available applications (for current arch $DISTRARCH):"
    __epm_play_list $psdir
    exit
fi

# allow enable ipfs in global conf
[ "$ipfs" = "--ipfs" ] && __epm_play_initialize_ipfs


while [ -n "$1" ] ; do
case "$1" in
    -h|--help)
        epm_play_help
        exit
        ;;

    --ipfs)
        shift
        [ "$ipfs" = "--ipfs" ] || __epm_play_initialize_ipfs
        ;;

    --remove)
        shift
        if [ -z "$1" ] ; then
            fatal "run --remove with 'all' or a project name"
        fi

        local list
        if [ "$1" = "all" ] ; then
            shift
            info "Retrieving list of installed apps ..."
            list="$(__list_installed_app)"
        else
            list="$*"
        fi

        __epm_play_remove $list
        exit
        ;;

    --update|--upgrade)
        shift
        local CMDUPDATE="--update"
        # check --force on common.sh side
        #[ -n "$force" ] && CMDUPDATE="--run"

        if [ -z "$1" ] ; then
            fatal "run --update with 'all' or a project name"
        fi

        local list
        if [ "$1" = "all" ] ; then
            shift
            info "Checking for available updates ..."
            list="$(short=1 __list_available_updates)"
            if [ -z "$list" ] ; then
                info "All installed applications are up to date."
                exit
            fi
            info "Apps to update: $(echo $list | wc -w)"
        else
            list="$*"
        fi

        __epm_play_update $CMDUPDATE $list
        exit
        ;;

    --installed)
        shift
        __is_app_installed "$1" "$2"
        #[ -n "$quiet" ] && exit
        exit
        ;;

    # internal options
    --installed-version|--available-version|--package-name|--product-alternatives|--info)
        __run_script "$2" "$1" "$3"
        exit
        ;;
    --list-installed-packages)
        __list_installed_packages
        exit
        ;;
    --list-all-packages)
        __list_all_packages
        exit
        ;;
    --list|--list-installed)
        __epm_play_list_installed
        exit
        ;;

    --full-list-all)
        [ -n "$short" ] || [ -n "$quiet" ] || echo "Available applications (for current arch $DISTRARCH):"
        __epm_play_list $psdir extra
        exit
        ;;

    --list-all)
        [ -n "$short" ] || [ -n "$quiet" ] || echo "Available applications (for current arch $DISTRARCH):"
        __epm_play_list $psdir
        [ -n "$quiet" ] || [ -n "$*" ] && exit
        echo
        #echo "Run epm play --help for help"
        epm_play_help
        exit
        ;;

    --search)
        shift
        [ -n "$1" ] || fatal "Search pattern is required"
        __epm_play_search "$1"
        exit
        ;;

    --list-scripts)
        [ -n "$short" ] || [ -n "$quiet" ] || echo "Run with a name of a play script to run:"
        __epm_play_list $prsdir
        exit
        ;;

    --list-updates|--list-upgradable)
        __list_available_updates
        exit
        ;;
    --latest)
        shift
        export latest="true"
        ;;
    --print-url)
        shift
        export EPM_OPTIONS="$EPM_OPTIONS --print-url"
        ;;
    -*)
        opt="$1"
        fatal 'Unknown option $opt'
        ;;
     *)
        break
        ;;
esac

done

__epm_play_install $(echo "$*" | sed -e 's|=| = |g')
}
