#!/usr/bin/python3
import os
import json
import argparse
import subprocess
import shlex

# COLORS!
RESET = "\033[0m"
GRN = "\033[1;32m"
LIT_GRN = "\033[92m"
RED = "\033[1;31m"
WARN = "\033[33m"
CLEAR ="\033c"
BOLD="\033[1m"
RESET_BOLD="\033[2m"

VRX_SERVICE_NAME="voxl-vrx"
VRX_CONFIG_FILE = "/etc/modalai/voxl-vrx.conf"
WESTON_AUTOSTART_SERVICE_NAME = "weston-autostart"

freq_to_ch = {
    2412: 1,
    2417: 2,
    2422: 3,
    2427: 4,
    2432: 5,
    2437: 6,
    2442: 7,
    2447: 8,
    2452: 9,
    2457: 10,
    2462: 11,
    2467: 12,
    2472: 13,
    2484: 14,
    5160: 32,
    5180: 36,
    5200: 40,
    5220: 44,
    5240: 48,
    5260: 52,
    5280: 56,
    5300: 60,
    5320: 64,
    5340: 68,
    5500: 100,
    5520: 104,
    5540: 108,
    5560: 112,
    5580: 116,
    5600: 120,
    5620: 124,
    5640: 128,
    5660: 132,
    5680: 136,
    5700: 140,
    5720: 144,
    5745: 149,
    5765: 153,
    5785: 157,
    5805: 161,
    5825: 165,
    5845: 169,
    5865: 173,
    5885: 177
}
valid_bw = { 20, 40 }

config_header = """/**
 * voxl-vrx Configuration File
 *
 * ============================================================================
 * WIRELESS RECEPTION SETTINGS
 * ============================================================================
 * frequency: Center frequency in MHz (must match VTX)
 * bandwidth: Channel bandwidth in MHz (20 or 40, must match VTX)
 * card: WiFi interface name (e.g., "wlan0", "ath0")
 * enable_fec: Enable Forward Error Correction (must match VTX, True/False)
 * enable_stbc: Enable Space-Time Block Coding (number of spatial streams)
 * enable_lq_resp: Enable repsonses to Link Quality Requests from a VTX unit
 *
 * ============================================================================
 * VIDEO DECODER SETTINGS
 * ============================================================================
 * width: Initial video width hint for decoder
 * height: Initial video height hint for decoder
 * encoding: Video codec ("h264" or "h265")
 *
 * ============================================================================
 * VIDEO DISPLAY SETTINGS
 * ============================================================================
 * stretch_to_screen: Stretch video to fill screen (may distort aspect ratio)
 * splash_on_lost_link: Show splash screen when link is lost
 * blackout_mode: Show black screen instead of splash/acquiring link/last frame when video is not live
 * osd_on_link_loss: OSD behavior when video link is lost
 *             0: No OSD on link loss
 *             1: Full OSD on link loss
 *             2: Minimal OSD on link loss (loss%, RSSI, FPS, channel, CPU temp only)
 * link_loss_indicator: Show indicator when OSD is disabled during link loss (proof of life in blackout mode)
 * stream_timeout_ms: Time in milliseconds before declaring stream lost
 *
 * ============================================================================
 * ON-SCREEN DISPLAY (OSD) SETTINGS
 * ============================================================================
 * debug_osd: Display debug information on OSD
 * osd_font_file: Path to TrueType font file for OSD text
 * osd_font_size: Font size for OSD text
 * ai_detection_enable: Enable AI object detection overlay if received from VTX
 * ai_detection_show_labels: Show labels on detected objects
 *
 * ============================================================================
 * DECODER PERFORMANCE SETTINGS
 * ============================================================================
 * overheat_mitigation: Thermal mitigation level
 *          -1 (Off/None): Don't throttle based on decode latency
 *           0 (Latest):   Use latest decode latency for throttling
 *          >0 (Average):  Number of decode latency samples to average for throttling
 * decode_latency_threshold: Threshold in milliseconds for throttling
 * display_damaged_frames: Show all decoded frames even if a frame is flagged to be dropped by decoder
 * drop_all_bad_pframes: Drop P-frames until next I-frame if decoder flags a frame to be dropped
 *
 * ============================================================================
 * RECORDING SETTINGS
 * ============================================================================
 * record_video: Start recording video on application startup
 * allow_record_local: Allow recording to local disk when no USB device present
 *
 * ============================================================================
 * UDP MODE (for LTE/Doodle/Silvus/etc)
 * ============================================================================
 * udp: Use UDP for TX/RX instead of packet injection
 * udp_interface_ip: Local IP address for UDP interface - used for TX/RX
 * udp_interface_tx_port: Local transmit port for UDP (-1 to let OS assign)
 * udp_interface_rx_port: Local receive port for UDP - port to RX on
 * udp_receiver_configs: Array of endpoints to TX to
 *   ip: IP address of endpoint to TX to
 *   port: Port on endpoint to TX to
 *
 * ============================================================================
 * Retransmission over UDP settings
 * ============================================================================
 * en_retransmit: Enable retransmissions from this VRX
 * en_retransmit_with_local_decode: Whether to do any further processing after retransmit (FEC decode, video decode, render, etc)
 * retransmit_tx_ip: IP address to transmit retransmissions from
 * retransmit_tx_port: Port to transmit retransmissions from
 * retransmit_rx_ip: IP address to send the retransmission to 
 * retransmit_rx_port: Port to send the retransimission to 
 *
 * ============================================================================
 * TELEMETRY & NETWORK SETTINGS
 * ============================================================================
 * gcs_ip: Ground Control Station IP address to forward MAVLink data to (Ex: ATAK device)
 * telem_tx_ip: IP on this VRX to transmit MAVLink/Telemetry data from
 * mpeg_ts_rebroadcast_port: Ground Control Station port to forward MPEG-TS format stream to (Ex: ATAK device)
 * en_mpeg_ts_rebroadcast: Enable transmitting encoded video stream over UDP (in MPEG-TS format) to GCS_IP 
 *
 * ============================================================================
 * SYSTEM SETTINGS
 * ============================================================================
 * perf_mode: Set CPU to performance governor when armed for lower latency (True/False)
 * enable_button_board: Enable/Disable button board 
 * battery_cell_count: Number of battery cells in VRX for voltage calculation and OSD
 *
 * ============================================================================
 * CHANNEL SCANNING
 * ============================================================================
 * scan_dwell_time_ms: Time in milliseconds to dwell on each channel during scan
 *
 * ============================================================================
 * DEBUG & LOGGING
 * ============================================================================
 * debug_log: Debug logging to disk behavior
 *            "off": No logging enabled
 *            "on":  Start logging on application startup
 *            "arm": Start logging when drone is armed
 *
 * ulog: Logging MAVLink and VTX Link stats to ulog behavior
 *            "off": No logging enabled
 *            "on":  Start logging on application startup
 *            "arm": Start logging when drone is armed
 *
 */
"""

default_config = {
  # WIRELESS RECEPTION SETTINGS
  "frequency": 5805,
  "bandwidth": 20,
  "card": "wlan0",
  "enable_fec": True,
  "enable_stbc": True,
  "enable_lq_resp": False,

  # VIDEO DECODER SETTINGS
  "width": 1280,
  "height": 720,
  "encoding": "h265",

  # VIDEO DISPLAY SETTINGS
  "stretch_to_screen": False,
  "splash_on_lost_link": False,
  "blackout_mode": False,
  "osd_on_link_loss": 1,
  "link_loss_indicator": True,
  "stream_timeout_ms": 100,

  # ON-SCREEN DISPLAY (OSD) SETTINGS
  "debug_osd": False,
  "osd_font_file": "/usr/share/fonts/truetype/sono/Sono-Medium.ttf",
  "osd_font_size": 16,
  "ai_detection_enable": True,
  "ai_detection_show_labels": True,

  # DECODER PERFORMANCE SETTINGS
  "overheat_mitigation": 1,
  "decode_latency_threshold": 100,
  "display_damaged_frames": False,
  "drop_all_bad_pframes": False,

  # RECORDING SETTINGS
  "record_video": False,
  "allow_record_local": False,

  # UDP RX MODE
  "udp": False,
  "udp_interface_ip": "",
  "udp_interface_tx_port": -1,
  "udp_interface_rx_port": 50000,
  "udp_receiver_configs": [
    {
      "ip": "",
      "port": -1
    }
  ],

  # Re-transmit settings
  "en_retransmit": False,
  "en_retransmit_with_local_decode": False,
  "retransmit_tx_ip": "",
  "retransmit_tx_port": -1,
  "retransmit_rx_ip": "",
  "retransmit_rx_port": -1,

  # TELEMETRY & NETWORK SETTINGS
  "gcs_ip": "10.0.0.1",
  "telem_tx_ip": "10.0.0.2",
  "mpeg_ts_rebroadcast_port": 8901,
  "en_mpeg_ts_rebroadcast": False,

  # SYSTEM SETTINGS
  "perf_mode": True,
  "enable_button_board": True,
  "battery_cell_count": 2,

  # CHANNEL SCANNING
  "scan_dwell_time_ms": 500,

  # DEBUG & LOGGING
  "debug_log": "off",
  "ulog": "off"
}

# Parse args
def parse_args() -> dict:
    """
    Parse and validate args
    """
    parser = argparse.ArgumentParser(add_help=False)
    parser.add_argument('-h', '--help',        help='Show this help message and exit', required=False, action='store_true')
    parser.add_argument('-l', '--list',        help='Show the current configuration',  required=False, action='store_true')

    # Wifibroadcast args
    parser.add_argument('-F', '--frequency',   help='Set frequency', required=False, type=int)
    parser.add_argument('-b', '--bandwidth',   help='Set bandwidth', required=False, type=int)

    # Factory settings for voxl-vrx 
    parser.add_argument('-m', '--factory_enable_mini_pini', help='Enable voxl-vrx service and restore default configuration file for Mini Pini modem', required=False, action='store_true')
    parser.add_argument('-f', '--factory_enable_spark_lan', help='Enable voxl-vrx service and restore default configuration file for SparkLAN modem', required=False, action='store_true')
    parser.add_argument('-I', '--factory_enable_fiber',     help='Setup for fiber operation', required=False, action='store_true')
    parser.add_argument('-L', '--factory_enable_lte',       help='Setup for LTE operation', required=False, action='store_true')
    parser.add_argument('-D', '--factory_enable_doodle',    help='Setup for Doodle operation', required=False, action='store_true')
    parser.add_argument('--factory_enable_dtc',       help='Setup for DTC operation', required=False, action='store_true')
    parser.add_argument('-T', '--factory_enable_dgcs_tower', help='Setup for DGCS tower operation', required=False, action='store_true')
    parser.add_argument('-P', '--factory_enable_dgcs_pilot', help='Setup for DGCS pilot operation', required=False, action='store_true')
    parser.add_argument('-e', '--enable',         help='Enable voxl-vrx services', required=False, action='store_true')
    parser.add_argument('-d', '--disable',        help='Disable voxl-vrx services', required=False, action='store_true')
    parser.add_argument('--blackout-mode',  help='Enable/Disable blackout mode',  required=False, type=int, default=-1)
    parser.add_argument('--osd-on-link-loss',  help='OSD behavior when video link is lost',  required=False, type=int, default=-1)
    parser.add_argument('--link-loss-indicator',  help='Enable/Disable link loss indicator',  required=False, type=int, default=-1)

    # Wizard
    parser.add_argument('-w', '--wizard',     help='Start interactive wizard', required=False, action='store_true')

    return vars(parser.parse_args())

# Print help message
def print_usage() -> None:
    """
    Print usage/help message
    """

    print("Description: Configure voxl-vrx settings.")
    print("")
    print("Usage: voxl-configure-vrx -h -F -b -e -d -m -f -D -T -P -w")
    print("")
    print("Optional Arguments (for VRX settings only, besides the wizard):")
    print(" -h, --help                  Show this help message and exit")
    print(" -l, --list                  Show the current configuration")

    # Wifibroadcast args
    print(" -F, --frequency             Set frequency")
    print(" -b, --bandwidth             Set bandwidth")

    # Toggle wifibroadcast/softap args
    print(" -e, --enable                Enable voxl-vrx service and load kernel modules if not already loaded")
    print(" -d, --disable               Disable voxl-vrx service")
    print(" --blackout-mode             Enable/Disable blackout mode")
    print(" --osd-on-link-loss          OSD behavior when video link is lost")
    print(" --link-loss-indicator       Enable/Disable link loss indicator")
    print(" -L, --factory_enable_lte              Enable voxl-vrx service, Setup for LTE operation")
    print(" -m, --factory_enable_mini_pini        Enable voxl-vrx service, load kernel modules if not already loaded, and restore default configuration file for Mini Pini modem")
    print(" -f, --factory_enable_spark_lan        Enable voxl-vrx service, load kernel modules if not already loaded, and restore default configuration file for SparkLAN modem")
    print(" -D, --factory_enable_doodle           Enable voxl-vrx service, Setup for Doodle radio operation")
    print(" --factory_enable_dtc                  Enable voxl-vrx service, Setup for DTC radio operation")
    print(" -T, --factory_enable_dgcs_tower       Enable voxl-vrx service, Setup for DGCS tower operation")
    print(" -P, --factory_enable_dgcs_pilot       Enable voxl-vrx service, Setup for DGCS pilot operation")

    # Wizard
    print(" -w, --wizard                Start interactive wizard")
    print("")


# Input validation helper
def validate_service_name(name: str) -> str:
    """
    Validate service name contains only safe characters.
    Raises ValueError if validation fails.
    """
    import re
    if not re.match(r'^[a-zA-Z0-9._-]+$', name):
        raise ValueError(f"Invalid service name: {name}")
    return name

# Check if a service is enabled
def service_enabled(service_name: str) -> bool:
    """
    Checks if a systemd service is enabled or not

    @service_name: name of service to check
    @return: True/False depending on service state
    """
    service_name = validate_service_name(service_name)
    result = subprocess.run(['systemctl', 'is-enabled', shlex.quote(service_name)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    if result.returncode == 0:
        return True
    else:
        return False


# Check if a service is running
def service_running(service_name: str) -> bool:
    """
    Checks if a systemd service is running or not

    @service_name: name of service to check
    @return: True/False depending on service state
    """
    service_name = validate_service_name(service_name)
    result = subprocess.run(['systemctl', 'is-active', shlex.quote(service_name)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    if result.returncode == 0:
        return True
    else:
        return False

# Check if a service is enabled
def service_exists(service_name: str) -> bool:
    """
    Checks if a systemd service exists

    @service_name: name of service to check
    @return: True/False depending on existence of service 
    """
    service_name = validate_service_name(service_name)
    result = subprocess.run(['systemctl', 'cat', shlex.quote(service_name)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    if result.returncode == 0:
        return True
    else:
        return False

# Restart a service
def restart_service(service_name: str) -> bool:
    """
    Restarts a systemd service

    @service_name: name of service to restart
    @return: True/False depending on if successful
    """
    service_name = validate_service_name(service_name)
    print(f"Restarting {service_name}, this may take a few seconds...")
    result = subprocess.run(['systemctl', 'restart', shlex.quote(service_name)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    if result.returncode != 0:
        print(f"Failed to restart {service_name}.")
        return False

    return True

# Enable and start a service
def enable_and_start_service(service_name: str) -> bool:
    """
    Enables and starts a systemd service

    @service_name: name of service to enable and starts
    @return: True/False depending on if successful
    """
    service_name = validate_service_name(service_name)

    # Enable service
    result = subprocess.run(['systemctl', 'enable', shlex.quote(service_name)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    if result.returncode != 0:
        print(f"Failed to enable {service_name}.")
        return False

    # Start service
    result = subprocess.run(['systemctl', 'start', shlex.quote(service_name)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    if result.returncode != 0:
        print(f"Failed to start {service_name}.")
        return False

    return True

# Disable and stop a service
def disable_and_stop_service(service_name: str) -> bool:
    """
    Disables and stops a systemd service

    @service_name: name of service to disable and stop
    @return: True/False depending on if successful
    """
    service_name = validate_service_name(service_name)

    # Disable service
    result = subprocess.run(['systemctl', 'disable', shlex.quote(service_name)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    if result.returncode != 0:
        print(f"Failed to disable {service_name}.")
        return False

    # Stop service
    result = subprocess.run(['systemctl', 'stop', shlex.quote(service_name)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    if result.returncode != 0:
        print(f"Failed to stop {service_name}.")
        return False
    
    return True

# Enable a service
def enable_service(service_name: str) -> bool:
    """
    Enables a systemd service

    @service_name: name of service to enable
    @return: True/False depending on if successful
    """
    service_name = validate_service_name(service_name)

    # Enable service
    result = subprocess.run(['systemctl', 'enable', shlex.quote(service_name)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    if result.returncode != 0:
        print(f"Failed to enable {service_name}.")
        return False

    return True

# Disable a service
def disable_service(service_name: str) -> bool:
    """
    Disables a systemd service

    @service_name: name of service to disable
    @return: True/False depending on if successful
    """
    service_name = validate_service_name(service_name)

    # Disable service
    result = subprocess.run(['systemctl', 'disable', shlex.quote(service_name)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    if result.returncode != 0:
        print(f"Failed to disable {service_name}.")
        return False

    return True

# Helper to load JSON file with optional comments
def load_json_with_comments(file_path: str) -> dict:
    """
    Load JSON from a file, filtering out lines containing '*' (comment lines).

    @file_path: Path to the JSON file
    @return: Parsed JSON as a dictionary
    """
    with open(file_path, 'r') as f:
        filtered_lines = [line for line in f if '*' not in line]
        return json.loads(''.join(filtered_lines))

# Helper to save JSON file with header comments
def save_json_with_header(file_path: str, config: dict) -> None:
    """
    Save JSON to a file with header comments preserved.

    @file_path: Path to the JSON file
    @config: Dictionary to save as JSON
    """
    with open(file_path, 'w') as f:
        f.write(config_header)
        json.dump(config, f, indent=4)
        f.write('\n')

# Read a file
def read_file(file: str) -> None:
    """
    Read the contents of a file given a file name.
    """
    if not os.path.exists(file):
        print(f"{BOLD}{RED}[ERROR]{RESET_BOLD} File {file} does not exist!")
        return

    with open(file, 'r') as config:
        print(config.read().strip())

# Create a configuration file with default settings
def write_default_config() -> None:
    """
    Write the default config file with header comments.
    """
    print("VRX Configuration:")
    for key, value in default_config.items():
        print(f"\t{key}: {value}")

    save_json_with_header(VRX_CONFIG_FILE, default_config)

# update config file via arg and val
def update_config_param(arg: str, value, parent: str = ""):
    """
    Update a param in the config file.
    @arg: The field to be updated
    @value: The value for the field to be set to 
    @parent: Nested/Subfield that the given arg is in 
    """
    # Ensure the config file exists
    if not os.path.exists(VRX_CONFIG_FILE):
        print(f"Config file '{VRX_CONFIG_FILE}' not found. Creating with default values.")
        save_json_with_header(VRX_CONFIG_FILE, default_config)

    # Read the existing config file
    try:
        config = load_json_with_comments(VRX_CONFIG_FILE)
    except json.JSONDecodeError as e:
        raise ValueError(f"Error decoding JSON from config file: {e}")

    # Update the configuration
    if not parent and arg in config:
        config[arg] = value
    elif parent and parent in config: # Nested value
        for list_item in config[parent]:
            if arg in list_item:
                list_item[arg] = value
    else:
        raise KeyError(f"Key '{arg}' not found in the configuration file.")

    # Write the updated config back to the file
    save_json_with_header(VRX_CONFIG_FILE, config)

    print(f"{arg} -> {value}")

# Dump the current configuration
def dump_config() -> None:
    """
    Show the current configuration.
    """
    vrx_config_json = load_json_with_comments(VRX_CONFIG_FILE)
    if vrx_config_json.get('card') is not None:                     print(f"Card: {vrx_config_json['card']}")
    if vrx_config_json.get('bandwidth') is not None:                print(f"Bandwidth: {vrx_config_json['bandwidth']}")
    if vrx_config_json.get('frequency') is not None:                print(f"Frequency: {vrx_config_json['frequency']}")
    if vrx_config_json.get('enable_fec') is not None:               print(f"Enable_fec: {vrx_config_json['enable_fec']}")
    if vrx_config_json.get('enable_stbc') is not None:              print(f"Enable_stbc: {vrx_config_json['enable_stbc']}")
    if vrx_config_json.get('width') is not None:                    print(f"Width: {vrx_config_json['width']}")
    if vrx_config_json.get('height') is not None:                   print(f"Height: {vrx_config_json['height']}")
    if vrx_config_json.get('encoding') is not None:                 print(f"Encoding: {vrx_config_json['encoding']}")
    if vrx_config_json.get('perf_mode') is not None:                print(f"Perf_mode: {vrx_config_json['perf_mode']}")
    if vrx_config_json.get('debug_log') is not None:                print(f"Debug_log: {vrx_config_json['debug_log']}")
    if vrx_config_json.get('debug_osd') is not None:                print(f"Debug_osd: {vrx_config_json['debug_osd']}")
    if vrx_config_json.get('overheat_mitigation') is not None:      print(f"Overheat_mitigation: {vrx_config_json['overheat_mitigation']}")
    if vrx_config_json.get('decode_latency_threshold') is not None: print(f"Decode_latency_threshold: {vrx_config_json['decode_latency_threshold']}")

# configure display options
def configure_display(blackout_mode=False):
    """
     Setup weston config, specifying splash screen, fullscreen mode, display refresh rate
     Use blackout_mode parameter to select appropriate weston config
    """
    if blackout_mode:
        subprocess.run(['mkdir', '-p', '/etc/xdg/weston'])
        subprocess.run(['cp', '-f', '/usr/share/modalai/vtx/weston-active-blackout.ini', '/etc/xdg/weston/weston.ini'])
    else:
        subprocess.run(['mkdir', '-p', '/etc/xdg/weston'])
        subprocess.run(['cp', '-f', '/usr/share/modalai/vtx/weston-active.ini', '/etc/xdg/weston/weston.ini'])

# Enable services
def enable_vrx():
    """
    Enable voxl-vrx service and load kernel modules if not already loaded
    """
    configure_display()
    enable_service(VRX_SERVICE_NAME)
    if service_exists(WESTON_AUTOSTART_SERVICE_NAME):
        enable_service(WESTON_AUTOSTART_SERVICE_NAME)
    subprocess.run(['bash', '/usr/bin/enable_vrx.sh', '-b', '5G', '-c', '161'])

# Enable services and load default config file for Mini Pini modem 
def factory_enable_mini_pini(): 
    """
    Enable voxl-vrx service, load kernel modules if not already loaded, and write default config file 
    """
    # Restore default config file
    configure_display()
    update_config_param("card", "ath0")
    update_config_param("udp", False)
    enable_service(VRX_SERVICE_NAME)
    if service_exists(WESTON_AUTOSTART_SERVICE_NAME):
        enable_service(WESTON_AUTOSTART_SERVICE_NAME)
    subprocess.run(['bash', '/usr/bin/enable_vrx.sh', '-b', '5G', '-c', '161'])
    

# Enable services and load default config file for SparkLAN modem
def factory_enable_spark_lan(): 
    """
    Enable voxl-vrx service, load kernel modules if not already loaded, and write default config file 
    """
    # Restore default config file
    configure_display()
    update_config_param("card", "wlan0")
    update_config_param("udp", False)
    enable_service(VRX_SERVICE_NAME)
    if service_exists(WESTON_AUTOSTART_SERVICE_NAME):
        enable_service(WESTON_AUTOSTART_SERVICE_NAME)

# Enable services and load default config file for SparkLAN modem
def factory_enable_lte(): 
    """
    Enable voxl-vrx service, load kernel modules if not already loaded, and write default config file 
    """
    # Restore default config file
    configure_display()
    update_config_param("udp", True)
    enable_service(VRX_SERVICE_NAME)
    if service_exists(WESTON_AUTOSTART_SERVICE_NAME):
        enable_service(WESTON_AUTOSTART_SERVICE_NAME)
        
# Enable services and update config file for Doodle use case
def factory_enable_doodle(): 
    """
    Enable voxl-vrx service, update config file for Doodle use case 
    """
    # Restore default config file
    configure_display()
    update_config_param("udp", True)
    update_config_param("udp_interface_ip", "10.223.0.101")
    update_config_param("udp_interface_rx_port", 50000)
    update_config_param("ip", "10.223.0.100", "udp_receiver_configs")
    update_config_param("port", 50001, "udp_receiver_configs")
    enable_service(VRX_SERVICE_NAME)
    if service_exists(WESTON_AUTOSTART_SERVICE_NAME):
        enable_service(WESTON_AUTOSTART_SERVICE_NAME)

# Enable services and update config file for DTC use case
def factory_enable_dtc(): 
    """
    Enable voxl-vrx service, update config file for DTC radio operation 
    """
    # Restore default config file
    configure_display()
    update_config_param("udp", True)
    update_config_param("udp_interface_ip", "192.168.0.101")
    update_config_param("udp_interface_rx_port", 50000)
    update_config_param("ip", "192.168.0.100", "udp_receiver_configs")
    update_config_param("port", 50001, "udp_receiver_configs")
    enable_service(VRX_SERVICE_NAME)
    if service_exists(WESTON_AUTOSTART_SERVICE_NAME):
        enable_service(WESTON_AUTOSTART_SERVICE_NAME)

# Enable services and update config file for fiber use case
def factory_enable_fiber(): 
    """
    Enable voxl-vrx service, update config file for fiber use case 
    """
    # Restore default config file
    configure_display()
    update_config_param("udp", True)
    update_config_param("udp_interface_ip", "10.0.0.2")
    update_config_param("udp_interface_rx_port", 50000)
    update_config_param("ip", "10.0.0.10", "udp_receiver_configs")
    update_config_param("port", 50001, "udp_receiver_configs")
    update_config_param("gcs_ip", "192.168.0.1")
    enable_service(VRX_SERVICE_NAME)
    if service_exists(WESTON_AUTOSTART_SERVICE_NAME):
        enable_service(WESTON_AUTOSTART_SERVICE_NAME)

# Enable services and update config file for DGCS tower use case
def factory_enable_dgcs_tower():
    """
    Enable voxl-vrx service, update config file for DGCS tower operation
    """
    configure_display()
    # RF baseline matches SparkLAN preset (wlan0, no UDP)
    update_config_param("card", "wlan0")
    update_config_param("udp", False)
    update_config_param("enable_button_board", False)
    update_config_param("en_retransmit", True)
    update_config_param("en_retransmit_with_local_decode", False)
    update_config_param("retransmit_tx_ip", "10.0.0.10")
    update_config_param("retransmit_rx_ip", "10.0.0.2")
    update_config_param("retransmit_tx_port", 5002)
    update_config_param("retransmit_rx_port", 5000)
    update_config_param("telem_tx_ip", "10.0.0.10")
    update_config_param("gcs_ip", "10.0.0.2")
    enable_service(VRX_SERVICE_NAME)
    if service_exists(WESTON_AUTOSTART_SERVICE_NAME):
        enable_service(WESTON_AUTOSTART_SERVICE_NAME)

# Enable services and update config file for DGCS pilot use case
def factory_enable_dgcs_pilot():
    """
    Enable voxl-vrx service, update config file for DGCS pilot operation
    """
    configure_display()
    update_config_param("udp", True)
    update_config_param("udp_interface_ip", "10.0.0.2")
    update_config_param("udp_interface_tx_port", 5002)
    update_config_param("udp_interface_rx_port", 5000)
    update_config_param("telem_tx_ip", "10.0.0.2")
    update_config_param("gcs_ip", "10.0.0.1")
    update_config_param("ip", "10.0.0.10", "udp_receiver_configs")
    update_config_param("port", 14550, "udp_receiver_configs")
    update_config_param("en_mpeg_ts_rebroadcast", True)
    enable_service(VRX_SERVICE_NAME)
    if service_exists(WESTON_AUTOSTART_SERVICE_NAME):
        enable_service(WESTON_AUTOSTART_SERVICE_NAME)

# Disable services
def disable_vrx():
    """
    Disable voxl-vrx servicee 
    """
    disable_service(VRX_SERVICE_NAME)

# Handle updating wifibroadcast configuration file
def update_vrx_config(args: dict) -> bool:
    """
    Update parameters in VRX configuration file given a set of args from command line
    @args: Dictionary of args passed in at the command line containing fields to be updated in the wifibroadcast configuration file
    """
    try:
        vrx_config_json = load_json_with_comments(VRX_CONFIG_FILE)

        for field, value in args.items():
            if value is None or value is False or value == -1: continue
            print(f"{field}: {vrx_config_json[field]} -> {value}")
            vrx_config_json[field] = value

        save_json_with_header(VRX_CONFIG_FILE, vrx_config_json)
    except FileNotFoundError as e:
        print(f"[ERROR] Unable to locate VRX configuration file.")
        print(str(e))
        return False
    except Exception as e:
        print(f"[ERROR] An unexpected error occurred while uploading VRX configuration. {str(e)}")
        save_json_with_header(VRX_CONFIG_FILE, vrx_config_json)
        return False
    return True

# Handle updating VRX configuration file via wizard
def wizard_update_vrx_config() -> bool:
    """
    Update parameters in VRX configuration file via wizard
    """
    options = []
    cases = {}
    invalid = False

    while True:
        try:
            read_file(VRX_CONFIG_FILE)
            vrx_config_json = load_json_with_comments(VRX_CONFIG_FILE)
            options = list(vrx_config_json.keys())
            options.insert(0,"Quit")
            options.insert(1,"Back")
            options.insert(2,"Restart Service")

            print(f"{LIT_GRN}Select a field to update:{RESET}")
            for index, option in enumerate(options):
                print(f"[{index + 1}] {option}")
                cases[index + 1] = option
    
            try:
                field = cases.get(int(input()))
                print(f"{CLEAR}", end="")
            except KeyboardInterrupt:
                print()
                exit(0)
            except:
                print(f"{WARN}Invalid answer provided, please select a valid option.{RESET}")
                continue
            
            if field == "Back": break
            if field == "Quit": exit(0)
            if field == "Restart Service": 
                restart_service(VRX_SERVICE_NAME)
                continue
            with open(VRX_CONFIG_FILE, 'w') as vrx_config:
                try:
                    if type(vrx_config_json[field]) == bool:
                        value = input(f"Enter a value for {field} (Current: bool, {vrx_config_json[field]}): ").lower()
                        if value == "false":  value = False
                        elif value == "true": value = True
                        else:
                            throw_exception = 1/0
                    elif type(vrx_config_json[field]) == int:
                        value = int(input(f"Enter a value for {field} (Current: int, {vrx_config_json[field]}): "))
                    elif type(vrx_config_json[field]) == str:
                        value = input(f"Enter a value for {field} (Current: string, {vrx_config_json[field]}): ")
                except KeyboardInterrupt:
                    print()
                    invalid = True
                    continue
                except:
                    print(f"{WARN}Invalid answer provided, please select a valid option.{RESET}")
                    invalid = True
                    continue

            print(f"{field}: {vrx_config_json[field]} -> {value}")
            vrx_config_json[field] = value
            save_json_with_header(VRX_CONFIG_FILE, vrx_config_json)
        except FileNotFoundError as e:
            print(f"{RED}{BOLD}[ERROR] Unable to locate configuration file, please run voxl-vrx at least once so that it generates a configuration file.{RESET}")
            print(str(e))
            return False
        except Exception as e:
            save_json_with_header(VRX_CONFIG_FILE, vrx_config_json)
            if not invalid: 
                print(f"{RED}{BOLD}[ERROR] An unexpected error occurred while uploading configuration. {str(e)}{RESET}")
                return False
            invalid = False
    return True

# Wizard for handling interactive mode
def wizard() -> int:
    """
    Handle interactive mode, guide user through option menus and handle input.
    Wizard options:
    [1] Quit wizard
    [2] Restart VRX     -> Restart voxl-vrx service after making changes
    [3] VRX settings    -> Show current VRX settings
    """

    options = ["Quit", "Restart VRX", "VRX Settings"]
    cases = {}
    while True:
        print(f"{LIT_GRN}Select an option:{RESET}")
        for index, option in enumerate(options):
            print(f"[{index + 1}] {option}")
            cases[index + 1] = option

        try:
            ans = cases.get(int(input()))
            print(f"{CLEAR}", end="")
        except KeyboardInterrupt:
            print()
            exit(0)
        except:
            print(f"{WARN}Invalid answer provided, please select a valid option.{RESET}")
            continue
            
        # Handle request
        if ans == "Quit":return 0
        elif ans == "Restart VRX": restart_service(VRX_SERVICE_NAME)
        elif ans == "VRX Settings": wizard_update_vrx_config()
        else:
            print(f"{WARN}Invalid answer provided, please select a valid option.{RESET}")
            continue

if __name__ == "__main__":
    args = parse_args()
    
    # Print help message if no args provided
    if all(value is None or value is False or value == -1 for value in args.values()) or args["help"]:	
        print_usage()
        exit(0)

    if args["disable"]: 
        disable_vrx()
        exit(0)

    if not os.path.exists(VRX_CONFIG_FILE):
        print(f"{WARN}[WARNING] Unable to locate VRX configuration file! Generating default configuration file now.{RESET}")
        write_default_config()

    if os.path.exists(VRX_CONFIG_FILE) and not os.path.getsize(VRX_CONFIG_FILE)>0: 
        print(f"{WARN}[WARNING] VRX configuration file may be empty (file size: {os.path.getsize(VRX_CONFIG_FILE)})! Generating default configuration file now.{RESET}")
        write_default_config()

    # Dump current configuration
    if args["list"]: dump_config()

    # Check for use wizard (no args provided)
    if args["wizard"]: exit(wizard())
    
    # Handle enable/disable and setup
    if args["factory_enable_lte"]: factory_enable_lte()
    if args["factory_enable_mini_pini"]: factory_enable_mini_pini()
    if args["factory_enable_spark_lan"]: factory_enable_spark_lan()
    if args["factory_enable_doodle"]: factory_enable_doodle()
    if args["factory_enable_dtc"]: factory_enable_dtc()
    if args["factory_enable_fiber"]: factory_enable_fiber()
    if args["factory_enable_dgcs_tower"]: factory_enable_dgcs_tower()
    if args["factory_enable_dgcs_pilot"]: factory_enable_dgcs_pilot()
    if args["enable"]: enable_vrx()
    if args["blackout_mode"] > -1:
        enable_blackout_mode = args["blackout_mode"] > 0
        update_config_param("blackout_mode", enable_blackout_mode)
        configure_display(enable_blackout_mode)
    if args["osd_on_link_loss"] > -1:  
        update_config_param("osd_on_link_loss", args["osd_on_link_loss"])
    if args["link_loss_indicator"] > -1:  
        enable_link_loss_indicator = args["link_loss_indicator"] > 0
        update_config_param("link_loss_indicator", enable_link_loss_indicator)

    # Handle Wifibroadcast configuration 
    if args["frequency"] or args["bandwidth"]: update_vrx_config(args)
    