def main():
    # Initialize PyQt5 application
    app = QApplication(sys.argv)

    lock_manager = LockManager(LOCK_FILE)
    try:
        lock_manager.acquire()
    except (IOError, OSError):
        QMessageBox.warning(None, "", "The program is already running.")
        if lock_manager.file:
            lock_manager.file.close()
        sys.exit()
    
    atexit.register(lock_manager.release)

    # Your main program logic here
    print("Program is running...")

    global MAX_PINGS, MAX_WORKERS

    tab_widget = QTabWidget()
    
    # Initialize SoundManager (assuming this is defined elsewhere in your code)
    sound_manager = SoundManager()

    # Main widget and layout setup
    main_widget = QWidget()
    main_layout = QVBoxLayout(main_widget)

    # UI setup (e.g., labels for status information)
    host_label = QLabel()
    host_label.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
    host_label.setStyleSheet("font-size: 12pt; color: black; padding-right: 10px;")

    clock_label = QLabel()
    clock_label.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
    clock_label.setStyleSheet("font-size: 12pt;")

    # Arrange layout
    main_layout.addWidget(host_label)
    main_layout.addWidget(tab_widget)
    main_layout.addWidget(clock_label)
    main_widget.setLayout(main_layout)
    main_widget.setWindowTitle("Server Monitor - Alert")
    main_widget.setGeometry(300, 300, 1100, 600)

    # Function to update status labels
    def update_labels():
        current_time = QTime.currentTime().toString("hh:mm:ss")
        current_date = QDate.currentDate().toString("dd MMM yyyy")
        # Assuming host_info_dict is defined/available
        total_hosts = len(host_info_dict)
        offline_hosts = 0  # Implement logic to determine online/offline hosts
        offline_hosts_wan = 0  # Implement logic to specifically track WAN status
        offline_hosts_serv = 0  # Implement logic for Server status

        for row in range(main_window.table_widget.rowCount()):
            item = main_window.table_widget.item(row, 0)  # Assuming the hostname is in the first column
            if item is not None:
                inspect_status = main_window.table_widget.item(row, 6).text()  # Assuming the inspect status is in the 7th column
                if inspect_status != 'Server up':
                    offline_hosts += 1
                    if inspect_status == 'WAN down':
                        offline_hosts_wan += 1
                    elif inspect_status == 'Server down':
                        offline_hosts_serv += 1

        online_hosts = total_hosts - (offline_hosts_wan + offline_hosts_serv)

        # Example label updates
        host_label.setText(f"Total: {total_hosts}   Online: {total_hosts - offline_hosts}   Offline: {offline_hosts}   [ Server: {offline_hosts_serv}   Wan: {offline_hosts_wan} ]")
        clock_label.setText(f"{current_date} - {current_time}  ")

    # Timer for updating labels
    timer = QTimer()
    timer.timeout.connect(update_labels)
    timer.start(1000)  # Update every second

    # Create main UI components
    main_window = OfflineHostMonitor(None, None, None, None)
    tab_widget.addTab(main_window, "Monitor")
    return_online_widget = ReturnOnlineWidget()
    tab_widget.addTab(return_online_widget, "Return Online")
    main_window.sound_manager = sound_manager  # Attach the sound manager to the main window
    settings_widget = SettingsWidget(None, None, None, None, None, tab_widget, main_window)
    tab_widget.addTab(settings_widget, "Settings")
    update_log_widget = UpdateLogWidget()
    tab_widget.addTab(update_log_widget, "Update Log")
    main_window.settings_widget = settings_widget
    
    # Prepare application exit cleanup
    def cleanup():
        stop_event.set()  # Signal all threads to stop
        for thread in active_threads:
            thread.join()  # Wait for threads to finish

    # Connect cleanup on application exit
    app.aboutToQuit.connect(cleanup)

    # File path setup and initial checks
    file_path = 'hostname.txt'
    offline_state_file = 'offline_state.json'
    if not os.path.exists(file_path):
        QMessageBox.critical(None, "File Not Found", "The file 'hostname.txt' was not found. Exiting the application.")
        sys.exit()
        
    host_info_list, hostname_count = read_host_list(file_path)
    print(f"Number of hostnames in file: {hostname_count}")
    if not host_info_list:
        sys.exit()

    # Load offline state
    offline_dict = load_offline_state(offline_state_file)
    host_info_dict = {hostname.upper(): (hostname, store_name, telephone, wan_a, wan_b) for hostname, store_name, telephone, wan_a, wan_b in host_info_list}
    offline_dict = {hostname: details for hostname, details in offline_dict.items() if hostname in host_info_dict}
    
    # Queue and threading setup
    queue = Queue()
    stop_event = threading.Event()
    active_threads = []
    cpu_count = psutil.cpu_count(logical=True)
    memory = psutil.virtual_memory()
    available_memory_gb = memory.available / (1024 ** 2)
    max_pings_based_on_memory = int(available_memory_gb / 0.1)
    MAX_PINGS = min(MAX_PINGS, max_pings_based_on_memory)
    semaphore = threading.Semaphore(MAX_PINGS)
    
    # Assign globals to widgets
    main_window.queue = queue
    main_window.offline_dict = offline_dict
    main_window.stop_event = stop_event
    main_window.semaphore = semaphore
    settings_widget.queue = queue
    settings_widget.offline_dict = offline_dict
    settings_widget.stop_event = stop_event
    settings_widget.semaphore = semaphore
    settings_widget.host_info_list = host_info_list
    return_online_signal = return_online_widget.dataUpdated
    
    # Start monitoring threads for each host
    for host_info in host_info_list:
        thread = threading.Thread(
            target=monitor_host,
            args=(host_info, queue, offline_dict, stop_event, semaphore, main_window)
        )
        thread.start()
        active_threads.append(thread)
    
    # Start thread to handle offline list updates
    update_thread = threading.Thread(
        target=update_offline_list,
        args=(queue, offline_dict, stop_event, return_online_signal, settings_widget, main_window),
        daemon=True
    )
    update_thread.start()
    active_threads.append(update_thread)
    
    # Show main widget
    main_widget.show()
    
    # Ensure state is saved on app quit
    app.aboutToQuit.connect(lambda: save_offline_state(offline_state_file, offline_dict))

    sys.exit(app.exec_())


when close or exit program make popup "Program is closing..." till no thread running the close all