#!/usr/bin/python3
import os
import sys
import argparse
import subprocess

from voxl_configure_common import *

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

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_pcap: Enable RX via RF
 * 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
 *
 * ============================================================================
 * Sharpening Filter (Unsharp Mask)
 * ============================================================================
 * Applies a unsharp mask to the Y (luminance) channel before
 * converting to RGB. Useful for enhancing detail on cameras without an IR filter.
 *
 * sharpen_amount: Strength of the sharpening effect. Multiplier on the detected
 *                 detail signal (original - blurred). Higher = more sharpening.
 *                 0.0 = disabled (passthrough), 0.5 = subtle, 1.0-2.0 = moderate,
 *                 3.0+ = aggressive (may produce visible halos around edges).
 * sharpen_threshold: Minimum luminance difference before sharpening is applied.
 *                    Acts as a noise gate to avoid amplifying sensor noise.
 *                    0.0 = sharpen everything, 0.01-0.03 = ignore noise,
 *                    0.1+ = only sharpen strong edges.
 *
 * ============================================================================
 * 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
 * mavlink_gcs_port: Port to send MAVLink to and receive MAVLink from a GCS/ATAK
 * mavlink_upstream_ip: Optional upstream VRX IP for forwarding GCS-origin MAVLink toward VTX
 * mavlink_upstream_port: Optional upstream VRX port for forwarding GCS-origin MAVLink toward VTX
 * allow_mavlink_forward_while_armed: Forward GCS-origin MAVLink toward VTX while vehicle is armed
 * 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
 *
 * ============================================================================
 * OSD Settings
 * ============================================================================
 * pit_mode_icon_col: OSD column to display pit mode icon (0 to 51)
 * pit_mode_icon_row: OSD row to display pit mode icon (0 to 19)
 * no_video_link_col: OSD column to display 'no video link' text (0 to 51)
 * no_video_link_row: OSD row to display 'no video link' text (0 to 19)
 * source_status_col: OSD column to display RF/IP link status (0 to 51)
 * source_status_row: OSD row to display RF/IP link status (0 to 19)
 *
 * ============================================================================
 * 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_pcap": True,
  "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,

  # Sharpen filter settings
  "sharpen_amount": 1.5,
  "sharpen_threshold": 0.0,

  # 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": [],

  # 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",
  "mavlink_gcs_port": 14550,
  "mavlink_upstream_ip": "",
  "mavlink_upstream_port": -1,
  "allow_mavlink_forward_while_armed": False,
  "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,

  # OSD Settings
  "pit_mode_icon_col": 14,
  "pit_mode_icon_row": 3,
  "no_video_link_col": 5,
  "no_video_link_row": 0,
  "source_status_col": 44,
  "source_status_row": 0,

  # 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('--factory_enable_dgcs_tower_w_fallback', help='Setup for DGCS tower operation with fallback IP stream', 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(" -P, --factory_enable_dgcs_pilot       Enable voxl-vrx service, Setup for DGCS pilot operation")
    print(" -T, --factory_enable_dgcs_tower       Enable voxl-vrx service, Setup for DGCS tower operation")
    print(" --factory_enable_dgcs_tower_w_fallback       Enable voxl-vrx service, Setup for DGCS tower operation with fallback IP stream")

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


# The VRX config file: path, header, and defaults bound once
CONFIG = ConfigFile(VRX_CONFIG_FILE, config_header, default_config, allow_default_keys=True)

# 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, config_header)

# Fields shown by --list: (config key, display label), in print order
DUMP_FIELDS = (
    ("card",                              "Card"),
    ("bandwidth",                         "Bandwidth"),
    ("frequency",                         "Frequency"),
    ("enable_fec",                        "Enable_fec"),
    ("enable_stbc",                       "Enable_stbc"),
    ("width",                             "Width"),
    ("height",                            "Height"),
    ("encoding",                          "Encoding"),
    ("perf_mode",                         "Perf_mode"),
    ("debug_log",                         "Debug_log"),
    ("debug_osd",                         "Debug_osd"),
    ("gcs_ip",                            "Gcs_ip"),
    ("telem_tx_ip",                       "Telem_tx_ip"),
    ("mavlink_gcs_port",                  "Mavlink_gcs_port"),
    ("mavlink_upstream_ip",               "Mavlink_upstream_ip"),
    ("mavlink_upstream_port",             "Mavlink_upstream_port"),
    ("allow_mavlink_forward_while_armed", "Allow_mavlink_forward_while_armed"),
    ("overheat_mitigation",               "Overheat_mitigation"),
    ("decode_latency_threshold",          "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'])

class Preset:
    """
    A factory setup: config overrides applied in insertion order (order only
    matters for the printed 'key -> value' lines), plus optional shell hooks
    run after the services are enabled.
    """
    def __init__(self, overrides: dict, shell_hooks: tuple = ()):
        self.overrides = overrides
        self.shell_hooks = shell_hooks

# Factory presets, keyed by their CLI flag name. Adding a new platform setup
# is a new entry here, not a new function.
PRESETS = {
    "factory_enable_mini_pini": Preset(
        overrides={
            "card": "ath0",
            "udp": False,
        },
        shell_hooks=(
            ['bash', '/usr/bin/enable_vrx.sh', '-b', '5G', '-c', '161'],
        )),
    "factory_enable_spark_lan": Preset(
        overrides={
            "card": "wlan0",
            "udp": False,
        }),
    "factory_enable_lte": Preset(
        overrides={
            "udp": True,
            "enable_pcap": False,
        }),
    "factory_enable_doodle": Preset(
        overrides={
            "udp": True,
            "enable_pcap": False,
            "udp_interface_ip": "10.223.0.101",
            "udp_interface_rx_port": 50000,
            "udp_receiver_configs": [{"ip": "10.223.0.100", "port": 50001}],
        }),
    "factory_enable_dtc": Preset(
        overrides={
            "udp": True,
            "enable_pcap": False,
            "udp_interface_ip": "192.168.0.101",
            "udp_interface_rx_port": 50000,
            "udp_receiver_configs": [{"ip": "192.168.0.100", "port": 50001}],
        }),
    "factory_enable_fiber": Preset(
        overrides={
            "udp": True,
            "enable_pcap": False,
            "udp_interface_ip": "10.0.0.2",
            "udp_interface_rx_port": 50000,
            "udp_receiver_configs": [{"ip": "10.0.0.10", "port": 50001}],
            "gcs_ip": "192.168.0.1",
        }),
    "factory_enable_dgcs_tower": Preset(
        overrides={
            "card": "wlan0",
            "frequency": 2412,
            "enable_pcap": True,        # Receive and TX back on defined "card"
            "enable_lq_resp": True,     # Enable link quality response
            "udp": False,
            "udp_interface_rx_port": 50000,
            "enable_button_board": False,
            "en_retransmit": True,
            "en_retransmit_with_local_decode": False,
            "retransmit_tx_ip": "10.0.0.10",
            "retransmit_rx_ip": "10.0.0.2",
            "retransmit_tx_port": 5002,
            "retransmit_rx_port": 5000,
            "telem_tx_ip": "10.0.0.10",
            "gcs_ip": "",
            "mavlink_gcs_port": 14550,
            "mavlink_upstream_ip": "",
            "mavlink_upstream_port": -1,
            "allow_mavlink_forward_while_armed": False,
        }),
    "factory_enable_dgcs_tower_w_fallback": Preset(
        overrides={
            "card": "wlan0",
            "frequency": 2412,
            "enable_pcap": True,        # Receive and TX back on defined "card"
            "enable_lq_resp": True,     # Enable link quality response
            "udp": True,                # Receive data on LTE modem
            # The rx port below should correspond to the wg0 interface on the tower
            "udp_interface_rx_port": 50000,
            "enable_button_board": False,
            "en_retransmit": True,
            "en_retransmit_with_local_decode": True,
            "retransmit_tx_ip": "10.0.0.10",
            "retransmit_rx_ip": "10.0.0.2",
            "retransmit_tx_port": 5002,
            "retransmit_rx_port": 5000,
            "telem_tx_ip": "10.0.0.10",
            "gcs_ip": "",
            "mavlink_gcs_port": 14550,
            "mavlink_upstream_ip": "",
            "mavlink_upstream_port": -1,
            "allow_mavlink_forward_while_armed": False,
        }),
    "factory_enable_dgcs_pilot": Preset(
        overrides={
            "enable_pcap": False,
            "udp": True,
            "udp_interface_ip": "10.0.0.2",
            "udp_interface_tx_port": 5002,
            "udp_interface_rx_port": 5000,
            "udp_receiver_configs": [],
            "telem_tx_ip": "10.0.0.2",
            "gcs_ip": "10.0.0.1",       # atak device IP
            "mavlink_gcs_port": 14550,
            "mavlink_upstream_ip": "10.0.0.10",
            "mavlink_upstream_port": 14550,
            "allow_mavlink_forward_while_armed": False,
            "en_mpeg_ts_rebroadcast": True,
            "battery_cell_count": 6,
        }),
}

# Order presets are applied in when multiple flags are given
PRESET_DISPATCH = (
    "factory_enable_lte",
    "factory_enable_mini_pini",
    "factory_enable_spark_lan",
    "factory_enable_doodle",
    "factory_enable_dtc",
    "factory_enable_fiber",
    "factory_enable_dgcs_tower",
    "factory_enable_dgcs_tower_w_fallback",
    "factory_enable_dgcs_pilot",
)

def apply_preset(preset: Preset) -> None:
    """
    Enable voxl-vrx service and update config file for the given preset
    """
    configure_display()
    CONFIG.load()
    CONFIG.apply(preset.overrides)
    CONFIG.save()
    enable_service(VRX_SERVICE_NAME)
    if service_exists(WESTON_AUTOSTART_SERVICE_NAME):
        enable_service(WESTON_AUTOSTART_SERVICE_NAME)
    for cmd in preset.shell_hooks:
        subprocess.run(cmd)

# 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'])

# 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, config_header)
        if service_running(VRX_SERVICE_NAME):
                if args.get("frequency") is not None:
                    subprocess.run(['voxl-send-command', 'vrx_latency', 'set_frequency', str(vrx_config_json["frequency"])])
                
    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, config_header)
        return False
    return True

# Item schemas for JSON-object list fields edited via the wizard, so adding
# to an empty list prompts per key with the right types
LIST_FIELD_TEMPLATES = {
    "udp_receiver_configs": UDP_RECEIVER_TEMPLATE,
}

# 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 Exception:
                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
            try:
                if isinstance(vrx_config_json[field], list):
                    value = edit_list_field(field, vrx_config_json[field], LIST_FIELD_TEMPLATES.get(field))
                else:
                    value = prompt_field_value(field, vrx_config_json[field])
                    if value is None:   # type not editable, message already printed
                        continue
            except KeyboardInterrupt:
                print()
                invalid = True
                continue
            except Exception:
                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, config_header)
        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, config_header)
            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
    """
    return run_wizard("Restart VRX", VRX_SERVICE_NAME, "VRX Settings", wizard_update_vrx_config)

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(VRX_CONFIG_FILE, DUMP_FIELDS)

    # Check for use wizard (no args provided)
    if args["wizard"]: exit(wizard())

    # Handle factory presets
    for preset_name in PRESET_DISPATCH:
        if args[preset_name]: apply_preset(PRESETS[preset_name])

    # Handle enable and display options
    if args["enable"]: enable_vrx()
    if args["blackout_mode"] > -1:
        enable_blackout_mode = args["blackout_mode"] > 0
        CONFIG.update_param("blackout_mode", enable_blackout_mode)
        configure_display(enable_blackout_mode)
    if args["osd_on_link_loss"] > -1:
        CONFIG.update_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
        CONFIG.update_param("link_loss_indicator", enable_link_loss_indicator)

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