#!/usr/bin/env bash

# EG-Installer
# SereneTeam (c) 2019.
# Twitter: @Serene_JP
#
# Yamada Hayao
# Twitter: @Hayao0819
# Email : shun819.mail@gmail.com



#===== 各基本ウィンドウの使い方 =====#
# プログレスバー
# command | loading [横幅] [高さ] [メッセージ]
#
# エラーウィンドウ
# error [横幅] [高さ] [メッセージ]
#
# 警告ウィンドウ
# warning [横幅] [高さ] [メッセージ]
#
# 情報ウィンドウ
# info [横幅] [高さ] [メッセージ]
#
# 改行する場合は \n と記述してください。　



#-- 設定 --#
settings=/etc/eg-installer/config
entry_extension="entry"
version=1.9



#-- エラーチェック --#
set -eu



#-- 変数定義 --#
current_path=$(cd $(dirname $0) && pwd)/$(basename $0)
current_dir=$(dirname $current_path)
options=$@
unset run



#-- 関数定義 --#

function call_me () {
    export recall=true
    bash ${0} $options
}

# ウィンドウの基本型
function window () {
    zenity \
        --title="$window_text" \
        --window-icon="$window_icon" \
        $@
}

# 読み込みウィンドウ
function loading () {
    window \
        --progress \
        --auto-close \
        --pulsate \
        --width="$1" \
        --height="$2" \
        --text="$3"
}

# エラーウィンドウ
function error () {
    window \
        --error \
        --width="$1" \
        --height="$2" \
        --text="$3"
}

# 警告ウィンドウ
function warning () {
    window \
        --warning \
        --width="$1" \
        --height="$2" \
        --text="$3"
}

# 情報ウィンドウ
function info () {
    window \
        --info \
        --width="$1" \
        --height="$2" \
        --text="$3"
}

# ユーザーチェック
function user_check () {
    if [[ $(getent passwd $1 > /dev/null ; printf $?) = 0 ]]; then
        if [[ -z $1 ]]; then
            printf 1
        fi
        printf 0
    else
        printf 1
    fi
}

# 設定上の関数チェック
function check_func () {
    if [[ ! $(type -t $1) = "function" ]]; then
        error 800 100 "$2"
        exit 1
    fi
}

# パッケージチェック
function check_pkg () {
    if [[ -n $(installed_list | grep -x "$1") ]]; then
        printf 0
    else
        printf 1
    fi
}

# 値の初期化
function clear_variable () {
    unset name
    unset package_name
    unset description
    unset run_preparing
    unset preparing_msg
    unset install
    unset uninstall
    unset preparing
    unset install_check
}

# デバッグモード
function debug_msg () {
    if [[ $debug_mode = true ]]; then
        echo -e $@
    fi
}



#-- ディスプレイチェック --#
if [[ -z $DISPLAY ]]; then
    echo "GUI環境で起動してください。" >&2
    exit 1
fi



#-- 設定読み込み --#
set +eu
if [[ ! -f $settings ]]; then
    error 600 100 "$settingsが存在しません。"
    exit 1
elif [[ ! -r $settings ]]; then
    error 600 100 "$settingsの読み込みに失敗しました。\n権限を確認してください。"
fi
source $settings
if [[ -z $ID ]]; then
    source /etc/os-release
fi
set -eu



#-- アイコンチェック --#
if [[ ! -f $window_icon ]]; then
    error 600 100 "$window_iconが存在しません。"
    exit 1
elif [[ ! -r $window_icon ]]; then
    error 600 100 "$window_iconが読み込めませんでした。権限を確認してください。"
    exit 1
fi



#-- 引数用関数 --#

# ArchLinuxモードを強制的に有効化
function enable_arch () {
    export ID=arch
}

# アップデータのための情報を表示
function show_updater_info () {
    echo "${version} ${settings}"
    exit 0
}

# dpkg用のinstalled_listを有効化
function enable_installed_list_dpkg () {
    installed_list () {
        ${pacman} -Q | awk '{print $2}';
    }
    [[ ! $recall = true ]] && warning 600 100 "dpkg,apt用のinstalled_listを使用します。" > /dev/null
}

# ヘルプを表示
function show_help () {
    info 700 100 "==　デバッグ用　==\nこれはデバッグ用オプションです。通常利用はしないでください。\n$settingsを変更することで値を保存できます。\n\n-a　:　ArchLinuxモードを強制的に有効化します。\n-d　:　dpkg,apt用のinstalled_listを使用します。\n-h　:　このヘルプを表示します。このオプションが有効な場合、他のオプションは無視されます。\n-p　:　pacman用のinstalled_listを使用します。\n-v　:　バージョン情報を表示します。\n-x　:　bashでのデバッグモードを有効化します。\n-z　:　デバッグモードを有効化します。（設定されたデバッグメッセージをターミナル上に表示します。）\n-r　[　実行する項目の番号　]　:　特定の動作をメニューをスキップして行います。\n-s　[スクリプトディレクトリ]　:　スクリプトディレクトリを指定します。\n-t　[　ウィンドウタイトル　]　:　ウィンドウタイトルを指定します。\n-u　[　　　ユーザー名　　　]　:　パッケージのビルドに使用するユーザーを指定します。\n"
    exit 0
}

# pacman用のinstalled_listを有効化
function enable_installed_list_pacman () {
    installed_list () {
        ${pacman} -Q | awk '{print $1}'
    }
    [[ ! $recall = true ]] && warning 600 100 "pacman用のinstalled_listを使用します。" > /dev/null
}

# 実行する内容を直接指定
function run_execution () {
    direct_execution=true
    if [[ ! $recall = true ]]; then
        case ${OPTARG} in 
            1) run="クリーンアップ";;
            2) run="アップグレード";;
            3) run="追加と削除" ;;
            *) error 600 100 "引数${OPTARG}が間違っています。"; exit 1 ;;
        esac
    else
        exit 0
    fi
}

# bashデバッグを有効化
function enable_bash_debug () {
    set -x
    warning 600 100 "bashデバッグモードが有効化されました。"
}

# デバッグメッセージを有効化
function enable_debug () {
    debug_mode=true
    warning 600 100 "デバッグモードが有効化されました。" 
}

# バージョン情報表示
function show_version () {
    window \
        --info \
        --width="600" \
        --height="100" \
        --text="＝＝　EG-Intaler　＝＝\nVersion:　${version}\nYamada　Hayao　shun819.mail@gmail.com"
    debug_msg "EG-installer v${version}"
    exit 0
}



#-- recall判定 --#
set +eu
if [[ ! $recall = true ]]; then
    recall=false
fi
set -eu



#-- Rootチェック --#
if [[ ! $UID = 0 ]]; then
    if [[ ! -f /tmp/user || -w /tmp/user ]]; then
        echo -n 'aur_user=' > /tmp/user
        echo "$(whoami)" >> /tmp/user
    fi
    pkexec env DISPLAY=$DISPLAY XAUTHORITY=$XAUTHORITY $current_path $options> /dev/null
    exit
fi



#-- デバッグ用引数 --#
set +eu
while getopts 'adeg:hpr:s:t:u:vwxz-:' arg; do
    case "${arg}" in
        a) enable_arch;;
        d) enable_installed_list_dpkg ;;
        e) show_updater_info;;
        g) softwares_update=${OPTARG} ; info 600 100 "アップデートスクリプトが${OPTARG}に指定されました。" ;;
        h) show_help;;
        p) enable_installed_list_pacman ;;
        r) run_execution;;
        s) script_dir=${OPTARG}; warning 600 100 "スクリプトディレクトリが${OPTARG}に指定されました。" ;;
        t) window_text=${OPTARG}; warning 600 100 "ウィンドウタイトルが${OPTARG}に設定されました。" ;;
        u) aur_user=${OPTARG};;
        v) show_version;;
        w) enable_bash_debug; enable_debug;;
        x) enable_bash_debug;;
        z) enable_debug;;
        -)
            case "${OPTARG}" in
                enable-arch ) enable_arch;;
                dpkg ) enable_installed_list_dpkg;;
                update-info ) show_updater_info;;
                help ) show_help ;;
                pacman ) enable_installed_list_pacman;;
                version ) show_updater_info;;
                bash-debug ) enable_bash_debug;;
                debug ) enable_debug;;
                * ) error 600 100 "引数が無効です。"; exit 1;;
            esac
            ;;
        "") : ;;
        * ) exit 1;;
    esac
done
set -eu







#-- check_pkgについて --#
if [[ ! $(type -t installed_list) = "function" ]]; then
    if [[ -f "/usr/bin/pacman" ]]; then
        enable_installed_list_dpkg
    elif [[ -f "/usr/bin/apt-get" ]]; then
        enable_installed_list_dpkg
    else
        error 800 100 "$settingsで、installed_listをディストリビューションごとに設定してください。\nわからない場合は、ディストリビューションの配布元へ連絡してください。"
        exit 1
    fi
fi



#-- スクリプトディレクトリのチェック --#
if [[ ! -d $script_dir ]]; then
    error 600 100 "$script_dirが存在しません。"
    exit
fi



#-- pacapt --#
if [[ $ID = "arch" || $ID = "arch32" ]]; then
    pacman=pacman
else
    if [[ ! -f $pacman ]]; then
        error 600 100 "$pacmanが存在しません。"
        exit 1
    fi
    if [[ ! -x $pacman ]]; then
        chmod 755 $pacman
    fi
fi



#-- デバッグモード --#
set +eu
if [[ -z $debug_mode ]]; then
    debug_mode=false
fi
set -eu


#-- softwares-update --##
set +eu
if [[ $softwares_update = "debug" ]]; then
    if [[ -f /tmp/softwares_update ]]; then
        rm -f /tmp/softwares_update
    fi
    echo -e '#!/usr/bin/env bash\n\nzenity --warning --text="疑似ソフトウェアアップデート デバッグモード" --width="300" --height="100"' > /tmp/softwares_update
    chmod 755 /tmp/softwares_update
    softwares_update=/tmp/softwares_update
fi
set -eu



#-- AURユーザー --#
set +eu
debug_msg $ID
if [[ ! $recall = true ]]; then
    if [[ $ID = "arch" || $ID = "arch32" ]]; then
        function ask_user () {
            set -eu
            aur_user=$(window --entry --text="パッケージのビルドに使用する一般ユーザーを入力してください。")
            set +eu
            if [[ -z $aur_user ]]; then
                error 600 100 "ユーザー名を入力してください。"
                ask_user
            fi
            if [[ $aur_user = "root" ]]; then
                error 600 100 "一般ユーザーを入力してください。"
                ask_user
            fi
        }
        if [[ -n $aur_user ]]; then
            if [[ $aur_user = root ]]; then
                error 600 100 "rootは使用できません。"
                ask_user
            else
                warning 600 100 "デバッグ用引数で指定されたユーザー($aur_user)を使用します。この設定は/tmp/userに保存されます。"
            fi
        elif [[ -f /tmp/user ]]; then
            source /tmp/user
            if [[ -n $aur_user ]]; then
                info 600 100 "/tmp/userに保存されているユーザー($aur_user)を使用します。"
            else
                ask_user
            fi
            [[ -z $aur_user ]] && ask_user
        elif [[ ! $SUDO_USER = root && -n $SUDO_USER ]]; then
            aur_user=$SUDO_USER
            info 600 100 "sudoで使用されていたユーザー($aur_user)を使用します。この設定は/tmp/userに保存されます。"
        else
            ask_user
        fi
        while [ $(user_check $aur_user) = 1 ]; do
            error 600 100 "指定されたユーザー($aur_user)は正しくありません。"
            ask_user
        done
        if [[ -f /tmp/user ]]; then
            rm -f /tmp/user
        fi
        echo -n 'aur_user=' > /tmp/user
        echo "$aur_user" >> /tmp/user
        export aur_user=$aur_user
    fi
fi
set -eu



#-- クリーンアップ --#
function cleanup () {
    $pacman -Scc --noconfirm | loading 600 100 "クリーンアップを実行中です。"
    if [[ $ID = "arch" || $ID = "arch32" ]]; then
        if [[ -n $(pacman -Qttdq) ]]; then
            $pacman -Qttdq | $pacman -Rsnc - | loading 600 100 "クリーンアップを実行中です。"
        fi
    else
        $pacman -Rsn --noconfirm | loading 600 100 "クリーンアップを実行中です"
    fi
}



#-- データベースのアップデート --#
function update_db () {
    $pacman -Syy --noconfirm
}



#-- パッケージのアップグレード --#
function upgrade_pkg () {
    $pacman -Syu --noconfirm
}



#-- インストールとアンインストール --#
function install_and_uninstall () {

    # スクリプトのアップデート処理
    set + eu
    if [[ -n $softwares_update && -f $softwares_update && -x $softwares_update ]]; then
        $softwares_update | loading 600 100 "スクリプトを更新しています。"
    fi
    set -eu

    # スクリプト読み込み
    scripts=($(cd $script_dir; ls *.${entry_extension}; cd ..))
    for package in ${scripts[@]}; do
        source $script_dir/$package

        function check_func () {
            set +eu
            if [[ ! $(type -t $1) = "function" ]]; then
                error 600 100 "スクリプト$packageの$1関数が間違っています。"
                exit 1
            fi
            set -eu
        }
        function check_variable () {
            set +eu
            eval variable=$1
            if [[ -z $variable ]]; then
                error 600 100 "スクリプト$packageに$variable変数が間違っています。"
                exit 1
            fi
            set -eu
        }

        check_variable name
        set +eu
        if [[ ! $install_check = false ]]; then
            check_variable package_name
            check_func uninstall
        fi
        set -eu
        check_variable description
        check_variable run_preparing
        check_func install
        if $run_preparing; then
            check_func preparing
        fi
        clear_variable
    done



    # リスト

    window \
        --warning \
        --width="600" \
        --height="100" \
        --text="スクリプトの読み込みを行います。これにはしばらく時間がかかる場合があります。\nしばらくたっても表示されない場合はターミナル上でスクリプトを実行してみてください。" \
        --ok-label="読み込み開始"

    gen_list () {
        window \
            --list \
            --checklist \
            --column="選択" \
            --column="パッケージ" \
            --column="インストール済" \
            --column="説明" \
            --width="900" \
            --height="500" \
            --text="インストールまたは削除したいパッケージを選択してください。" \
            $(
                scripts=($(cd $script_dir; ls *.${entry_extension}; cd ..))
                for package in ${scripts[@]}; do
                    source $script_dir/$package
                    set +eu
                    if [[ $install_check = false ]]; then
                        status_display="None"
                    elif [[ $(check_pkg $package_name) = 0 ]]; then
                        status_display="はい"
                    else
                        status_display="いいえ"
                    fi
                    set -eu
                    echo "FALSE"
                    echo "$name"
                    echo "$status_display"
                    echo "$description"
                    clear_variable
                done
            )
    }

    selected_list=$(gen_list; exit_code=$?)
    selected_list=(${selected_list//'|'/ })
    set +eu
    if [[ ! $exit_code = 0 && -z $selected_list ]]; then
        error 600 100 "パッケージが選択されませんでした。$( 
            if [[ ! $direct_execution = "true" ]]; then 
                echo 'トップに戻ります。'
            else
                echo '終了します。'
            fi
        )"
        call_me $options
        exit
    fi
    set -eu


    # データベースの更新
    update_db | loading 600 100 "リポジトリデータベースを更新しています。"



    # 実行

    for selected in ${selected_list[@]}; do
        # 選択パッケージに対応しているファイルを探す
        scripts=($(cd $script_dir; ls *.${entry_extension}; cd ..))
        for package in ${scripts[@]}; do
            set name
            set description
            set preparing
            set install

            source $script_dir/$package
            if [[ $name = $selected ]]; then
                break
            fi
            clear_variable
        done

        # インストール or アンインストール
        source $script_dir/$package
        set +eu
        if [[ $install_check = false ]]; then
            set -eu
            window \
                --question \
                --text="スクリプト$nameを適用します。よろしいですか？\nこのスクリプトは適用済、未適用を範囲していません。複数回の適用は正常に動作しない可能性がありますので注意してください。" \
                --ok-label="続行する" \
                --cancel-label="中断する" \
                --width=600 \
                --height=100
            if $run_preparing; then
                preparing | loading 600 100 "パッケージをビルドしています"
            fi
            install | loading 600 100 "パッケージ$nameをインストールしています"
            set +eu
        else
            if [[ $(check_pkg $package_name) = 1 ]]; then
                set -eu
                window \
                    --question \
                    --text="パッケージ$nameをインストールします。よろしいですか？" \
                    --ok-label="続行する" \
                    --cancel-label="中断する" \
                    --width=600 \
                    --height=100
                if $run_preparing; then
                    if [[ -n $preparing_msg ]]; then
                        preparing | loading 600 100 $preparing_msg
                    else
                        preparing | loading 600 100 "パッケージをビルドしています"
                    fi
                fi
                install | loading 600 100 "パッケージ$nameをインストールしています"
                set +eu
            else
                set -eu
                window \
                    --question \
                    --text="パッケージ$nameをアンインストールします。よろしいですか？" \
                    --ok-label="続行する" \
                    --cancel-label="中断する" \
                    --width=600 \
                    --height=100
                uninstall | loading 600 100 "パッケージ$nameをアンインストールしています。"
                set +eu
            fi
        fi
        set -eu
    done
    info 600 100 "処理が完了しました。\n詳細はターミナルを参照してください。"
}



#-- 実行 --#
set +eu
unset exit_code

# メニュー
if [[ ! $direct_execution = true ]]; then
    run=$(
        window \
            --info \
            --text="何を実行しますか？\n\n終了・・・終了します$( if [[ $ID = "arch" || $ID = "arch32" ]]; then echo "\nAUR情報を削除・・・保存されているAURのユーザー情報を削除します。"; fi )$( if [[ -n $softwares_update && -f $softwares_update && -x $softwares_update ]]; then echo "\n一覧をアップデート・・・「追加と削除」で選択できる項目をアップデートします。"; fi )\nクリーンアップ・・・パッケージのクリーンアップを行います。\nアップグレード・・・パッケージのアップグレードを行います\n追加と削除・・・パッケージのインストールやアンインストールを行います。 " \
            --ok-label="終了する" \
            $(
                # ArchLinux用メニュー
                if [[ $ID = "arch" || $ID = "arch32" ]]; then
                    echo '--extra-button=AUR情報を削除'
                fi
            ) \
            $(
                # スクリプトのアップデート
                if [[ -n $softwares_update && -f $softwares_update && -x $softwares_update ]]; then
                # if true; then
                    echo "--extra-button=一覧をアップデート"
                fi
            ) \
            --extra-button="クリーンアップ" \
            --extra-button="アップグレード" \
            --extra-button="追加と削除" \
            --width="400" \
            --height="120"
    )
    exit_code=$?
    case $exit_code in
                0 ) exit 0 ;;
                * ) :;;
    esac
fi


debug_msg "run=$run"
debug_msg "exit_code=$exit_code"
case $run in
    "追加と削除" ) install_and_uninstall ;;
    "アップグレード" ) upgrade_pkg | loading 600 100 "パッケージのアップグレードを行っています。" ;;
    "AUR情報を削除" ) rm -f /tmp/user ; info 600 100 "保存されているユーザーを削除しました" ; exit 0;;
    "クリーンアップ" ) cleanup ;;
    "一覧をアップデート" ) $softwares_update | loading 600 100 "スクリプトを更新しています。";;
    * ) error 600 100 "例外が発生しました。"; exit 1;;
esac
set -eu



#-- 最初に戻る --#
call_me $options
