#!/usr/bin/env bash

running=0

_check_running() {
    if [ $running -ne 0 ]; then
        _unlock
        exit $running
    fi
}

_onkill() {
    running=1
}

_lock() {
    if [ -f ~/.config/dotfiles/.data/update_lock ]; then
        return 1
    fi

    touch ~/.config/dotfiles/.data/update_lock
}

_lock_or_error_message() {
    _lock
    local value=$?

    if [ $value -ne 0 ]; then
        echo -e "\033[31;1merror:\033[0m update seems to be already running"
        echo -e "\033[1minfo:\033[0m if you're sure it's not running, run \`update force-unlock\`"
        return $value
    fi
}

_unlock() {
    rm -rf ~/.config/dotfiles/.data/update_lock
}

_check_date_if_format() {
    local format=`_date_format`

    if [ "$format" != "" ]; then
        _check_date $format $1
    fi
}

_date_format() {

    case $UPDATE_CHECK in
        "daily") echo +%d/%m/%Y;;
        "weekly") echo +%V/%Y;;
        "monthly") echo +%m/%Y;;
    esac

}

_check_date_file() {
    mkdir -p ~/.config/dotfiles/.data
    touch ~/.config/dotfiles/.data/update_date
}

_sentence() {
    subject0=("Your" "The" "This")
    subject1=("system" "machine" "pc" "computer")
    adjective=("awesome" "incredible" "amazing" "brave" "hard working" "loyal" "nice" "polite" "powerful" "pro-active" "reliable" "fabulous" "fantastic" "incredible" "outstanding" "remarkable" "spectacular" "splendid" "super" "happy" "cheerful")
    verb=("is" "seems" "looks" "appears to be")
    no_verb=("is not" "doesn't seem" "doesn't look" "appears not to be")
    adverb=("up-to-date" "ready" "updated")
    dot=("." "!" "...")

    n_subject0=`shuf -i0-"$((${#subject0[@]}-1))" -n1`
    n_subject1=`shuf -i0-"$((${#subject1[@]}-1))" -n1`
    n_adjective=`shuf -i0-"$((${#adjective[@]}-1))" -n1`
    n_adverb=`shuf -i0-"$((${#adverb[@]}-1))" -n1`
    n_dot=`shuf -i0-"$((${#dot[@]}-1))" -n1`

    if [ $1 = "updated" ];  then
        color="32"
        n_verb=`shuf -i0-"$((${#verb[@]}-1))" -n1`
        s_verb=${verb[$n_verb]}
        end=""
    elif [ $1 = "not_updated" ]; then
        color="31"
        n_verb=`shuf -i0-"$((${#no_verb[@]}-1))" -n1`
        s_verb=${no_verb[$n_verb]}
        end="Run \`update\` to update your system."
    fi

    echo -e "\033[$color;1m${subject0[$n_subject0]} ${adjective[$n_adjective]} ${subject1[$n_subject1]} $s_verb ${adverb[$n_adverb]}${dot[$n_dot]} ${end}\033[0m"
}

_check_date() {

    _check_date_file

    old_date=`cat ~/.config/dotfiles/.data/update_date`
    new_date=`date $1`

    if [ "$new_date" != "$old_date" ]; then
        _sentence not_updated
    elif [ "$UPDATE_CHECK_ALWAYS" = "true" ] || [ "$2" = "force" ]; then
        _sentence updated
    fi
}

update-system() {

    if [ -f ~/.config/dotfiles/.data/noroot ]; then
        # If this file exists, it means root is not available: skip the system update
        echo -e "\033[33;1m=== You don't have root, skipping system update ===\033[0m"
        return
    fi

    echo -e "\033[32;1m=== Starting system update, please enter your password ===\033[0m"
    sudo true
    if [ $? -ne 0 ]; then
        echo "Could not get sudo..."
        return 1
    fi

    echo -e "\033[32;1m=== Updating system ===\033[0m"
    start_system_update=`date +%s`

    # Debian based systems
    command -v apt > /dev/null 2>&1
    if [ $? -eq 0 ]; then
        sudo apt update -y
        if [ $? -eq 0 ]; then
            sudo apt upgrade -y
            if [ $? -eq 0 ]; then
                sudo apt autoremove -y
            fi
        fi
    fi

    # Archlinux based systems
    command -v yay > /dev/null 2>&1

    if [ $? -eq 0 ]; then
        yay -Syu --noconfirm
        yay -Syua --noconfirm
    else
        command -v pacman > /dev/null 2>&1

        if [ $? -eq 0 ]; then
            sudo pacman -Syu --noconfirm
        fi
    fi

    # Fedora based systems
    command -v dnf > /dev/null 2>&1
    if [ $? -eq 0 ]; then
        sudo dnf upgrade
    fi

    seconds=$((`date +%s` - $start_system_update ))
    formatted=`date -ud "@$seconds" +'%H hours %M minutes %S seconds'`
    echo -e "\033[32;1m=== System updated in $formatted ===\033[0m"
}

update-rust() {

    # Update rust if installed
    command -v rustup > /dev/null 2>&1
    if [ $? -ne 0 ]; then
        return
    fi

    start_rust_update=`date +%s`
    echo -e "\033[32;1m=== Updating rustup ===\033[0m"
    rustup self update

    echo -e "\033[32;1m=== Updating rust ===\033[0m"
    rustup update

    cargo install-update --help > /dev/null 2>&1

    # Program to update cargo programs is not installed
    if [ $? -ne 0 ]; then

        # This program requires openssl, so check that it is installed
        pkg-config --libs --cflags openssl > /dev/null 2>&1
        installed_openssl=$?

        # We need to install openssl
        if [ $installed_openssl -ne 0 ]; then

            # If the user has root power
            if [ ! -f ~/.config/dotfiles/.data/noroot ]; then

                echo -e "\033[32;1m=== libssl-dev is needed to update rust packages ===\033[0m"
                sudo true

                if [ $? -eq 0 ]; then

                    # For ubuntu
                    command -v apt > /dev/null 2>&1
                    if [ $? -eq 0 ]; then
                        sudo apt install -y libssl-dev
                        installed_openssl=0
                    fi

                    # For fedora
                    command -v dnf > /dev/null 2>&1
                    if [ $? -eq 0 ]; then
                        sudo dnf install openssl-devel
                        installed_openssl=0
                    fi

                fi

            fi
        fi

        # Only try to install if openssl is installed
        if [ $installed_openssl -eq 0 ]; then
            echo -e "\033[32;1m=== Installing rust packages updater ===\033[0m"
            cargo install cargo-update
        fi
    fi

    # If the user has no root, and if he doesn't have openssl, we won't install cargo update.
    # Before running cargo update, we check again that it is installed.
    cargo install-update --help > /dev/null 2>&1
    if [ $? -eq 0 ]; then
        echo -e "\033[32;1m=== Updating rust packages ===\033[0m"
        cargo install-update -ag
    fi

    seconds=$((`date +%s` - $start_rust_update ))
    formatted=`date -ud "@$seconds" +'%H hours %M minutes %S seconds'`
    echo -e "\033[32;1m=== Rust updated in $formatted ===\033[0m"

}

update-wasm() {
    command -v wasmer > /dev/null 2>&1
    if [ $? -ne 0 ]; then
        return
    fi

    start_wasm_update=`date +%s`

    wasmer self-update

    seconds=$((`date +%s` - $start_wasm_update ))
    formatted=`date -ud "@$seconds" +'%H hours %M minutes %S seconds'`
    echo -e "\033[32;1m=== Wasm updated in $formatted ===\033[0m"
}

update-npm() {

    # Update node packages if installed
    command -v npm > /dev/null 2>&1
    if [ $? -ne 0 ]; then
        return
    fi

    if [ ! -d ~/.npmbin ]; then
        return
    fi

    start_npm_update=`date +%s`
    echo -e "\033[32;1m=== Updating node packages ===\033[0m"

    for package in $(npm -g outdated --parseable --depth=0 | cut -d: -f3)
    do
        npm -g install "$package"
    done

    seconds=$((`date +%s` - $start_npm_update ))
    formatted=`date -ud "@$seconds" +'%H hours %M minutes %S seconds'`
    echo -e "\033[32;1m=== Node packages updated in $formatted ===\033[0m"
}

update-dotfiles() {
    start_dotfiles_update=`date +%s`

    current_dir=$PWD

    echo -e "\033[32;1m=== Updating dotfiles ===\033[0m"
    cd ~/.config/dotfiles && git pull

    if [ -d ~/.config/oh-my-zsh ]; then
        echo -e "\033[32;1m=== Updating oh-my-zsh ===\033[0m"
        cd ~/.config/oh-my-zsh && git pull
    fi

    if [ -d ~/.config/awesome/.git ]; then
        echo -e "\033[32;1m=== Updating awesome config ===\033[0m"
        cd ~/.config/awesome/ && git pull
    fi

    cd $current_dir

    seconds=$((`date +%s` - $start_dotfiles_update ))
    formatted=`date -ud "@$seconds" +'%H hours %M minutes %S seconds'`
    echo -e "\033[32;1m=== Dotfiles updated in $formatted ===\033[0m"
}

update-neovim() {
    command -v nvim > /dev/null 2>&1
    if [ $? -ne 0 ]; then
        return
    fi

    start_neovim_update=`date +%s`
    echo -e "\033[32;1m=== Updating neovim packages ===\033[0m"

    nvim +PlugUpdate +qall

    seconds=$((`date +%s` - $start_neovim_update ))
    formatted=`date -ud "@$seconds" +'%H hours %M minutes %S seconds'`
    echo -e "\033[32;1m=== Neovim updated in $formatted ===\033[0m"
}

update-postpone() {
    local format=`_date_format`

    if [ $? -eq 0 ]; then
        _check_date_file
        date $format > ~/.config/dotfiles/.data/update_date
    fi
}

_print_help() {
    echo -e "\033[32mupdate\033[0m"
    echo -e "Thomas Forgione <thomas@forgione.fr>"
    echo -e "A script that automatically updates your system"
    echo
   _print_usage
}

_print_usage() {
    echo -e "\033[33mUSAGE:\033[0m"
    echo -e "    update <subcommand>"
    echo
    echo -e "\033[33mSUBCOMMANDS:\033[0m"
    echo -e "    \033[32msystem\033[0m          Updates the system (Debian, Archlinux, Fedora)"
    echo -e "    \033[32mrust\033[0m            Updates rust and rust packages (requires rustup)"
    echo -e "    \033[32mwasm\033[0m            Updates wasmer"
    echo -e "    \033[32mnpm\033[0m             Updates npm packages (in ~/.config/npmbin)"
    echo -e "    \033[32mdotfiles\033[0m        Updates the dotfiles"
    echo -e "    \033[32mneovim\033[0m          Updates the neovim packages"
    echo -e "    \033[32mcheck\033[0m           Checks whether the system has been recently updated"
    echo -e "    \033[32mstartup\033[0m         Same as \`update check\` but is silent in case of success"
    echo -e "    \033[32mpostpone\033[0m        Skip the current update (disable check warnings)"
    echo -e "    \033[32mforce-unlock\033[0m    Deletes the lock file"
}

partial-test() {
    case $1 in
        "system") return 0;;
        "rust") return 0;;
        "wasm") return 0;;
        "npm") return 0;;
        "dotfiles") return 0;;
        "neovim") return 0;;
        "check") return 0;;
        "startup") return 0;;
        "postpone") return 0;;
        "force-unlock") return 0;;
        *) return 1;;
    esac
}

partial-requires-lock() {
    case $1 in
        "system") return 0;;
        "rust") return 0;;
        "wasm") return 0;;
        "npm") return 0;;
        "dotfiles") return 0;;
        "neovim") return 0;;
        "check") return 1;;
        "startup") return 1;;
        "postpone") return 1;;
        "force-unlock") return 1;;
        *) return 1;;
    esac
}

partial-update() {
    case $1 in
        "system") update-system;;
        "rust") update-rust;;
        "wasm") update-wasm;;
        "npm") update-npm;;
        "dotfiles") update-dotfiles;;
        "neovim") update-neovim;;
        "check") _check_date_if_format force;;
        "startup") _check_date_if_format;;
        "postpone") update-postpone;;
        "force-unlock") _unlock;;
        *) return 1
    esac
}

main() {

    if [ $# -eq 0 ]; then
        _lock_or_error_message
        if [ $? -ne 0 ]; then
            return $?
        fi

        start=`date +%s`
        echo -e "\033[32;1m=== Starting full update ===\033[0m"

        # Update the system
        _check_running
        update-system

        # Update rust and rust packages
        _check_running
        update-rust

        # Update wasmer
        _check_running
        update-wasm

        # Update npm and npm packages
        _check_running
        update-npm

        # Update the dotfiles
        _check_running
        update-dotfiles

        # Update the neovim packages
        _check_running
        update-neovim

        _check_running
        update-postpone

        _check_running
        seconds=$((`date +%s` - $start ))
        formatted=`date -ud "@$seconds" +'%H hours %M minutes %S seconds'`
        echo -e "\033[32;1m=== Update finished in $formatted ===\033[0m"

        _check_running
        _check_date_if_format force

        _check_running
        _unlock

    else

        local lock_required=1

        for part in $@; do

            if [ $part == "-h" ] || [ $part == "--help" ]; then
                _print_help
                exit 0
            fi

            partial-test $part
            if [ $? -ne 0 ]; then
                echo -e "\033[1;31merror:\033[0m unrocognized update command \"$part\""
                _print_usage
                return 1
            fi

            partial-requires-lock $part
            lock_required=$(($lock_required * $?))
        done

        if [ $lock_required -eq 0 ]; then
            _lock_or_error_message
            if [ $? -ne 0 ]; then
                return $?
            fi
        fi

        for part in $@; do
            _check_running
            partial-update $part
        done

        if [ $lock_required -eq 0 ]; then
            _unlock
        fi
    fi
}

trap _onkill 2 3
main $@