#!/bin/bash

_debug() {
    if [ -n "${DEBUG}" ]; then
        echo "DEBUG:   $1" >&2
        shift
        for text in "$@"; do
            echo "         ${text}" >&2
        done
    fi
}

_error() {
    echo "ERROR:   $1" >&2
    shift
    for text in "$@"; do
        echo "         ${text}" >&2
    done
}

findjava() {
    # We try hard to find the proper 'java' command
    local JAVA_FOUND=0
    if [ -n "${FLEXIBEE_JAVA}" ] && [ -x "${FLEXIBEE_JAVA}" ]; then
        JAVA_FOUND=1
        JAVACMD="${FLEXIBEE_JAVA}"
        _debug "Using \$FLEXIBEE_JAVA to find java virtual machine."
    else
        for javaPath in /usr/lib/jvm/{java,temurin}-11-*/{,jre/}bin/java; do
            if [ -x "$javaPath" ]; then
                JAVA_FOUND=1
                JAVACMD=$javaPath
                _debug "Using \$javaPath to find java virtual machine."
                break
            fi
        done
    fi
    if [ "$JAVA_FOUND" = 0 ]; then
        if [ -n "${JAVACMD}" ] && [ -x "${JAVACMD}" ]; then
            _debug "Using \$JAVACMD to find java virtual machine."
        elif [ -n "${JAVA_BINDIR}" ] && [ -x "${JAVA_BINDIR}/java" ]; then
            JAVACMD="${JAVA_BINDIR}/java"
            _debug "Using \$JAVA_BINDIR to find java virtual machine."
        elif [ -n "${JAVA_HOME}" ] && [ -x "${JAVA_HOME}/bin/java" ]; then
            JAVACMD="${JAVA_HOME}/bin/java"
            _debug "Using \$JAVA_HOME to find java virtual machine."
        else
            JAVACMD=$(which java)
            if [ -n "${JAVACMD}" ] && [ -x "${JAVACMD}" ]; then
                _debug "Using \$PATH to find java virtual machine."
            elif [ -x /usr/bin/java ]; then
                _debug "Using /usr/bin/java to find java virtual machine."
                JAVACMD=/usr/bin/java
            fi
        fi
    fi

    # if we were successful, we return 0 else we complain and return 1
    if [ -n "${JAVACMD}" ] && [ -x "${JAVACMD}" ]; then
        _debug "Using '$JAVACMD' as java virtual machine..."
        return 0
    else
        _error "Couldn't find a java virtual machine," \
               "define JAVACMD, JAVA_BINDIR, JAVA_HOME or PATH."
        return 1
    fi
}

_source() {
    if [ -f "$1" ]; then
        _debug "Sourcing '$1'."
        . "$1"
    fi
}

_quit() {
    echo "$@"
    exit 1
}

MYLANG=$(echo "$LANG" | cut -c 1-2)

echoLocalizedErr() {
    echo

    if which tput &> /dev/null; then
        echoLocalizedMsg "$(boldText "$1")" "$(boldText "$2")"
    else
        echoLocalizedMsg "    $1" "    $2"
    fi

    echo
}

boldText() {
    echo "$(tput bold)$1$(tput sgr0)"
}

echoLocalizedMsg() {
    if [ "x$MYLANG" = "xcs" ]; then
        echo "$1"
    else
        echo "$2"
    fi
}

upgradePostgreSQL() {
    local VERSION=$1
    local OLD_PORT=$2
    local NEW_PORT=$3
    local OLD_VERSION=$4
    local NEW_VERSION=$5
    local LIB_DIR=/usr/share/flexibee/lib

    checkDatabaseEmpty "$NEW_PORT"

    for i in "$LIB_DIR"/{postgres-updater,winstrom-sql,winstrom-server,winstrom-connector}-"$VERSION".jar\
        "$LIB_DIR"/{postgresql,slf4j-api}-*.jar; do
        UPDATER_CP=$UPDATER_CP:$i
    done

    if $JAVACMD -cp "$UPDATER_CP" -Dfile.encoding=UTF-8 cz.winstrom.postgresupdater.HeadlessUpdate \
    "$OLD_PORT" "$NEW_PORT" "$OLD_VERSION" "$NEW_VERSION" > /dev/null; then
        echoLocalizedMsg "Upgrade databáze úspěšně dokončen." "Database upgrade completed successfully."
        echo
    else
        replacePgPortInFlexiConfig "$NEW_PORT" "$OLD_PORT"
        echoLocalizedErr "Upgrade databáze selhal!" "Failed to upgrade database!"
        exit 1
    fi
}

checkDatabaseEmpty() {
    local PORT=$1

    NUM_USERS=$(/bin/su - postgres -c "/usr/bin/psql -p $PORT -d postgres  \
        -Atc \"select count(usename) from pg_user where usename not in ('dba', 'postgres')\"") || NUM_USERS=0

    if [ "$NUM_USERS" != "0" ]; then
        echoLocalizedErr \
            'PostgreSQL databáze na portu '"$PORT"' již obsahuje data, která mohou být pozůstatkem předchozí'\
' nedokončené aktualizace. Pokud aktualizace selže, bude nutné data odstranit.' \
            'The PostgreSQL database on port '"$PORT"' already contains data that may be a remnant of a previous'\
' unfinished update. If the update fails, the data will need to be deleted.'
    fi
}

checkVersionAvailable() {
    local VERSION=$1

    local PATH1="/usr/lib/postgresql/"
    local PATH2="/usr/pgsql-"
    local SUFFIX=bin/postgres

    if [ -f $PATH1"$VERSION"/"$SUFFIX" ] || [ -f $PATH2"$VERSION"/"$SUFFIX" ]; then
        [[ "$DBVERSION" = "0" ]] && DBVERSION=$VERSION

        return 0
    fi

    return 1
}

getPgPortFromFlexiConfig() {
    getEntryFromFlexiConfig port "\d+"
}

getPgPasswordFromFlexiConfig() {
    getEntryFromFlexiConfig password ".*"
}

getEntryFromFlexiConfig() {
    local ENTRY=$1
    local TYPE=$2

    # ignore XML comments in server config
    sed -e '/<!--/{
:loop
    /-->/{
        s/-->/#commentEND#/
        s/<!--.*#commentEND#//
        /<!--/b loop
        b done
    }
    N
    b loop
:done
}' /etc/flexibee/flexibee-server.xml | grep -Po "(?<=<entry key=\"$ENTRY\">)$TYPE(?=<\/entry>)"
}

replacePgPortInFlexiConfig() {
    local OLD_PORT=$1
    local NEW_PORT=$2
    sed -i -E "s/(<entry key=\"port\">)$OLD_PORT/\1$NEW_PORT/g" /etc/flexibee/flexibee-server.xml
}

getPgPortFromPgConfig() {
    local PORT
    PORT=$(grep -Po "^\s*port\s*=\s*\K\d+" "$1" | tail -1)

    if [ -z "$PORT" ]; then
        # Port was not found in config file. We will use default PostgreSQL port.
        PORT=5432
    fi

    echo "$PORT"
}

isPortInPgConf() {
    local CONF=$1
    local PORT=$2

    if [ ! -f "$CONF" ]; then
        return 1
    fi

    local PORT_IN_CONF
    PORT_IN_CONF=$(getPgPortFromPgConfig "$CONF")

    if [ "$PORT_IN_CONF" = "$PORT" ]; then
        return 0
    else
        return 1
    fi
}

getServiceName() {
    echo "${SERVICE_NAME//VER/$1}"
}

isSupportedVersion() {
    if printf '%s\0' "${SUPPORTEDVERSIONS[@]}" | grep -Fxqz -- "$1"; then
        return 0
    fi

    return 1
}

serviceAction() {
    checkDetectCalled

    local ACTION=$1
    local VERBOSE=$2
    local CMD

    if [ "$HAS_SYSTEMCTL" = 1 ]; then
        CMD="systemctl $ACTION $PG_SERVICE"
    else
        CMD="$PG_SERVICE $ACTION"
    fi

    if [ -z "$VERBOSE" ]; then
        $CMD &> /dev/null
    else
        $CMD
    fi
}

getVersionForCluster() {
    local _RESULT=$1
    local CLUSTER=$2
    local PORT=$3
    local ERROR_ON_MULT_VER=$4
    local VER
    local NUM_VER

    VER=$(pg_lsclusters -h | grep -Po "\d+(\.\d+)?(?=\s+$CLUSTER\s+$PORT)")
    NUM_VER=$(echo "$VER" | wc -w)

    if [ "$NUM_VER" = "1" ]; then
        eval "$_RESULT"="$VER"

    elif [ "$NUM_VER" = "0" ]; then
        echoLocalizedErr \
            "Pro port '$PORT' neexistuje databázový cluster '$CLUSTER'" \
            "No database cluster '$CLUSTER' exists for port '$PORT'!"
        return 1

    elif [ -n "$ERROR_ON_MULT_VER" ]; then
        echoLocalizedErr \
            "Nalezeno více než jeden cluster '$CLUSTER' na portu '$PORT'!" \
            "More than one cluster '$CLUSTER' found on port '$PORT'!"
        return 1

    else
        local LAST_VER=$(echo "$VER" | tail -1)
        echoLocalizedMsg \
            "Nalezeno více než jeden cluster '$CLUSTER' na portu '$PORT'. Použije se verze: $LAST_VER" \
            "More than one cluster '$CLUSTER' found on port '$PORT'. Selected version: $LAST_VER"
        eval "$_RESULT"="$LAST_VER"
    fi
}

checkDetectCalled() {
    if [ -z "$PG_SERVICE" ]; then
        _quit "First call WinStrom_detectPostgresql!"
    fi
}

WinStrom_restartPostgresql() {
    serviceAction restart
}

WinStrom_startPostgresql() {
    # pg_ctlcluster na System V vrací 2 pokud cluster už běží. Na systemd vrací ve stejné situaci 0.
    if [ -z "${PG_SERVICE%%pg_ctlcluster*}" ]; then
        RET=0
        serviceAction start || RET=$?

        if [ "$RET" = 0 ] || [ "$RET" = 2 ]; then
            return 0;
        else
            return $RET
        fi
    fi

    serviceAction start
}

WinStrom_stopPostgresql() {
    serviceAction stop
}

WinStrom_postgresqlStatus() {
    serviceAction status
}

WinStrom_postgresqlStatusVerbose() {
    serviceAction status verbose
}

detectClustering() {
      export CLUSTER_NAME
      export HAS_CLUSTERS=0

      if which pg_lsclusters &> /dev/null; then
          HAS_CLUSTERS=1
          CLUSTER_NAME=flexibee
      fi
}

# detect PostgreSQL version and service name
WinStrom_detectPostgresql() {
    if [ "$1" = "1" ]; then
        FRESH_INSTALL=1
    fi
    # default values
    export PG_USER=postgres
    export PG_GROUP=postgres
    export USING_DEFAULT_PG=0

    if which systemctl &> /dev/null; then
        HAS_SYSTEMCTL=1
    else
        HAS_SYSTEMCTL=0
    fi

    # detect Cluster support (Debian/Ubuntu specific)
    detectClustering

    SERVICE_NAME=postgresql-VER
    if [ "$HAS_CLUSTERS" = 1 ] && [ "$HAS_SYSTEMCTL" = 1 ]; then
        SERVICE_NAME=postgresql@VER-$CLUSTER_NAME
    fi

    export DBVERSION=0

    SUPPORTEDVERSIONS=( "17" "16" "15" "14" "13" )

    # check PostgreSQL from flexibee-server.xml only when upgrading
    if [ "$FRESH_INSTALL" != "1" ] && [ -f "/etc/flexibee/flexibee-server.xml" ]; then
        CFG_PORT=$(getPgPortFromFlexiConfig)

        if [ -n "$CFG_PORT" ]; then
            if [ "$HAS_CLUSTERS" = "1" ]; then
                getVersionForCluster TMP_VERSION "$CLUSTER_NAME" "$CFG_PORT" || true

                if isSupportedVersion "$TMP_VERSION"; then
                    checkVersionAvailable "$TMP_VERSION"
                fi
            else
                # first we check default installation
                if isPortInPgConf /var/lib/pgsql/data/postgresql.conf "$CFG_PORT"; then
                    TMP_VERSION=$(cat /var/lib/pgsql/data/PG_VERSION)
                    # check if this version is supported
                    if isSupportedVersion "$TMP_VERSION"; then
                        DBVERSION=-1
                    fi
                # then PGDG installation
                else
                    for ver in "${SUPPORTEDVERSIONS[@]}"
                    do
                        if isPortInPgConf "/var/lib/pgsql/$ver/data/postgresql.conf" "$CFG_PORT"; then
                            checkVersionAvailable "$ver"
                            break
                        fi
                    done
                fi
            fi
        fi
    fi

    # try to find any supported version
    if [ "$DBVERSION" = 0 ]; then
        for ver in "${SUPPORTEDVERSIONS[@]}"
        do
            if checkVersionAvailable "$ver"; then
                break
            fi
        done
    fi

    if [ "$DBVERSION" -le 0 ]; then
        # default installation Debian
        if [ -f "/var/lib/flexibee/PG_VERSION" ]; then
           TMP_VERSION=$(cat /var/lib/flexibee/PG_VERSION)
        # default installation RHL
        elif [ -f "/var/lib/pgsql/data/PG_VERSION" ]; then
           TMP_VERSION=$(cat /var/lib/pgsql/data/PG_VERSION)
        fi

        # use default installation only if it is supported version
        if [ -n "$TMP_VERSION" ] && isSupportedVersion "$TMP_VERSION"; then
           DBVERSION=$TMP_VERSION
           USING_DEFAULT_PG=1

           if [ "$HAS_SYSTEMCTL" = "1" ]; then
               PG_SERVICE="postgresql"
           elif [ -f "/etc/init.d/postgresql" ]; then
               PG_SERVICE="/etc/init.d/postgresql"
           fi
        fi
    else
        if [ "$HAS_SYSTEMCTL" = "1" ]; then
            PG_SERVICE=$(getServiceName "$DBVERSION")
        elif [ -f "/etc/init.d/postgresql-$DBVERSION" ]; then
            PG_SERVICE="/etc/init.d/postgresql-$DBVERSION"
        elif [ "$HAS_CLUSTERS" = "1" ]; then
            PG_SERVICE="pg_ctlcluster $DBVERSION $CLUSTER_NAME"
        fi
    fi

    if [ -z "$PG_SERVICE" ]; then
        _quit "PostgreSQL service could not be detected!"
    fi
}

findPgConfigRedHat() {
    if [ "$USING_DEFAULT_PG" = 1 ]; then
        if [ -f /etc/postgresql/postgresql.conf ]; then
            CONFIGFILE=/etc/postgresql/postgresql.conf
            PG_HBA_FILE=/etc/postgresql/pg_hba.conf
        elif [ -f /var/lib/pgsql/data/postgresql.conf ]; then
           CONFIGFILE=/var/lib/pgsql/data/postgresql.conf
           PG_HBA_FILE=/var/lib/pgsql/data/pg_hba.conf
       elif [ -f /var/lib/postgresql/data/postgresql.conf ]; then
           CONFIGFILE=/var/lib/postgresql/data/postgresql.conf
           PG_HBA_FILE=/var/lib/postgresql/data/pg_hba.conf
       fi
    else
        if [ -f "/var/lib/pgsql/$DBVERSION/data/postgresql.conf" ]; then
            CONFIGFILE=/var/lib/pgsql/$DBVERSION/data/postgresql.conf
            PG_HBA_FILE=/var/lib/pgsql/$DBVERSION/data/pg_hba.conf
        elif [ -f "/var/lib/postgresql/$DBVERSION/data/postgresql.conf" ]; then
            CONFIGFILE=/var/lib/postgresql/$DBVERSION/data/postgresql.conf
            PG_HBA_FILE=/var/lib/postgresql/$DBVERSION/data/pg_hba.conf
        fi
    fi
}

# finds postgresql.conf, pg_hba.conf and port
WinStrom_detectPortAndConf() {
    checkDetectCalled

    export CONFIGFILE
    export PG_HBA_FILE
    export CLUSTERARG
    export PG_PORT

    if [ "$HAS_CLUSTERS" = 1 ]; then
        CLUSTERARG="--cluster $DBVERSION/$CLUSTER_NAME"
        CONFIGFILE=/etc/postgresql/$DBVERSION/$CLUSTER_NAME/postgresql.conf
        PG_HBA_FILE=/etc/postgresql/$DBVERSION/$CLUSTER_NAME/pg_hba.conf
        PG_PORT=$(pg_lsclusters -h "$DBVERSION" "$CLUSTER_NAME" | grep -Po "$CLUSTER_NAME\s*\K\d+")
    else
        findPgConfigRedHat
    fi

    if [ ! -f "$CONFIGFILE" ] || [ ! -f "$PG_HBA_FILE" ]; then
        _quit "Could not find PostgreSQL configuration!"
    fi

    if [ -z "$PG_PORT" ]; then
        PG_PORT=$(getPgPortFromPgConfig "$CONFIGFILE")
    fi
}

# initializes new database (cluster) if needed
WinStrom_createAndStartDatabase() {
    checkDetectCalled

    if [ "$HAS_CLUSTERS" = 1 ]; then
        if ! pg_lsclusters "$DBVERSION" "$CLUSTER_NAME" &> /dev/null; then
            CFG_PORT=$(getPgPortFromFlexiConfig)
            if [ -n "$CFG_PORT" ] && isPortFree $CFG_PORT; then
              FREE_PORT=$CFG_PORT
            else
              FREE_PORT=$(WinStrom_findNextFreePort 5431)
            fi

            _debug "Creating database cluster $CLUSTER_NAME for version $DBVERSION on port $FREE_PORT."

            /usr/bin/pg_createcluster --port="$FREE_PORT" --encoding=utf-8 --locale=cs_CZ.utf8 --user=$PG_USER \
            --group=$PG_GROUP --start-conf=auto --socketdir=/var/run/postgresql/ "$DBVERSION" "$CLUSTER_NAME" \
            > /dev/null || _quit "Failed to create new cluster $DBVERSION/$CLUSTER_NAME"
        fi

        if ! WinStrom_startPostgresql; then
            _quit "Database \"$DBVERSION\" \"$CLUSTER_NAME\" failed to start with exit code $?."
        fi
        # Other scripts depend on this variable
        RUNNING=1
    else
        RUNNING=0
        CREATED=0
        export NEED_RESTART=0

        WinStrom_detectPostgresqlRunning

        if [ "x$RUNNING" = "x0" ]; then
            # On some distribution (SUSE) PostgreSQL creates its configuration during first start
            WinStrom_startPostgresql && RUNNING=1 || true
        fi

        # Call initdb script
        if [ "x$RUNNING" = "x0" ]; then
            if [ "$USING_DEFAULT_PG" = 1 ] && [ ! -f /var/lib/pgsql/data/PG_VERSION ]; then
                if [ -f /usr/bin/postgresql-setup ]; then
                    INITCMD="/usr/bin/postgresql-setup"
                fi
            elif [ "$USING_DEFAULT_PG" = 0 ] && [ ! -f "/var/lib/pgsql/${DBVERSION}/data/PG_VERSION" ]; then
                if [ -f "/usr/pgsql-${DBVERSION}/bin/postgresql${DBVERSION}-setup" ]; then
                    INITCMD="/usr/pgsql-${DBVERSION}/bin/postgresql${DBVERSION}-setup"
                elif [ -f "/usr/pgsql-${DBVERSION}/bin/postgresql-${DBVERSION}-setup" ]; then
                    INITCMD="/usr/pgsql-${DBVERSION}/bin/postgresql-${DBVERSION}-setup"
                fi
            fi

            if [ -n "$INITCMD" ]; then
                _debug "Calling database initialization with command: $INITCMD."
                $INITCMD initdb || _quit "Can't initialize PostgreSQL"
                CREATED=1
            fi
        fi

        if [ "$RUNNING" = "0" ] && [ "$CREATED" = "1" ]; then
            WinStrom_startPostgresql && RUNNING=1
        fi

        if [ "$RUNNING" = "0" ]; then
            WinStrom_detectPortAndConf

            # PostgreSQL is not running, maybe it's configured to use unavailable port
            if ! isPortFree "$PG_PORT"; then
                # port $PG_PORT already used, let's find a free one
                TMP_PORT=$(WinStrom_findNextFreePort "$PG_PORT")
                if [ -n "$TMP_PORT" ]; then
                    _debug "Moving PostgreSQL $DBVERSION from port $PG_PORT to $TMP_PORT."
                    sed -i -E "s/#?\s*port\s*=\s*$PG_PORT/port = $TMP_PORT/g" "$CONFIGFILE"
                    PG_PORT=$TMP_PORT
                    WinStrom_startPostgresql && RUNNING=1
                fi
            fi
        fi
    fi
}

isPortFree() {
    if (echo >"/dev/tcp/localhost/$1") &> /dev/null; then
        return 1 # something is listening on this port
    else
        return 0 # port is free
    fi
}

WinStrom_findNextFreePort() {
    local PORT=$1
    for i in {1..20}; do
        TMP_PORT=$((PORT+i))
        if [ "$TMP_PORT" != "5434" ] && isPortFree $TMP_PORT; then
            echo $TMP_PORT
            break
        fi
    done
}

WinStrom_detectPostgresqlRunning() {
    checkDetectCalled

    RUNNING=0
    if WinStrom_postgresqlStatus; then
        RUNNING=1
    fi
}

WinStrom_detectRunning() {
    SUFF=$1
    RUNNING=0
    if [ -f "$FLEXIBEE_PID$SUFF" ]; then
        ps "$(cat "$FLEXIBEE_PID$SUFF")" >/dev/null && RUNNING=1 || true
    fi
    return 0
}



WinStrom_startIfNeeded() {
    checkDetectCalled
    R=$1

    # Start PostgreSQL
    if [ "x$R" = "x0" ]; then
        _debug "Starting PostgreSQL"
        OK=1
        WinStrom_startPostgresql || OK=0

        if [ "x$OK" == "x0" ]; then
            _quit "Can't start PostgreSQL"
        fi

        sleep 1
    fi
}
