#!/usr/bin/env python3

import gi
import os

from requests.packages import package

import Functions as fn
import signal

import subprocess
from Functions import os
from queue import Queue
from time import sleep
import sys
import time

# UI modules
from ui.GUI import GUI
from ui.SplashScreen import SplashScreen
from ui.ProgressBarWindow import ProgressBarWindow
from ui.AppFrameGUI import AppFrameGUI
from ui.AboutDialog import AboutDialog
from ui.MessageDialog import MessageDialog
from ui.PacmanLogWindow import PacmanLogWindow
from ui.PackageListDialog import PackageListDialog
from ui.ProgressDialog import ProgressDialog
from ui.ISOPackagesWindow import ISOPackagesWindow
from ui.PackageSearchWindow import PackageSearchWindow
from ui.PackagesImportDialog import PackagesImportDialog

# Configuration module
from Settings import Settings

gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, Gdk, GdkPixbuf, Pango, GLib

#      #============================================================
#      #=  Authors:  Erik Dubois - Cameron Percival   - Fennec     =
#      #============================================================

# Folder structure

# cache contains descriptions - inside we have corrections for manual intervention
# + installed applications list
# yaml is the folder that is used to create the application
# yaml-awesome is a copy/paste from Calamares to meld manually - not used in the app

base_dir = os.path.dirname(os.path.realpath(__file__))


class Main(Gtk.Window):
    # Create a queue, for worker communication (Multithreading - used in GUI layer)
    queue = Queue()

    # Create a queue to handle package install/removal
    pkg_queue = Queue()

    # Create a queue for storing search results
    search_queue = Queue()

    # Create a queue for storing Pacman log file contents
    pacmanlog_queue = Queue()

    # Create a queue for storing packages waiting behind an in-progress pacman install transaction
    pkg_holding_queue = Queue()

    def __init__(self):
        try:
            super(Main, self).__init__(title="Sofirem")

            self.set_border_width(10)
            self.connect("delete-event", self.on_close)
            self.set_position(Gtk.WindowPosition.CENTER)
            self.set_icon_from_file(os.path.join(base_dir, "images/sofirem.png"))
            self.set_default_size(1100, 900)

            # ctrl+f give focus to search entry
            self.connect("key-press-event", self.on_keypress_event)

            # used for notifications
            self.timeout_id = None

            # default: displaying versions are disabled
            self.display_versions = False

            # initial app load search_activated is set to False
            self.search_activated = False

            # initial app load show the progress dialog window when a package is installed/uninstalled
            self.display_package_progress = False

            print(
                "---------------------------------------------------------------------------"
            )
            print("If you have errors, report it on the discord channel of ArcoLinux")
            print(
                "---------------------------------------------------------------------------"
            )
            print("You can receive support on https://discord.gg/stBhS4taje")
            print(
                "---------------------------------------------------------------------------"
            )
            print(
                "Many applications are coming from the Arch Linux repos and can be installed"
            )
            print(
                "without any issues. Other applications are available from third party repos"
            )
            print("like Chaotic repo, ArcoLinux repo and others.")
            print(
                "---------------------------------------------------------------------------"
            )
            print("We do NOT build packages from AUR.")
            print(
                "---------------------------------------------------------------------------"
            )
            print("Some packages are only available on the ArcoLinux repos.")
            print(
                "---------------------------------------------------------------------------"
            )

            if os.path.exists(fn.sofirem_lockfile):
                running = fn.check_if_process_running("sofirem")
                if running is True:
                    fn.logger.error(
                        "Sofirem lock file found in %s" % fn.sofirem_lockfile
                    )
                    fn.logger.error("Is there another Sofirem instance running ?")

                    sys.exit(1)

            else:
                splash_screen = SplashScreen()

                while Gtk.events_pending():
                    Gtk.main_iteration()

                sleep(1.5)
                splash_screen.destroy()

                # test there is no pacman lock file on the system
                if fn.check_pacman_lockfile():
                    message_dialog = MessageDialog(
                        "Error",
                        "Sofirem cannot proceed pacman lockfile found",
                        "Pacman cannot lock the db, a lockfile is found inside %s"
                        % fn.pacman_lockfile,
                        "Is there another Pacman process running ?",
                        "error",
                        False,
                    )
                    message_dialog.show_all()
                    message_dialog.run()
                    message_dialog.hide()

                    sys.exit(1)

                fn.logger.info("pkgver = pkgversion")
                fn.logger.info("pkgrel = pkgrelease")
                print(
                    "---------------------------------------------------------------------------"
                )
                fn.logger.info("Distro = " + fn.distr)
                print(
                    "---------------------------------------------------------------------------"
                )

                # start making sure sofirem starts next time with dark or light theme
                if os.path.isdir(fn.home + "/.config/gtk-3.0"):
                    try:
                        if not os.path.islink("/root/.config/gtk-3.0"):
                            if os.path.exists("/root/.config/gtk-3.0"):
                                fn.shutil.rmtree("/root/.config/gtk-3.0")

                            fn.shutil.copytree(
                                fn.home + "/.config/gtk-3.0", "/root/.config/gtk-3.0"
                            )
                    except Exception as e:
                        fn.logger.warning("GTK config: %s" % e)

                if os.path.isdir("/root/.config/xsettingsd/xsettingsd.conf"):
                    try:
                        if not os.path.islink("/root/.config/xsettingsd/"):
                            if os.path.exists("/root/.config/xsettingsd/"):
                                fn.shutil.rmtree("/root/.config/xsettingsd/")
                            if fn.path.isdir(fn.home + "/.config/xsettingsd/"):
                                fn.shutil.copytree(
                                    fn.home + "/.config/xsettingsd/",
                                    "/root/.config/xsettingsd/",
                                )
                    except Exception as e:
                        fn.logger.warning("xsettingsd config: %s" % e)

                # store package information into memory, and use the dictionary returned to search in for quicker retrieval
                fn.logger.info("Storing package metadata started")

                self.packages = fn.store_packages()
                fn.logger.info("Storing package metadata completed")

                fn.logger.info("Categories = %s" % len(self.packages.keys()))

                total_packages = 0

                for category in self.packages:
                    total_packages += len(self.packages[category])

                fn.logger.info("Total packages = %s" % total_packages)

                fn.logger.info("Setting up GUI")

                GUI.setup_gui(
                    self,
                    Gtk,
                    Gdk,
                    GdkPixbuf,
                    base_dir,
                    os,
                    Pango,
                    fn.settings_config,
                )

                # Create installed.lst file for first time

                fn.get_current_installed()
                installed_lst_file = "%s/cache/installed.lst" % base_dir
                packages_app_start_file = "%s/%s-packages.txt" % (
                    fn.log_dir,
                    fn.datetime.now().strftime("%Y-%m-%d-%H-%M-%S"),
                )
                if os.path.exists(installed_lst_file):
                    fn.logger.info("Created installed.lst")
                    # Keep log of installed packages before the app makes changes
                    # fn.shutil.copy(installed_lst_file, packages_app_start_file)

                # pacman sync db and also tests network connectivity

                thread_pacman_sync_db = fn.threading.Thread(
                    name="thread_pacman_sync_db",
                    target=self.pacman_db_sync,
                    daemon=True,
                )
                thread_pacman_sync_db.start()
                # if self.pacman_db_sync() is False:
                #     sys.exit(1)

        except Exception as e:
            fn.logger.error("Exception in Main() : %s" % e)

    # =====================================================
    #               PACMAN DB SYNC
    # =====================================================

    def pacman_db_sync(self):
        sync_err = fn.sync_package_db()

        if sync_err is not None:
            fn.logger.error("Pacman db synchronization failed")

            print(
                "---------------------------------------------------------------------------"
            )

            GLib.idle_add(
                self.show_sync_db_message_dialog,
                sync_err,
                priority=GLib.PRIORITY_DEFAULT,
            )

        else:
            fn.logger.info("Pacman db synchronization completed")

            return True

    def show_sync_db_message_dialog(self, sync_err):
        message_dialog = MessageDialog(
            "Error",
            "Pacman db synchronization failed",
            "Failed to run command = pacman -Sy\nPacman db synchronization failed\nCheck the synchronization logs, and verify you can connect to the appropriate mirrors\n\n",
            sync_err,
            "error",
            True,
        )

        message_dialog.show_all()
        message_dialog.run()
        message_dialog.hide()

    # =====================================================
    #               WINDOW KEY EVENT CTRL + F
    # =====================================================

    # sets focus on the search entry
    def on_keypress_event(self, widget, event):
        shortcut = Gtk.accelerator_get_label(event.keyval, event.state)

        if shortcut in ("Ctrl+F", "Ctrl+Mod2+F"):
            # set focus on text entry, select all text if any
            self.searchentry.grab_focus()

        if shortcut in ("Ctrl+I", "Ctrl+Mod2+I"):
            fn.show_package_info(self)

    # =====================================================
    #               SEARCH ENTRY
    # =====================================================

    def on_search_activated(self, searchentry):
        if searchentry.get_text_length() == 0 and self.search_activated:
            GUI.setup_gui(
                self,
                Gtk,
                Gdk,
                GdkPixbuf,
                base_dir,
                os,
                Pango,
                None,
            )
            self.search_activated = False

        if searchentry.get_text_length() == 0:
            self.search_activated = False

        search_term = searchentry.get_text()
        # if the string is completely whitespace ignore searching
        if not search_term.isspace():
            try:
                if len(search_term.rstrip().lstrip()) > 0:
                    # test if the string entered by the user is in the package name
                    # results is a dictionary, which holds a list of packages
                    # results[category]=pkg_list

                    # searching is processed inside a thread

                    th_search = fn.threading.Thread(
                        name="thread_search",
                        target=fn.search,
                        args=(
                            self,
                            search_term.rstrip().lstrip(),
                        ),
                    )
                    fn.logger.info("Starting search")

                    th_search.start()

                    # get the search_results from the queue
                    results = self.search_queue.get()

                    if results is not None:
                        fn.logger.info("Search complete")

                        if len(results) > 0:
                            total = 0
                            for val in results.values():
                                total += len(val)

                            fn.logger.info("Search found %s results" % total)
                            # make sure the gui search only displays the pkgs inside the results

                            GUI.setup_gui_search(
                                self,
                                Gtk,
                                Gdk,
                                GdkPixbuf,
                                base_dir,
                                os,
                                Pango,
                                results,
                                search_term,
                                None,
                            )

                            self.search_activated = True
                    else:
                        fn.logger.info("Search found %s results" % 0)
                        self.searchentry.grab_focus()

                        message_dialog = MessageDialog(
                            "Info",
                            "Search returned 0 results",
                            "Failed to find search term inside the package name or description.",
                            "Try to search again using another term",
                            "info",
                            False,
                        )

                        message_dialog.show_all()
                        message_dialog.run()
                        message_dialog.hide()

                elif self.search_activated == True:
                    GUI.setup_gui(
                        self,
                        Gtk,
                        Gdk,
                        GdkPixbuf,
                        base_dir,
                        os,
                        Pango,
                        None,
                    )
                    self.search_activated = False
            except Exception as err:
                fn.logger.error("Exception in on_search_activated(): %s" % err)

            finally:
                if self.search_activated == True:
                    self.search_queue.task_done()

    def on_search_cleared(self, searchentry, icon_pos, event):
        if self.search_activated:
            GUI.setup_gui(
                self,
                Gtk,
                Gdk,
                GdkPixbuf,
                base_dir,
                os,
                Pango,
                None,
            )

        self.searchentry.set_placeholder_text("Search...")

        self.search_activated = False

    # =====================================================
    #               RESTART/QUIT BUTTON
    # =====================================================

    def on_close(self, widget, data):
        # to preserve settings, save current options to conf file inside $HOME/.config/sofirem/sofirem.yaml

        settings = Settings(self.display_versions, self.display_package_progress)
        settings.write_config_file()

        # make a final installed packages file inside /var/log/sofirem/
        # this allows a before/after comparison
        # fn.on_close_create_packages_file()

        if os.path.exists(fn.sofirem_lockfile):
            os.unlink(fn.sofirem_lockfile)

        if os.path.exists(fn.sofirem_pidfile):
            os.unlink(fn.sofirem_pidfile)

        # see the comment in fn.terminate_pacman()
        fn.terminate_pacman()

        Gtk.main_quit()
        print(
            "---------------------------------------------------------------------------"
        )
        print("Thanks for using Sofirem")
        print("Report issues to make it even better")
        print(
            "---------------------------------------------------------------------------"
        )
        print("You can report issues on https://discord.gg/stBhS4taje")
        print(
            "---------------------------------------------------------------------------"
        )

    # ====================================================================
    #                     Button Functions
    # ====================================================================
    # Given what this function does, it might be worth considering making it a
    # thread so that the app doesn't block while installing/uninstalling is happening.

    def app_toggle(self, widget, active, package):
        # switch widget is currently toggled off

        if widget.get_state() == False and widget.get_active() == True:
            if len(package.name) > 0:
                inst_str = [
                    "pacman",
                    "-S",
                    package.name,
                    "--needed",
                    "--noconfirm",
                ]

                if self.display_package_progress is True:
                    if fn.check_pacman_lockfile():
                        widget.set_state(False)
                        widget.set_active(False)
                        proc = fn.get_pacman_process()

                        message_dialog = MessageDialog(
                            "Warning",
                            "Sofirem cannot proceed pacman lockfile found",
                            "Pacman cannot lock the db, a lockfile is found inside %s"
                            % fn.pacman_lockfile,
                            "Pacman is running: %s" % proc,
                            "warning",
                            False,
                        )

                        message_dialog.show_all()
                        message_dialog.run()
                        message_dialog.hide()
                        return True
                    else:
                        package_metadata = fn.get_package_information(package.name)

                        if (
                            type(package_metadata) is str
                            and package_metadata.strip()
                            == "error: package '%s' was not found" % package.name
                        ):
                            self.package_found = False
                            fn.logger.warning(
                                "The package %s was not found in any configured Pacman repositories"
                                % package.name
                            )
                            fn.logger.warning("Package install cannot continue")

                            message_dialog = MessageDialog(
                                "Error",
                                "Pacman repository error: package '%s' was not found"
                                % package.name,
                                "Sofirem cannot process the request",
                                "Are the correct pacman mirrorlists configured ?",
                                "error",
                                False,
                            )
                            message_dialog.show_all()
                            message_dialog.run()
                            message_dialog.hide()

                            widget.set_state(False)
                            widget.set_active(False)

                            return True
                        else:
                            widget.set_state(True)
                            widget.set_active(True)

                            progress_dialog = ProgressDialog(
                                "install",
                                package,
                                " ".join(inst_str),
                                package_metadata,
                            )

                            progress_dialog.show_all()

                            self.pkg_queue.put(
                                (
                                    package,
                                    "install",
                                    widget,
                                    inst_str,
                                    progress_dialog,
                                ),
                            )

                            th = fn.threading.Thread(
                                name="thread_pkginst",
                                target=fn.install,
                                args=(self,),
                                daemon=True,
                            )

                            th.start()
                            fn.logger.debug("Package-install thread started")

                else:
                    progress_dialog = None
                    widget.set_sensitive(False)

                widget.set_active(True)
                widget.set_state(True)

                fn.logger.info("Package to install : %s" % package.name)

                # another pacman transaction is running, add items to the holding queue
                if (
                    fn.check_pacman_lockfile() is True
                    and self.display_package_progress is False
                ):
                    self.pkg_holding_queue.put(
                        (
                            package,
                            "install",
                            widget,
                            inst_str,
                            progress_dialog,
                        ),
                    )

                    if fn.is_thread_alive("thread_check_holding_queue") is False:
                        th = fn.threading.Thread(
                            target=fn.check_holding_queue,
                            name="thread_check_holding_queue",
                            daemon=True,
                            args=(self,),
                        )

                        th.start()
                        fn.logger.debug("Check-holding-queue thread started")
                elif self.display_package_progress is False:
                    self.pkg_queue.put(
                        (
                            package,
                            "install",
                            widget,
                            inst_str,
                            progress_dialog,
                        ),
                    )

                    th = fn.threading.Thread(
                        name="thread_pkginst",
                        target=fn.install,
                        args=(self,),
                        daemon=True,
                    )

                    th.start()
                    fn.logger.debug("Package-install thread started")

        # switch widget is currently toggled on
        if widget.get_state() == True and widget.get_active() == False:
            # Uninstall the package

            if len(package.name) > 0:
                uninst_str = ["pacman", "-Rs", package.name, "--noconfirm"]

                fn.logger.info("Package to remove : %s" % package.name)

                if fn.check_pacman_lockfile():
                    widget.set_state(True)
                    widget.set_active(True)

                    fn.logger.info("Pacman lockfile found, uninstall aborted")

                    GLib.idle_add(
                        self.show_lockfile_message_dialog,
                        priority=GLib.PRIORITY_DEFAULT,
                    )

                    return True

                if self.display_package_progress is True:
                    package_metadata = fn.get_package_information(package.name)

                    progress_dialog = ProgressDialog(
                        "uninstall",
                        package,
                        " ".join(uninst_str),
                        package_metadata,
                    )

                    progress_dialog.show_all()
                else:
                    progress_dialog = None

                widget.set_active(False)
                widget.set_state(False)

                self.pkg_queue.put(
                    (
                        package,
                        "uninstall",
                        widget,
                        uninst_str,
                        progress_dialog,
                    ),
                )

                th = fn.threading.Thread(
                    name="thread_pkgrem",
                    target=fn.uninstall,
                    args=(self,),
                    daemon=True,
                )

                th.start()
                fn.logger.debug("Package-uninstall thread started")

        # fn.print_running_threads()

        # return True to prevent the default handler from running
        return True

        # App_Frame_GUI.GUI(self, Gtk, vboxStack1, fn, category, package_file)
        # widget.get_parent().get_parent().get_parent().get_parent().get_parent().get_parent().get_parent().queue_redraw()
        # self.gui.hide()
        # self.gui.queue_redraw()
        # self.gui.show_all()

    def show_lockfile_message_dialog(self):
        proc = fn.get_pacman_process()
        message_dialog = MessageDialog(
            "Warning",
            "Sofirem cannot proceed pacman lockfile found",
            "Pacman cannot lock the db, a lockfile is found inside %s"
            % fn.pacman_lockfile,
            "Process running = %s" % proc,
            "warning",
            False,
        )

        message_dialog.show_all()
        message_dialog.run()
        message_dialog.hide()

        message_dialog.destroy()

    def recache_clicked(self, widget):
        # Check if cache is out of date. If so, run the re-cache, if not, don't.
        # pb = ProgressBarWindow()
        # pb.show_all()
        # pb.set_text("Updating Cache")
        # pb.reset_timer()

        fn.logger.info("Recache applications - start")

        fn.cache_btn()

    # ================================================================
    #                   SETTINGS
    # ================================================================

    def on_package_search_clicked(self, widget):
        fn.logger.debug("Showing Package Search window")
        self.toggle_popover()

        package_search_win = PackageSearchWindow()
        package_search_win.show_all()

    def on_arcolinux_iso_packages_clicked(self, widget):
        fn.logger.debug("Showing ArcoLinux ISO Packages window")
        arcolinux_iso_packages_window = ISOPackagesWindow()
        arcolinux_iso_packages_window.show()

    def on_about_app_clicked(self, widget):
        fn.logger.debug("Showing About dialog")
        self.toggle_popover()

        about = AboutDialog()
        about.run()
        about.hide()
        about.destroy()

    def on_packages_export_clicked(self, widget):
        self.toggle_popover()

        dialog_packagelist = PackageListDialog()
        dialog_packagelist.show_all()

    def on_packages_import_clicked(self, widget):
        self.toggle_popover()
        try:
            if not os.path.exists(fn.pacman_lockfile):
                package_file = "%s/packages-x86_64.txt" % (fn.export_dir,)
                package_import_logfile = "%spackages-install-status-%s-%s.log" % (
                    fn.log_dir,
                    fn.datetime.today().date(),
                    fn.datetime.today().time().strftime("%H-%M-%S"),
                )

                if os.path.exists(package_file):
                    # check we have a valid file
                    lines = None
                    with open(package_file, encoding="utf-8", mode="r") as f:
                        lines = f.readlines()

                    if lines is not None:
                        if (
                            "# This file was auto-generated by the ArchLinux Tweak Tool on"
                            in lines[0]
                            or "# This file was auto-generated by Sofirem on"
                            in lines[0]
                        ):
                            fn.logger.info("Package list file is valid")
                            packages_list = []
                            for line in lines:
                                if not line.startswith("#"):
                                    packages_list.append(line.strip())

                            if len(packages_list) > 0:
                                dialog_package_import = PackagesImportDialog(
                                    package_file,
                                    packages_list,
                                    package_import_logfile,
                                )
                                dialog_package_import.show_all()

                    else:
                        message_dialog = MessageDialog(
                            "Error",
                            "Package file is not valid %s" % package_file,
                            "Export a list of packages first using the Show Installed Packages button",
                            "",
                            "error",
                            False,
                        )

                        message_dialog.show_all()
                        message_dialog.run()
                        message_dialog.hide()
                else:
                    message_dialog = MessageDialog(
                        "Warning",
                        "Cannot locate export package file %s" % package_file,
                        "Export a list of packages first using the Show Installed Packages button",
                        "",
                        "warning",
                        False,
                    )

                    message_dialog.show_all()
                    message_dialog.run()
                    message_dialog.hide()
            else:
                message_dialog = MessageDialog(
                    "Error",
                    "Pacman lock file found %s" % fn.pacman_lockfile,
                    "Cannot proceed, another pacman process is running",
                    "",
                    "error",
                    False,
                )

                message_dialog.show_all()
                message_dialog.run()
                message_dialog.hide()
        except Exception as e:
            fn.logger.error("Exception in on_packages_import_clicked(): %s" % e)

    # show/hide popover
    def toggle_popover(self):
        if self.popover.get_visible():
            self.popover.hide()
        else:
            self.popover.show_all()

    def on_settings_clicked(self, widget):
        self.toggle_popover()

    # ArcoLinux keys, mirrors setup

    def arco_keyring_toggle(self, widget, data):
        # toggle is currently off, add keyring
        if widget.get_state() == False and widget.get_active() == True:
            fn.logger.info("Installing ArcoLinux keyring")
            install_keyring = fn.install_arco_keyring()

            if install_keyring == 0:
                fn.logger.info("Installation of ArcoLinux keyring = OK")
                rc = fn.add_arco_repos()
                if rc == 0:
                    fn.logger.info("ArcoLinux repos added into %s" % fn.pacman_conf)
                    widget.set_active(True)
                else:
                    message_dialog = MessageDialog(
                        "Error",
                        "Failed to update pacman conf",
                        "Errors occurred during update of the pacman config file",
                        rc,
                        "error",
                        True,
                    )

                    message_dialog.show_all()
                    message_dialog.run()
                    message_dialog.hide()

                    widget.set_active(False)
                    widget.set_state(False)

                    return True

            else:
                message_dialog = MessageDialog(
                    "Error",
                    "Failed to install ArcoLinux keyring",
                    "Errors occurred during install of the ArcoLinux keyring",
                    "Command run = %s\n\n Error = %s"
                    % (install_keyring["cmd_str"], install_keyring["output"]),
                    "error",
                    True,
                )

                message_dialog.show_all()
                message_dialog.run()
                message_dialog.hide()

                widget.set_active(False)
                widget.set_state(False)

                return True
        # toggle is currently on
        if widget.get_state() == True and widget.get_active() == False:
            remove_keyring = fn.remove_arco_keyring()

            if remove_keyring == 0:
                fn.logger.info("Removing ArcoLinux keyring OK")

                rc = fn.remove_arco_repos()
                if rc == 0:
                    fn.logger.info("ArcoLinux repos removed from %s" % fn.pacman_conf)
                    widget.set_active(False)
                else:
                    message_dialog = MessageDialog(
                        "Error",
                        "Failed to update pacman conf",
                        "Errors occurred during update of the pacman config file",
                        rc,
                        "error",
                        True,
                    )

                    message_dialog.show_all()
                    message_dialog.run()
                    message_dialog.hide()

                    widget.set_active(True)
                    widget.set_state(True)

                    return True
            else:
                fn.logger.error("Failed to remove ArcoLinux keyring")

                message_dialog = MessageDialog(
                    "Error",
                    "Failed to remove ArcoLinux keyring",
                    "Errors occurred during removal of the ArcoLinux keyring",
                    "Command run = %s\n\n Error = %s"
                    % (remove_keyring["cmd_str"], remove_keyring["output"]),
                    "error",
                    True,
                )

                message_dialog.show_all()
                message_dialog.run()
                message_dialog.hide()

                widget.set_active(False)
                widget.set_state(False)

                return True

    def arco_mirrorlist_toggle(self, widget, data):
        # self.toggle_popover()

        # toggle is currently off

        if widget.get_state() == False and widget.get_active() == True:
            widget.set_active(True)
            widget.set_state(True)

            # before installing the mirrorlist make sure the pacman.conf file does not have any references to /etc/pacman.d/arcolinux-mirrorlist
            # otherwise the mirrorlist package will not install
            rc_remove = fn.remove_arco_repos()
            if rc_remove == 0:
                install_mirrorlist = fn.install_arco_mirrorlist()

                if install_mirrorlist == 0:
                    fn.logger.info("Installation of ArcoLinux mirrorlist = OK")

                    rc_add = fn.add_arco_repos()
                    if rc_add == 0:
                        fn.logger.info("ArcoLinux repos added into %s" % fn.pacman_conf)
                        self.pacman_db_sync()

                    else:
                        message_dialog = MessageDialog(
                            "Error",
                            "Failed to update pacman conf",
                            "Errors occurred during update of the pacman config file",
                            rc_add,
                            "error",
                            True,
                        )

                        message_dialog.show_all()
                        message_dialog.run()
                        message_dialog.hide()

                        widget.set_active(False)
                        widget.set_state(False)

                        return True

                else:
                    fn.logger.error("Failed to install ArcoLinux mirrorlist")

                    message_dialog = MessageDialog(
                        "Error",
                        "Failed to install ArcoLinux mirrorlist",
                        "Errors occurred during install of the ArcoLinux mirrorlist",
                        "Command run = %s\n\n Error = %s"
                        % (install_mirrorlist["cmd_str"], install_mirrorlist["output"]),
                        "error",
                        True,
                    )
                    message_dialog.show_all()
                    message_dialog.run()
                    message_dialog.hide()

                    widget.set_active(False)
                    widget.set_state(False)

                    return True
            else:
                message_dialog = MessageDialog(
                    "Error",
                    "Failed to update pacman conf",
                    "Errors occurred during update of the pacman config file",
                    rc,
                    "error",
                    True,
                )

                message_dialog.show_all()
                message_dialog.run()
                message_dialog.hide()

                widget.set_active(False)
                widget.set_state(False)

                return True
        # toggle is currently on
        if widget.get_state() == True and widget.get_active() == False:
            widget.set_active(False)
            widget.set_state(False)

            fn.logger.info("Removing ArcoLinux mirrorlist")

            remove_mirrorlist = fn.remove_arco_mirrorlist()

            if remove_mirrorlist == 0:
                fn.logger.info("Removing ArcoLinux mirrorlist OK")

                rc = fn.remove_arco_repos()
                if rc == 0:
                    fn.logger.info("ArcoLinux repos removed from %s" % fn.pacman_conf)
                    widget.set_active(False)
                else:
                    message_dialog = MessageDialog(
                        "Error",
                        "Failed to update pacman conf",
                        "Errors occurred during update of the pacman config file",
                        rc,
                        "error",
                        True,
                    )

                    message_dialog.show_all()
                    message_dialog.run()
                    message_dialog.hide()

                    widget.set_active(True)
                    widget.set_state(True)

                    return True
            else:
                fn.logger.error("Failed to remove ArcoLinux mirrorlist")

                message_dialog = MessageDialog(
                    "Error",
                    "Failed to remove ArcoLinux mirrorlist",
                    "Errors occurred during removal of the ArcoLinux mirrorlist",
                    "Command run = %s\n\n Error = %s"
                    % (remove_mirrorlist["cmd_str"], remove_mirrorlist["output"]),
                    "error",
                    True,
                )

                message_dialog.show_all()
                message_dialog.run()
                message_dialog.hide()

                widget.set_active(True)
                widget.set_state(True)

                return True

        return True

    def version_toggle(self, widget, data):
        if widget.get_active() == True:
            fn.logger.debug("Showing package versions")

            self.display_versions = True
            GLib.idle_add(
                self.refresh_main_gui,
                priority=GLib.PRIORITY_DEFAULT,
            )
        else:
            fn.logger.debug("Hiding package versions")
            self.display_versions = False
            GLib.idle_add(
                self.refresh_main_gui,
                priority=GLib.PRIORITY_DEFAULT,
            )

    def refresh_main_gui(self):
        self.remove(self.vbox)
        GUI.setup_gui(self, Gtk, Gdk, GdkPixbuf, base_dir, os, Pango, None)
        self.show_all()

    def on_pacman_log_clicked(self, widget):
        try:
            self.toggle_popover()

            thread_addlog = "thread_addPacmanLogQueue"
            self.thread_add_pacmanlog_alive = fn.is_thread_alive(thread_addlog)

            if self.thread_add_pacmanlog_alive == False:
                fn.logger.info("Starting thread to monitor Pacman Log file")

                th_add_pacmanlog_queue = fn.threading.Thread(
                    name=thread_addlog,
                    target=fn.add_pacmanlog_queue,
                    args=(self,),
                    daemon=True,
                )
                th_add_pacmanlog_queue.start()

            if self.thread_add_pacmanlog_alive is True:
                # need to recreate the textview, can't use existing reference as it throws a seg fault

                self.textview_pacmanlog = Gtk.TextView()
                self.textview_pacmanlog.set_property("editable", False)
                self.textview_pacmanlog.set_property("monospace", True)
                self.textview_pacmanlog.set_border_width(10)
                self.textview_pacmanlog.set_vexpand(True)
                self.textview_pacmanlog.set_hexpand(True)

                # use the reference to the text buffer initialized before the logtimer thread started
                self.textview_pacmanlog.set_buffer(self.textbuffer_pacmanlog)

                window_pacmanlog = PacmanLogWindow(
                    self.textview_pacmanlog,
                    self.modelbtn_pacmanlog,
                )
                window_pacmanlog.show_all()

                self.start_logtimer = window_pacmanlog.start_logtimer

            else:
                # keep a handle on the textbuffer, this is needed again later, if the pacman log file dialog is closed
                # since the textbuffer will already hold textdata at that point

                # textview is used inside another thread to update as the pacmanlog file is read into memory
                self.textbuffer_pacmanlog = Gtk.TextBuffer()

                self.textview_pacmanlog = Gtk.TextView()
                self.textview_pacmanlog.set_property("editable", False)
                self.textview_pacmanlog.set_property("monospace", True)
                self.textview_pacmanlog.set_border_width(10)
                self.textview_pacmanlog.set_vexpand(True)
                self.textview_pacmanlog.set_hexpand(True)

                self.textview_pacmanlog.set_buffer(self.textbuffer_pacmanlog)

                window_pacmanlog = PacmanLogWindow(
                    self.textview_pacmanlog,
                    self.modelbtn_pacmanlog,
                )
                window_pacmanlog.show_all()

            thread_logtimer = "thread_startLogTimer"
            thread_logtimer_alive = False

            thread_logtimer_alive = fn.is_thread_alive(thread_logtimer)

            # a flag to indicate that the textview will need updating, used inside fn.start_log_timer
            self.start_logtimer = True

            if thread_logtimer_alive == False:
                th_logtimer = fn.threading.Thread(
                    name=thread_logtimer,
                    target=fn.start_log_timer,
                    args=(self, window_pacmanlog),
                    daemon=True,
                )
                th_logtimer.start()

            self.thread_add_pacmanlog_alive = True
            self.modelbtn_pacmanlog.set_sensitive(False)

        except Exception as e:
            fn.logger.error("Exception in on_pacman_log_clicked() : %s" % e)

    def package_progress_toggle(self, widget, data):
        if widget.get_active() is True:
            self.display_package_progress = True
        if widget.get_active() is False:
            self.display_package_progress = False


# ====================================================================
#                       MAIN
# ====================================================================


def signal_handler(sig, frame):
    fn.logger.info("Sofirem is closing.")
    if os.path.exists("/tmp/sofirem.lock"):
        os.unlink("/tmp/sofirem.lock")

    if os.path.exists("/tmp/sofirem.pid"):
        os.unlink("/tmp/sofirem.pid")
    Gtk.main_quit(0)


# These should be kept as it ensures that multiple installation instances can't be run concurrently.
if __name__ == "__main__":
    try:
        signal.signal(signal.SIGINT, signal_handler)

        if not os.path.isfile("/tmp/sofirem.lock"):
            with open("/tmp/sofirem.pid", "w") as f:
                f.write(str(os.getpid()))

            style_provider = Gtk.CssProvider()
            style_provider.load_from_path(base_dir + "/sofirem.css")

            Gtk.StyleContext.add_provider_for_screen(
                Gdk.Screen.get_default(),
                style_provider,
                Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION,
            )
            w = Main()
            w.show_all()

            fn.logger.info("App Started")

            Gtk.main()
        else:
            fn.logger.info("Sofirem lock file found")

            md = Gtk.MessageDialog(
                parent=Main(),
                flags=0,
                message_type=Gtk.MessageType.INFO,
                buttons=Gtk.ButtonsType.YES_NO,
                text="Sofirem Lock File Found",
            )
            md.format_secondary_markup(
                "A Sofirem lock file has been found. This indicates there is already an instance of <b>Sofirem</b> running.\n\
                Click 'Yes' to remove the lock file and try running again"
            )  # noqa

            result = md.run()
            md.destroy()

            if result in (Gtk.ResponseType.OK, Gtk.ResponseType.YES):
                pid = ""
                if os.path.exists(fn.sofirem_pidfile):
                    with open("/tmp/sofirem.pid", "r") as f:
                        line = f.read()
                        pid = line.rstrip().lstrip()

                    if fn.check_if_process_running(int(pid)):
                        # needs to be fixed - todo

                        # md2 = Gtk.MessageDialog(
                        #     parent=Main,
                        #     flags=0,
                        #     message_type=Gtk.MessageType.INFO,
                        #     buttons=Gtk.ButtonsType.OK,
                        #     title="Application Running!",
                        #     text="You first need to close the existing application",
                        # )
                        # md2.format_secondary_markup(
                        #     "You first need to close the existing application"
                        # )
                        # md2.run()
                        fn.logger.info(
                            "You first need to close the existing application"
                        )
                    else:
                        os.unlink("/tmp/sofirem.lock")
                        sys.exit(1)
                else:
                    # in the rare event that the lock file is present, but the pid isn't
                    os.unlink("/tmp/sofirem.lock")
                    sys.exit(1)
            else:
                sys.exit(1)
    except Exception as e:
        fn.logger.error("Exception in __main__: %s" % e)
