#!/usr/bin/env -S bash

# A Bash script to monitor system stats and output them in JSON format.

# --- Configuration ---
# Default sleep duration in seconds. Can be overridden by the first argument.
SLEEP_DURATION=3

# --- Argument Parsing ---
# Check if a command-line argument is provided for the sleep duration.
if [[ -n "$1" ]]; then
  # Basic validation to ensure the argument is a number (integer or float).
  if [[ "$1" =~ ^[0-9]+(\.[0-9]+)?$ ]]; then
    SLEEP_DURATION=$1
  else
    # Output to stderr if the format is invalid.
    echo "Warning: Invalid duration format '$1'. Using default of ${SLEEP_DURATION}s." >&2
  fi
fi

# --- Global Cache Variables ---
# These variables will store the discovered CPU temperature sensor path and type
# to avoid searching for it on every loop iteration.
TEMP_SENSOR_PATH=""
TEMP_SENSOR_TYPE=""

# Network speed monitoring variables
PREV_RX_BYTES=0
PREV_TX_BYTES=0
PREV_TIME=0

# --- Data Collection Functions ---

#
# Gets memory usage in GB, MB, and as a percentage.
#
get_memory_info() {
  awk '
    /MemTotal/ {total=$2}
    /MemAvailable/ {available=$2}
    END {
      if (total > 0) {
        usage_kb = total - available
        usage_gb = usage_kb / 1000000
        usage_percent = (usage_kb / total) * 100
        printf "%.1f %.0f\n", usage_gb, usage_percent
      } else {
        # Fallback if /proc/meminfo is unreadable or empty.
        print "0.0 0 0"
      }
    }
  ' /proc/meminfo
}

#
# Gets the usage percentage of the root filesystem ("/").
#
get_disk_usage() {
  # df gets disk usage. --output=pcent shows only the percentage for the root path.
  # tail -1 gets the data line, and tr removes the '%' sign and whitespace.
  df --output=pcent / | tail -1 | tr -d ' %'
}

#
# Calculates current CPU usage over a short interval.
#
get_cpu_usage() {
  # Read all 10 CPU time fields to prevent errors on newer kernels.
  read -r cpu prev_user prev_nice prev_system prev_idle prev_iowait prev_irq prev_softirq prev_steal prev_guest prev_guest_nice < /proc/stat
  
  # Calculate previous total and idle times.
  local prev_total_idle=$((prev_idle + prev_iowait))
  local prev_total=$((prev_user + prev_nice + prev_system + prev_idle + prev_iowait + prev_irq + prev_softirq + prev_steal + prev_guest + prev_guest_nice))
  
  # Wait for a short period.
  sleep 0.05
  
  # Read all 10 CPU time fields again for the second measurement.
  read -r cpu user nice system idle iowait irq softirq steal guest guest_nice < /proc/stat
  
  # Calculate new total and idle times.
  local total_idle=$((idle + iowait))
  local total=$((user + nice + system + idle + iowait + irq + softirq + steal + guest + guest_nice))
  
  # Add a check to prevent division by zero if total hasn't changed.
  if (( total <= prev_total )); then
      echo "0.0"
      return
  fi

  # Calculate the difference over the interval.
  local diff_total=$((total - prev_total))
  local diff_idle=$((total_idle - prev_total_idle))

  # Use awk for floating-point calculation and print the percentage.
  awk -v total="$diff_total" -v idle="$diff_idle" '
    BEGIN {
      if (total > 0) {
        # Formula: 100 * (Total - Idle) / Total
        usage = 100 * (total - idle) / total
        printf "%.1f\n", usage
      } else {
        print "0.0"
      }
    }'
}

#
# Finds and returns the CPU temperature in degrees Celsius.
# Caches the sensor path for efficiency.
#
get_cpu_temp() {
  # If the sensor path hasn't been found yet, search for it.
  if [[ -z "$TEMP_SENSOR_PATH" ]]; then
    for dir in /sys/class/hwmon/hwmon*; do
      # Check if the 'name' file exists and read it.
      if [[ -f "$dir/name" ]]; then
        local name
        name=$(<"$dir/name")
        # Check for supported sensor types.
        if [[ "$name" == "coretemp" || "$name" == "k10temp" || "$name" == "zenpower" ]]; then
          TEMP_SENSOR_PATH=$dir
          TEMP_SENSOR_TYPE=$name
          break # Found it, no need to keep searching.
        fi
      fi
    done
  fi

  # If after searching no sensor was found, return 0.
  if [[ -z "$TEMP_SENSOR_PATH" ]]; then
    echo 0
    return
  fi

  # --- Get temp based on sensor type ---
  if [[ "$TEMP_SENSOR_TYPE" == "coretemp" ]]; then
    # For Intel 'coretemp', average all available temperature sensors.
    local total_temp=0
    local sensor_count=0

    # Use a for loop with a glob to iterate over all temp input files.
    # This is more efficient than 'find' for this simple case.
    for temp_file in "$TEMP_SENSOR_PATH"/temp*_input; do
      # The glob returns the pattern itself if no files match,
      # so we must check if the file actually exists.
      if [[ -f "$temp_file" ]]; then
        total_temp=$((total_temp + $(<"$temp_file")))
        sensor_count=$((sensor_count + 1))
      fi
    done

    if (( sensor_count > 0 )); then
      # Use awk for the final division to handle potential floating point numbers
      # and convert from millidegrees to integer degrees Celsius.
      awk -v total="$total_temp" -v count="$sensor_count" 'BEGIN { print int(total / count / 1000) }'
    else
      # If no sensor files were found, return 0.
      echo 0
    fi

  elif [[ "$TEMP_SENSOR_TYPE" == "k10temp" ]]; then
    # For AMD 'k10temp', find the 'Tctl' sensor, which is the control temperature.
    local tctl_input=""
    for label_file in "$TEMP_SENSOR_PATH"/temp*_label; do
      if [[ -f "$label_file" ]] && [[ $(<"$label_file") == "Tctl" ]]; then
        # The input file has the same name but with '_input' instead of '_label'.
        tctl_input="${label_file%_label}_input"
        break
      fi
    done

    if [[ -f "$tctl_input" ]]; then
      # Read the temperature and convert from millidegrees to degrees.
      echo "$(( $(<"$tctl_input") / 1000 ))"
    else
      echo 0 # Fallback
    fi
  elif [[ "$TEMP_SENSOR_TYPE" == "zenpower" ]]; then
          # For zenpower, read the first available temp sensor
          for temp_file in "$TEMP_SENSOR_PATH"/temp*_input; do
                  if [[ -f "$temp_file" ]]; then
                          local temp_value
                          temp_value=$(cat "$temp_file" | tr -d '\n\r') # Remove any newlines
                          echo "$((temp_value / 1000))"
                          return
                  fi
          done
          echo 0

          if [[ -f "$tctl_input" ]]; then
                  # Read the temperature and convert from millidegrees to degrees.
                  echo "$(($(<"$tctl_input") / 1000))"
          else
                  echo 0 # Fallback
          fi
  else
    echo 0 # Should not happen if cache logic is correct.
  fi
}



# --- Main Loop ---
# This loop runs indefinitely, gathering and printing stats.
while true; do
  # Call the functions to gather all the data.
  # get_memory_info
  read -r mem_gb mem_per <<< "$(get_memory_info)"
  
  # Command substitution captures the single output from the other functions.
  disk_per=$(get_disk_usage)
  cpu_usage=$(get_cpu_usage)
  cpu_temp=$(get_cpu_temp)
  
  # Get network speeds
  current_time=$(date +%s.%N)
  total_rx=0
  total_tx=0
  
  # Read total bytes from /proc/net/dev for all interfaces
  while IFS=: read -r interface stats; do
    # Skip only loopback interface, allow other interfaces
    if [[ "$interface" =~ ^lo[[:space:]]*$ ]]; then
      continue
    fi
    
    # Extract rx and tx bytes (fields 1 and 9 in the stats part)
    rx_bytes=$(echo "$stats" | awk '{print $1}')
    tx_bytes=$(echo "$stats" | awk '{print $9}')
    
    # Add to totals if they are valid numbers
    if [[ "$rx_bytes" =~ ^[0-9]+$ ]] && [[ "$tx_bytes" =~ ^[0-9]+$ ]]; then
      total_rx=$((total_rx + rx_bytes))
      total_tx=$((total_tx + tx_bytes))
    fi
  done < <(tail -n +3 /proc/net/dev)
  
  # Calculate speeds if we have previous data
  rx_speed=0
  tx_speed=0
  
  if [[ "$PREV_TIME" != "0" ]]; then
    time_diff=$(awk -v current="$current_time" -v prev="$PREV_TIME" 'BEGIN { printf "%.3f", current - prev }')
    rx_diff=$((total_rx - PREV_RX_BYTES))
    tx_diff=$((total_tx - PREV_TX_BYTES))
    
    # Calculate speeds in bytes per second using awk
    rx_speed=$(awk -v rx="$rx_diff" -v time="$time_diff" 'BEGIN { printf "%.0f", rx / time }')
    tx_speed=$(awk -v tx="$tx_diff" -v time="$time_diff" 'BEGIN { printf "%.0f", tx / time }')
  fi
  
  # Update previous values for next iteration
  PREV_RX_BYTES=$total_rx
  PREV_TX_BYTES=$total_tx
  PREV_TIME=$current_time

  # Use printf to format the final JSON output string, adding the mem_mb key.
  printf '{"cpu": "%s", "cputemp": "%s", "memgb":"%s", "memper": "%s", "diskper": "%s", "rx_speed": "%s", "tx_speed": "%s"}\n' \
    "$cpu_usage" \
    "$cpu_temp" \
    "$mem_gb" \
    "$mem_per" \
    "$disk_per" \
    "$rx_speed" \
    "$tx_speed"

  # Wait for the specified duration before the next update.
  sleep "$SLEEP_DURATION"
done
