#!/bin/bash
#
# https://git.math.uzh.ch/typo3/qfq/-/blob/master/extension/Resources/Public/scripts/inotify.sh
#
# Monitors directory $BASE_TRIGGER_PATH for updates on files.
# ...
#

# Default PHP script location (can be overridden with BASE)
PHP_SCRIPT_SUFFIX="typo3conf/ext/qfq/Classes/External/auto-cron.php"
LOCALCONFIGURATION_PATH="typo3conf/LocalConfiguration.php"
LOG_PATH="fileadmin/protected/qfqProject/log/qfqTrigger.log"
BASE_TRIGGER_PATH="fileadmin/protected/qfqProject/trigger"

# Declare associative array to store last processed time for each file path
declare -A last_processed_times

# Declare associative array to store running job PIDs for each file path
declare -A running_jobs

# Maximum number of concurrent processes
MAX_CONCURRENT=5

# Get the directory where this script is located
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
CONFIG_FILE="$(dirname "$(readlink -f "$0")")/inotify.json"

# Function to write to the log file
log_message() {
    local BASE_PATH="$1"
    local MESSAGE="$2"
    local LOG_FILE="${BASE_PATH}${LOG_PATH}"
    
    # Create log directory if it doesn't exist
    local LOG_DIR=$(dirname "${LOG_FILE}")
    mkdir -p "${LOG_DIR}"
    
    # Write timestamp and message to log
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $MESSAGE" >> "${LOG_FILE}"
}

# Function to extract baseUrl from TYPO3 configuration
extract_base_url() {
    local BASE_PATH="$1"
    local CONFIG_FILE="${BASE_PATH}${LOCALCONFIGURATION_PATH}"
    
    # Check if the configuration file exists
    if [ ! -r "$CONFIG_FILE" ]; then
        echo "Error: Can not read configuration file: $CONFIG_FILE"
        return 1
    fi
    
    # Use grep to extract the baseUrl value
    # This is a simplified approach - we're looking for the 'baseUrl' => 'value' pattern
    local BASE_URL=$(grep -A 1 "'baseUrl' =>" "$CONFIG_FILE" | grep "=>" | cut -d "'" -f 4)
    
    if [ -z "$BASE_URL" ]; then
        echo "Warning: Could not extract baseUrl from $CONFIG_FILE"
        return 1
    fi
    
    # Check if baseUrl starts with http:// or https://
    if [[ ! "$BASE_URL" =~ ^https?:// ]]; then
        # Add https:// prefix if no protocol is specified
        BASE_URL="https://${BASE_URL}"
    fi
    
    # Ensure the baseUrl ends with a slash
    if [[ ! "$BASE_URL" == */ ]]; then
        BASE_URL="${BASE_URL}/"
    fi
    
    echo "$BASE_URL"
    return 0
}

# Function to count running jobs by checking processes
count_running_jobs() {
    local count=0
    for key in "${!running_jobs[@]}"; do
        local pid=${running_jobs[$key]}
        if kill -0 $pid 2>/dev/null; then
            ((count++))
        else
            # Process is no longer running, remove it from tracking
            unset running_jobs[$key]
        fi
    done
    echo $count
}

# Function to check if a file needs reprocessing after job completion
check_for_reprocessing() {
    local BASE_PATH="$1"
    local FILE_PATH="$2"
    local BASE_URL="$3"
    
    local FILE_KEY="$FILE_PATH"
    local FILENAME=$(basename "$FILE_PATH")
    
    # Get the current file modification time
    if [ -f "$FILE_PATH" ]; then
        local CURRENT_MOD_TIME=$(stat -c %Y "$FILE_PATH")
        
        # Check if the file was modified while the job was running
        if [ "$CURRENT_MOD_TIME" -gt "${last_processed_times[$FILE_KEY]}" ]; then
            echo "File $FILE_PATH was modified while job was running. Touching file to trigger reprocessing."
            log_message "$BASE_PATH" "INFO: File $FILENAME was modified while job was running. Touching file to trigger reprocessing."
            
            # Touch the file to trigger reprocessing via the event system
            touch "$FILE_PATH"
        fi
    fi
    
    # Remove from running jobs tracking
    unset running_jobs[$FILE_KEY]
}

# Function to process a file
process_file() {
    local BASE_PATH="$1"
    local MODIFIED_FILE="$2"
    local BASE_URL="$3"
    local FILENAME=$(basename "$MODIFIED_FILE")
    local FILE_KEY="${MODIFIED_FILE}"
    
    # Update the last processed time for this file
    last_processed_times[$FILE_KEY]=$(date +%s)
    
    # Check if we have too many concurrent jobs running
    local running_count=$(count_running_jobs)
    if [ $running_count -ge $MAX_CONCURRENT ]; then
        echo "Maximum concurrent jobs ($MAX_CONCURRENT) reached. Currently running: $running_count"
        log_message "$BASE_PATH" "WARN: Maximum concurrent jobs ($MAX_CONCURRENT) reached. Waiting for a slot to process $FILENAME"
        while [ $(count_running_jobs) -ge $MAX_CONCURRENT ]; do
            sleep 1
        done
        echo "Slot available now, proceeding with execution"
        log_message "$BASE_PATH" "INFO: Slot available, proceeding with execution of $FILENAME"
    fi
    
    # Start the job
    {
        # Prepare curl URL with baseUrl if available
        local CURL_URL="$FILENAME"
        if [ ! -z "$BASE_URL" ]; then
            CURL_URL="${BASE_URL}${FILENAME}"
        fi
        
        # Log execution start
        log_message "$BASE_PATH" "INFO: Starting curl execution for trigger file: $FILENAME, URL: $CURL_URL"
        
        # Execute curl with the URL (silencing output)
        curl -L -s "$CURL_URL" > /dev/null 2>&1
        curl_exit_code=$?

        # Log curl result
        if [ $curl_exit_code -eq 0 ]; then
            log_message "$BASE_PATH" "INFO: Completed curl execution for $FILENAME successfully"
        else
            log_message "$BASE_PATH" "ERROR: Failed curl execution for $FILENAME with exit code $curl_exit_code"
        fi

        echo "Executed curl on: $CURL_URL at $(date)"
        
        # Check if the file needs to be processed again
        check_for_reprocessing "$BASE_PATH" "$MODIFIED_FILE" "$BASE_URL"
        
        echo "Completed job for: $MODIFIED_FILE"
    } &
    
    # Store the background process PID for tracking
    running_jobs[$FILE_KEY]=$!
    echo "Started job for $MODIFIED_FILE with PID ${running_jobs[$FILE_KEY]}"
}

# Function to monitor a single base path
monitor_path() {
    local BASE_PATH="$1"
    
    # Derive paths from BASE_PATH
    local TRIGGER_DIR="${BASE_PATH}${BASE_TRIGGER_PATH}"
    local PHP_SCRIPT="${BASE_PATH}${PHP_SCRIPT_SUFFIX}"
    
    echo "PHP script: ${PHP_SCRIPT}"
    
    # Extract the baseUrl from configuration
    local BASE_URL=$(extract_base_url "$BASE_PATH")
    local BASE_URL_STATUS=$?
    
    if [ $BASE_URL_STATUS -eq 0 ]; then
        echo "Extracted baseUrl: $BASE_URL"
    else
        echo "Failed to extract baseUrl, will use only filename for curl commands"
        BASE_URL=""
    fi
    
    # Check if the directory exists
    if [ ! -d "$TRIGGER_DIR" ]; then
        mkdir -p "$TRIGGER_DIR"
        if [ $? -ne 0 ] ; then
          echo "Error: Directory $TRIGGER_DIR could not be created."
          exit 1
        fi
    fi
    
    # Monitor the trigger directory for various events
    inotifywait -m -e close_write --format '%e %w%f' "$TRIGGER_DIR" | while read EVENT MODIFIED_FILE TIMESTAMP
    do
        echo "[$(date)] Event: $EVENT on File: $MODIFIED_FILE"
        
        # Get filename without path
        FILENAME=$(basename "$MODIFIED_FILE")
        
        # Get file modification time as timestamp
        FILE_MOD_TIME=$(stat -c %Y "$MODIFIED_FILE")
        
        # Create a unique key for this file path
        FILE_KEY="${MODIFIED_FILE}"
        
        # Check if there's already a job running for this file
        if [[ -n "${running_jobs[$FILE_KEY]}" ]]; then
            # Check if the process is still running
            if kill -0 ${running_jobs[$FILE_KEY]} 2>/dev/null; then
                echo "A job is already running for $MODIFIED_FILE. Skipping for now."
                log_message "$BASE_PATH" "INFO: Skipping execution for $FILENAME - a job is already running for this file"
                continue
            else
                # Process is no longer running, clean up tracking
                echo "Previous job for $MODIFIED_FILE has completed or terminated"
                unset running_jobs[$FILE_KEY]
            fi
        fi
        
        # Get the last processed time for this file path
        LAST_PROCESSED=${last_processed_times[$FILE_KEY]:-0}
        
        # Check if the file was modified after the last processed time
        if [ "$FILE_MOD_TIME" -gt "$LAST_PROCESSED" ]; then
            echo "File modification is newer than last processed time."
            
            # Process the file
            process_file "$BASE_PATH" "$MODIFIED_FILE" "$BASE_URL"
        else
            echo "File modification is not newer than last processed time. Skipping execution."
            log_message "$BASE_PATH" "INFO: Skipping execution for $FILENAME - file modification time is not newer than last processed time"
        fi
    done
}

# Check if jq is installed
if ! command -v jq &> /dev/null; then
    echo "Error: This script requires jq to be installed for JSON parsing."
    echo "Install it with: sudo apt-get install jq"
    exit 1
fi

# Check if config file exists
if [ ! -f "$CONFIG_FILE" ]; then
    echo "Error: Configuration file not found at: $CONFIG_FILE"
    echo "Please create a inotify.json file with the following format:"
    echo '{
  "paths": [
    "/var/www/html/typo3-instance1/",
    "/var/www/html/typo3-instance2/",
    "/path/to/other/instance/"
  ]
}'
    exit 1
fi

echo "Loading configuration from: $CONFIG_FILE"

# Extract paths from the JSON config file
PATHS=$(jq -r '.paths[]' "$CONFIG_FILE")
if [ -z "$PATHS" ]; then
    echo "Error: No paths defined in configuration file or invalid JSON format."
    exit 1
fi

echo "Starting directory monitoring for multiple paths..."
echo "Maximum concurrent jobs: $MAX_CONCURRENT"

# Start a monitoring process for each base path
PIDS=()
while IFS= read -r BASE_PATH; do
    # Ensure BASE_PATH ends with a slash
    if [[ ! "$BASE_PATH" == */ ]]; then
        BASE_PATH="${BASE_PATH}/"
    fi
    
    # Start monitoring in the background
    monitor_path "$BASE_PATH" &
    
    # Store the PID for potential cleanup
    PIDS+=($!)
    
    echo "Started monitoring for $BASE_PATH (PID: ${PIDS[-1]})"
done <<< "$PATHS"

# Function to clean up on exit
cleanup() {
    echo "Terminating all monitoring processes..."
    kill ${PIDS[@]} 2>/dev/null
    
    echo "Terminating any running jobs..."
    for key in "${!running_jobs[@]}"; do
        local pid=${running_jobs[$key]}
        if kill -0 $pid 2>/dev/null; then
            echo "Killing job with PID $pid"
            kill $pid 2>/dev/null
        fi
    done
    
    echo "Exiting..."
    exit
}

# Set up trap to kill all child processes when script terminates
trap cleanup SIGINT SIGTERM EXIT

# Wait for all background processes
echo "Monitoring running in parallel. Press Ctrl+C to stop."
wait