#!/usr/bin/env bash
#
# SPDX-License-Identifier: MPL-2.0
# SPDX-FileCopyrightText: 2025 Tony Germano and Mitch Gaffigan
#

# =============================================================================
# Connect Hub Server Launcher Script
#
# Description:
#   This script is the main launcher for the Connect Hub (OIE)
#   server. It prepares the Java environment and executes the server launcher
#   JAR file.
#
#   The script automatically finds a compatible Java runtime (version 17+ by
#   default) by searching for a valid executable in the following priority order:
#     1. The OIE_JAVA_PATH environment variable.
#     2. The -java-cmd directive in the oieserver.vmoptions file or included
#        .vmoptions files. Must specify the path to the 'java' executable.
#        This is the preferred way to declare the desired version for running
#        the server and can be overridden by OIE_JAVA_PATH. Can be a relative
#        path from the location of this script.
#     3. The JAVA_HOME environment variable.
#     4. The 'java' command available in the system's PATH.
#
#   It also parses the 'oieserver.vmoptions' file to configure JVM options,
#   system properties (-D...), and classpath modifications.
#
# Usage:
#   ./oieserver.sh [app-arguments]
#
#   All [app-arguments] are passed directly to the underlying Java application
#   (com.mirth.connect.server.launcher.MirthLauncher).
#
# Configuration via Environment Variables:
#   OIE_JAVA_PATH   - (Highest priority) Set the full path to the 'java'
#                   executable to be used. Can be a relative path from the
#                   location of this script or a tilde path
#                   (e.g., ~/path/to/java).
#   JAVA_HOME       - Set the path to the root of a Java installation. The
#                   script will look for 'bin/java' within this path.
# =============================================================================

APP_ARGS=("$@")
MIN_JAVA_VERSION=17

# Set OIE_HOME to the script directory
OIE_HOME="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
# The engine expects it's working directory to be OIE_HOME
if ! cd "$OIE_HOME"; then
    echo "Error: Could not change to the OIE_HOME directory: $OIE_HOME" >&2
    exit 1
fi
LAUNCHER_JAR="$OIE_HOME/mirth-server-launcher.jar"
CLASSPATH="$LAUNCHER_JAR"
VMOPTIONS=()
# This will hold the validated path to the Java executable. It is intentionally left empty for now.
FINAL_JAVA_CMD=""
# This will temporarily hold the result from parsing the vmoptions file.
VMOPTIONS_JAVA_CMD=""
VMOPTIONS_JAVA_CMD_FILE=""


# --- Function to resolve a path to a canonical absolute path ---
# Resolves a given path, handling tilde, relative, and '..' components.
# @param $1: The path to resolve.
# @echo: The resolved, canonical absolute path.
resolve_canonical_path() {
    local path_to_resolve="$1"

    # Explicitly handle tilde expansion first
    path_to_resolve=$(sed -E "s,^~(/|$),${HOME}\1," <<< "$path_to_resolve")

    # If the path is not absolute, assume it's relative to OIE_HOME
    if [[ ! "$path_to_resolve" =~ ^/ ]]; then
        path_to_resolve="$OIE_HOME/$path_to_resolve"
    fi

    # Use cd and pwd to resolve '..' and '.' components for a canonical path.
    if [[ -d "$(dirname "$path_to_resolve")" ]]; then
        echo "$(cd "$(dirname "$path_to_resolve")" && pwd)/$(basename "$path_to_resolve")"
    else
        echo "$path_to_resolve"
    fi
}

# --- Function to expand environment variable in a string ---
# @param $1: The line to expand
# @echo: The expanded line
expand_line_variables() {
    local line="$1"
    local result_line=""

    # This loop consumes the line from left to right, preventing recursive expansion.
    while [[ "$line" =~ (\$\{([a-zA-Z_][a-zA-Z0-9_]*)\}) ]]; do
        # Append the text before the match to our result.
        result_line+="${line%%"${BASH_REMATCH[0]}"*}"

        # Get the variable name and its value.
        local var_name="${BASH_REMATCH[2]}"
        # Use indirect expansion to get the variable's value. Do not replace if not set.
        if [[ -n "${!var_name+x}" ]]; then
            local var_value="${!var_name}"
            result_line+="$var_value"
        else
            result_line+="${BASH_REMATCH[0]}"
        fi

        # Update the line to be only the portion *after* the match.
        line="${line#*"${BASH_REMATCH[0]}"}"
    done

    # Append any remaining part of the line after the last match and return.
    echo "$result_line$line"
}

# --- Function to validate Java version ---
# Checks if a given command points to a Java executable of the required minimum version.
# @param $1: The java command or path to check
# @return: 0 on success (is valid), 1 on failure.
is_valid_java_version() {
    local java_cmd="$1"

    # Check if the command is found and is executable
    if ! command -v "$java_cmd" &> /dev/null || ! [[ -x "$(command -v "$java_cmd")" ]]; then
        return 1
    fi

    # Execute 'java -version' and capture the output from stderr
    # Example output: openjdk version "17.0.2" 2022-07-19
    local version_output
    version_output=$("$java_cmd" -version 2>&1)

    # Check if the version command succeeded
    if [[ $? -ne 0 ]]; then
        return 1
    fi

    # Extract the major version number. This works for formats like "1.8.0" and "17.0.2".
    local major_version
    major_version=$(echo "$version_output" | head -n 1 | cut -d '"' -f 2 | cut -d '.' -f 1)

    # Check if the extracted version is a number and meets the minimum requirement
    if [[ "$major_version" =~ ^[0-9]+$ ]] && [[ "$major_version" -ge "$MIN_JAVA_VERSION" ]]; then
        return 0 # Success
    else
        return 1 # Failure
    fi
}

# Set Java options by parsing the vmoptions file
parse_vmoptions() {
    local file="$1"

    if [[ ! -f "$file" ]]; then
        echo "Warning: VM options file not found: $file" >&2
        return 1
    fi

    # Read the file line by line
    while IFS= read -r line; do
        # Trim leading/trailing whitespace
        line=$(echo "$line" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')

        # Skip empty lines and comments
        if [[ -z "$line" || "$line" =~ ^# ]]; then
            continue
        fi

        line=$(expand_line_variables "$line")
        
        # Check for -include-options directive
        if [[ "$line" =~ ^-include-options[[:space:]]+(.+) ]]; then
            local included_file="${BASH_REMATCH[1]}"
            # Resolve relative paths
            if [[ ! "$included_file" =~ ^/ ]]; then # Not an absolute path
                included_file="$(dirname "$file")/$included_file"
            fi
            # Recursively call parse_vmoptions for the included file
            parse_vmoptions "$included_file"
        elif [[ "$line" =~ ^-classpath[[:space:]]+(.+) ]]; then
            # Handle -classpath directive
            CLASSPATH="${BASH_REMATCH[1]}"
        elif [[ "$line" =~ ^-classpath/a[[:space:]]+(.+) ]]; then
            # Handle -classpath/a directive (append to existing classpath)
            CLASSPATH="${CLASSPATH}:${BASH_REMATCH[1]}"
        elif [[ "$line" =~ ^-classpath/p[[:space:]]+(.+) ]]; then
            # Handle -classpath/p directive (prepend to existing classpath)
            CLASSPATH="${BASH_REMATCH[1]}:${CLASSPATH}"
        elif [[ "$line" =~ ^-java-cmd[[:space:]]+(.+) ]]; then
            # Handle -java-cmd directive
            # Store the path and the file it was found in. Validation is deferred.
            VMOPTIONS_JAVA_CMD=$(resolve_canonical_path "${BASH_REMATCH[1]}")
            VMOPTIONS_JAVA_CMD_FILE="$file"
        else
            # Add the option to the accumulated string
            VMOPTIONS+=("$line")
        fi
    done < "$file"
    return 0
}

# --- Main Logic ---

# 1. Recursively parse the VM options file to populate vmoptions variables.
parse_vmoptions "$OIE_HOME/oieserver.vmoptions"

# 2. Ensure the launcher JAR is always in the classpath.
case "$CLASSPATH" in
    *"$LAUNCHER_JAR"*)
        # It's already there, do nothing.
        ;;
    *)
        # Prepend the launcher JAR if a custom classpath was provided.
        echo "Info: Prepending mirth-server-launcher.jar to the classpath."
        CLASSPATH="$LAUNCHER_JAR:$CLASSPATH"
        ;;
esac

# 3. Discover the Java executable using the documented priority order.

# Check OIE_JAVA_PATH (fail-fast on invalid).
if [[ -n "$OIE_JAVA_PATH" ]]; then
    resolved_path=$(resolve_canonical_path "$OIE_JAVA_PATH")
    if is_valid_java_version "$resolved_path"; then
        echo "Info: Found suitable java version specified by the OIE_JAVA_PATH environment variable"
        FINAL_JAVA_CMD="$resolved_path"
    else
        echo "Error: '$resolved_path' is specified by the OIE_JAVA_PATH environment variable, which is not a valid Java executable of at least version $MIN_JAVA_VERSION. Exiting." >&2
        exit 1
    fi
fi

# Check -java-cmd from vmoptions (fail-fast on invalid, only if not already found).
if [[ -z "$FINAL_JAVA_CMD" ]] && [[ -n "$VMOPTIONS_JAVA_CMD" ]]; then
    if is_valid_java_version "$VMOPTIONS_JAVA_CMD"; then
        echo "Info: Found suitable java version specified by the -java-cmd directive in '$VMOPTIONS_JAVA_CMD_FILE'"
        FINAL_JAVA_CMD="$VMOPTIONS_JAVA_CMD"
    else
        echo "Error: '$VMOPTIONS_JAVA_CMD' is specified by the -java-cmd directive in '$VMOPTIONS_JAVA_CMD_FILE', which is not a valid Java executable of at least version $MIN_JAVA_VERSION. Exiting." >&2
        exit 1
    fi
fi

# Check JAVA_HOME (no fail-fast).
if [[ -z "$FINAL_JAVA_CMD" ]] && [[ -d "$JAVA_HOME" ]]; then
    java_home_path="$JAVA_HOME/bin/java"
    if is_valid_java_version "$java_home_path"; then
        echo "Info: Found suitable java version specified by the JAVA_HOME environment variable"
        FINAL_JAVA_CMD="$java_home_path"
    else
        echo "Warning: '$java_home_path' is specified by the JAVA_HOME environment variable, which is not a valid Java executable of at least version $MIN_JAVA_VERSION. Ignoring." >&2
    fi
fi

# Check system PATH (no fail-fast).
if [[ -z "$FINAL_JAVA_CMD" ]]; then
    if is_valid_java_version "java"; then
        echo "Info: Found suitable java version in the PATH"
        FINAL_JAVA_CMD="java"
    else
        echo "Warning: 'java' does not exist in your PATH or is not a valid Java executable of at least version $MIN_JAVA_VERSION." >&2
    fi
fi

# 4. Final check for a valid Java path before execution.
if [[ -z "$FINAL_JAVA_CMD" ]]; then
    echo "Error: Could not find a Java ${MIN_JAVA_VERSION}+ installation." >&2
    echo "Please configure -java-cmd in conf/custom.vmoptions, set OIE_JAVA_PATH, set JAVA_HOME, or ensure 'java' in your PATH is version ${MIN_JAVA_VERSION} or higher." >&2
    exit 1
fi

JAVA_OPTS=("${VMOPTIONS[@]}" 
           "-cp" "$CLASSPATH" 
           "com.mirth.connect.server.launcher.MirthLauncher" 
           "${APP_ARGS[@]}")

# Launch Connect Hub (as this PID with exec)
echo "Starting Connect Hub..."
echo "$FINAL_JAVA_CMD ${JAVA_OPTS[*]}"
exec "$FINAL_JAVA_CMD" "${JAVA_OPTS[@]}"
