#!/bin/bash

##############################################################################
# This script allows you to easily manage Platypush instances through Python #
# virtual environment. You can build environments from a config.yaml file    #
# and automatically managed the required dependencies, as well as start,     #
# stop and remove them                                                       #
#                                                                            #
# @author: Fabio Manganiello <fabio@platypush.tech>                          #
# @licence: MIT                                                              #
##############################################################################


workdir="$HOME/.local/share/platypush/venv"

function build {
    cfgfile=

    while getopts ':c:' opt; do
        case ${opt} in
            c)
                cfgfile=$OPTARG;;
            \?)
                echo "Invalid option: -$OPTARG" >&2
                exit 1;;
            :)
                echo "Option -$OPTARG requires the path to a Platypush configuration file" >&2
                exit 1;;
        esac
    done

    if [[ -z "$cfgfile" ]]; then
        echo "Usage: $0 build -c <path-to-platypush-config-file>" >&2
        exit 1
    fi

    echo "Parsing configuration file"
    pip_cmd=
    pkg_cmd=
    includes=()
    cmd_exec=()

    while read -r line; do
      if echo "$line" | grep -E "^pip:\s*"; then
        pip_cmd="$(echo "$line" | sed -r -e 's/^pip:\s*(.*)'/\\1/)"
      elif echo "$line" | grep -E "^packages:\s*"; then
        pkg_cmd="$(echo "$line" | sed -r -e 's/^packages:\s*(.*)'/\\1/)"
      elif echo "$line" | grep -E "^exec:\s*"; then
        cmd_exec+=("$(echo "$line" | sed -r -e 's/^exec:\s*(.*)'/\\1/)")
      elif echo "$line" | grep -E "^include:\s*"; then
        includes+=("$(echo "$line" | sed -r -e 's/^include:\s*(.*)'/\\1/)")
      elif echo "$line" | grep -E "^device_id:\s*"; then
        device_id="$(echo "$line" | sed -r -e 's/^device_id:\s*(.*)'/\\1/)"
      fi
    done <<< "$(python <<EOF
from platypush.config import Config
from platypush.utils.manifest import get_install_commands_from_conf

deps = get_install_commands_from_conf('$(realpath "${cfgfile}")')
print(f'device_id: {Config.get("device_id")}')

if deps.get('pip'):
    print(f'pip: {deps["pip"]}')

if deps.get('packages'):
    print(f'packages: {deps["packages"]}')

for cmd in deps.get('exec', []):
    print(f'exec: {cmd}')

for include in Config._included_files:
    print(f'include: {include}')
EOF
)"

    envdir="${workdir}/${device_id}"
    etcdir="${envdir}/etc/platypush"

    echo "Preparing virtual environment for device $device_id"
    mkdir -p "$envdir"
    mkdir -p "$etcdir"
    srcdir=$(dirname "$cfgfile")

    for ((i=0; i < ${#includes[@]}; i++)); do
        incdir=$(dirname "${includes[$i]}")
        incdir=$(realpath --relative-to="$srcdir" "$incdir")
        destdir="$etcdir/$incdir"
        mkdir -p "$destdir"
        cp "${includes[$i]}" "$destdir"
    done

    cp "$cfgfile" "$etcdir/config.yaml"
    cfgfile="${etcdir}/config.yaml"

    python3 -m venv "${envdir}"
    cd "${envdir}" || exit 1
    source "${envdir}/bin/activate"

    echo "Installing required dependencies"
    # shellcheck disable=SC2086
    [ -n "${pkg_cmd}" ] && sudo ${pkg_cmd}
    [ -n "${pip_cmd}" ] && ${pip_cmd}

    for ((i=0; i < ${#cmd_exec[@]}; i++)); do
        ${cmd_exec[$i]}
    done

    pip install --upgrade git+https://git.platypush.tech/platypush/platypush.git
    echo "Platypush virtual environment prepared under $envdir"
}

function start {
    if [[ -z "$1" ]]; then
        echo "Usage: $0 start <env-name>" >&2
        exit 1
    fi

    env=$1
    envdir="${workdir}/${env}"
    rundir="${envdir}/var/run"
    pidfile="${rundir}/platypush.pid"
    cfgfile="${envdir}/etc/platypush/config.yaml"

    if [[ ! -d "$envdir" ]]; then
        echo "No such directory: $envdir" >&2
        exit 1
    fi

    mkdir -p "${rundir}"

    if [[ -f "$pidfile" ]]; then
        if pgrep -F "${pidfile}"; then
            echo "Another instance (PID $(cat "${pidfile}")) is running, please stop that instance first"
            exit 1
        fi

        echo "A PID file was found but the process does not seem to be running, starting anyway"
        rm -f "$pidfile"
    fi

    python3 -m venv "${envdir}"
    cd "${envdir}" || exit 1
    source bin/activate
    bin/platypush -c "$cfgfile" -P "$pidfile" &
    start_time=$(date +'%s')
    timeout=30

    while :; do
        [[ -f "$pidfile" ]] && break
        now=$(date +'%s')
        elapsed=$(( now-start_time ))
        if (( elapsed >= timeout )); then
            echo "Platypush instance '$env' did not start within $timeout seconds" >&2
            exit 1
        fi

        echo -n '.'
        sleep 1
    done

    pid=$(cat "$pidfile")
    echo
    echo "Platypush environment $env started with PID $pid"
    wait "${pid}"
    echo "Platypush environment $env terminated"
}

function stop {
    if [[ -z "$1" ]]; then
        echo "Usage: $0 stop <env-name>" >&2
        exit 1
    fi

    env=$1
    envdir="${workdir}/${env}"
    rundir="${envdir}/var/run"
    pidfile="${rundir}/platypush.pid"

    if [[ ! -d "$envdir" ]]; then
        echo "No such directory: $envdir" >&2
        exit 1
    fi

    if [[ ! -f "$pidfile" ]]; then
        echo "No pidfile found for instance \"${env}\""
        exit 1
    fi

    pid=$(cat "$pidfile")
    pids="$pid $(ps --no-headers -o pid= --ppid "$pid")"
    # shellcheck disable=SC2086
    kill -9 ${pids}
    rm -f "$pidfile"
    echo "Instance '$env' with PID $pid stopped"
}

function rme {
    if [[ -z "$1" ]]; then
        echo "Usage: $0 rm <env-name>" >&2
        exit 1
    fi

    envdir="${workdir}/$1"
    rundir="${envdir}/var/run"
    pidfile="${rundir}/platypush.pid"

    if [[ ! -d "$envdir" ]]; then
        echo "No such directory: $envdir" >&2
        exit 1
    fi

    if [[ -f "$pidfile" ]]; then
        if pgrep -F "${pidfile}"; then
            echo "Another instance (PID $(cat "$pidfile")) is running, please stop that instance first"
            exit 1
        fi

        echo "A PID file was found but the process does not seem to be running, removing anyway"
    fi

    echo "WARNING: This operation will permanently remove the Platypush environment $1"
    echo -n "Are you sure you want to continue? (y/N) "
    IFS= read -r answer
    echo "$answer" | grep -E '^[yY]' >/dev/null || exit 0
    rm -rf "$envdir"
    echo "$envdir removed"
}

function usage {
    echo "Usage: $0 <build|start|stop|rm> [options]" >&2
    exit 1
}

if (( $# < 1 )); then
    usage
fi

action=$1
shift
mkdir -p "${workdir}"

# shellcheck disable=SC2048,SC2086
case ${action} in
    'build') build $*;;
    'start') start $*;;
    'stop') stop $*;;
    'rm') rme $*;;
    *) usage;;
esac
