# ----- imports -----
import os
import json
import subprocess
from pathlib import Path
from gi.repository import Gtk, Adw
from .row_items import RowItem

# ----- variables ------
opj = os.path.join
folder_explainer_blurb = "Control access level for exposed folders.\n " \
                         "\"D\" = access is disabled\n " \
                         "\"RO\" = access is Read Only\n " \
                         "\"RW\" = access is Read / Write\n"
enable_disable_explainer_blurb = "Enable or Disable the umtp-responder service"
start_stop_restart_explainer_blurb = "Start, Stop, or Restart the umtp-responder service"
service_state = "disabled"  # default state, overridden when user_prefs are loaded
service_state_has_loaded = False
exposed_folders = [
    {"index": 0, "path": "/tmp", "access_level": "ro"}
]  # default folder, overridden when user_prefs are loaded
user_prefs_defaults = {
    "exposed_folders": [
        {"index": 0, "path": "/tmp", "access_level": "ro"}
    ],
    "service_state": "disabled"
}


# ----- functions -----
def get_file_loc():
    # get the current path on disk
    this_loc = os.path.dirname(os.path.abspath(__file__))
    return this_loc


def get_logname():
    p = os.popen('logname', 'r')
    username = str.strip(p.readline())
    p.close()
    return username


def write_the_conf_to_disk(cf):
    uh = Path.home()
    cf_orig = f"{uh}/.config/umtp-responder-gui/umtprd.conf"
    cf_bk = f"{uh}/.config/umtp-responder-gui/umtprd.conf.bk"
    # move the existing conf file to a backup if it exists
    if os.path.exists(cf_orig):
        os.rename(cf_orig, cf_bk)
    # write the new conf file to disk
    with open(cf_orig, "w") as f:
        f.write(cf)


# ----- classes -----

@Gtk.Template(filename=f'{get_file_loc()}/_folder_content.ui')
class FolderContent(Gtk.Box):
    __gtype_name__ = "FolderContent"

    folder_explainer_label = Gtk.Template.Child()
    folder_list_box = Gtk.Template.Child()
    folder_saved_button = Gtk.Template.Child()
    folder_needs_save_button = Gtk.Template.Child()

    def __init__(self, revealer=None, label=None):
        Gtk.Box.__init__(self)
        self.notification_revealer = revealer
        self.notification_label = label
        self.exposed_folders = exposed_folders
        self.folder_explainer_label.set_label(folder_explainer_blurb)
        self.add_folders_to_list_box()

    def add_folders_to_list_box(self):
        # loop through the exposed_folders
        # make a new listrowbox make a RowItem
        # add all the rowitems to the folderlistbox
        for i in self.exposed_folders:
            # create the rowitem from the saved prefs info
            ri = RowItem(i["path"], i["access_level"])
            # get the del_button reference in the row
            # connect it's 'clicked' signal to the delete_row_item function
            # it needs to pass the index as part of the c_data
            c_data = {
                "deleted_row": i["index"],
                "folder_list_box": self.folder_list_box
            }
            db = ri.get_del_button_reference()
            db.connect("clicked", self.delete_row_item, c_data)
            # get the toggle button refs, connect their "toggled" signals to the
            # access_level_toggled function
            for b in ri.get_references_for_toggle_buttons():
                b.connect("toggled", self.access_level_toggled)
            # lbr.set_name(i["path"])  # set the name of the row widget to be the folder path
            ri.set_halign(Gtk.Align(3))  # set the rowitem to be CENTER alignment
            # lbr.set_activatable(False)
            # lbr.set_selectable(False)
            # lbr.show()
            # self.folder_list_box.insert(lbr, i["index"])
            self.folder_list_box.insert(ri, i["index"])

    def add_single_item_to_list(self, folder_path):
        # create a new rowitem
        ri = RowItem(folder_path, "disabled")
        # get the number of children that the list has
        lbrc = self.folder_list_box.observe_children()
        num = lbrc.get_n_items()
        # get the del_button reference in the row
        # connect it's 'clicked' signal to the delete_row_item function
        # it needs to pass the index as part of the c_data
        # since the list/index starts at 0, then the number of children will be
        # the next index number. ie if there are two children, then the first one's index is
        # 0, the second one's index is 1, and the new row here then is index 2 (same as num)
        c_data = {
            "deleted_row": num,
            "folder_list_box": self.folder_list_box
        }
        db = ri.get_del_button_reference()
        db.connect("clicked", self.delete_row_item, c_data)
        # get the toggle button refs, connect their "toggled" signals to the
        # access_level_toggled function
        for b in ri.get_references_for_toggle_buttons():
            b.connect("toggled", self.access_level_toggled)
        ri.set_halign(Gtk.Align(3))  # set the rowitem to be CENTER alignment
        self.folder_list_box.insert(ri, num)
        self.swap_save_button_state(1)

    def delete_row_item(self, delete_buttton_widget, callback_data):
        # connect to the listbox
        flb = callback_data["folder_list_box"]
        # get the row at the index in the callback_data
        r = flb.get_row_at_index(callback_data["deleted_row"])
        # destroy that row (this is only visual, the save button will
        # need to be clicked to keep the change, otherwise the row
        # will be recreated from stored settings
        flb.remove(r)
        self.swap_save_button_state(1)

    @Gtk.Template.Callback()
    def on_folder_needs_save_button_clicked(self, folder_save_button_widget):
        folders_to_save_to_disk = []
        # get a ListModel of all the ListBox children
        lbrc = self.folder_list_box.observe_children()
        # get the num of children
        num = lbrc.get_n_items()
        # loop through the children
        # from each child get the
        # path,
        # the access_level (basically the selected toggle in the gui)
        # and the index in the listbox
        x = 0
        while x < num:
            c = lbrc.get_item(x)
            c_path = c.folder_path
            c_index = c.get_index()
            c_access_level = c.get_access_level()
            # create the dict to hold that info
            c_dict = {"index": c_index, "path": c_path, "access_level": c_access_level}
            # append those to the list
            folders_to_save_to_disk.append(c_dict)
            x += 1

        # save the folders to the user_prefs
        # set the path
        user_home = Path.home()
        upj = Path(f"{user_home}/.config/umtp-responder-gui/user_prefs.json")
        prefs_to_save = {
            "service_state": service_state,
            "exposed_folders": folders_to_save_to_disk
        }

        # write that to disk
        with open(upj, "w") as f:
            prefs = json.dumps(prefs_to_save)
            f.write(prefs)

        # update the in-memory exposed_folders list
        global exposed_folders
        exposed_folders = folders_to_save_to_disk.copy()

        # build the conf file from the base+ appropriate tail template(s)
        # load the base template
        base_template = opj(get_file_loc(), "templates/base")
        with open(base_template, "r") as f:
            bt = f.read()
        # loop through the exposed_folders list and create the
        # strings to add to the base_template
        # then concat them all into a new string var
        exposed_folders_string = ""
        for ef in exposed_folders:
            # get the display name from the path
            path_split = ef["path"].split('/')
            display_name = path_split.pop((len(path_split) - 1))
            if ef["access_level"] == "disabled":
                s = f'# storage "{ef["path"]}" "{display_name}" "{ef["access_level"]}" \n'
            else:
                s = f'storage "{ef["path"]}" "{display_name}" "{ef["access_level"]}" \n'
            exposed_folders_string += s

        # combine the template and folder strings
        conf_file = bt + exposed_folders_string

        # write the file to disk
        write_the_conf_to_disk(conf_file)

        # get the username, should be 'alarm' but in case we're running as some other name
        user_name = get_logname()

        # update the config
        subprocess.run(['pkexec', 'umtp-responder-control', 'update', user_name])

        # swap the button back to normal
        self.swap_save_button_state(0)

    def access_level_toggled(self, toggle_button_widget):
        self.swap_save_button_state(1)

    def swap_save_button_state(self, state):
        # 0 == everything has been saved, reset to normal
        # 1 == changes need to be saved, set button to "suggested-action"
        if state == 0:
            self.folder_saved_button.set_visible(True)
            self.folder_needs_save_button.set_visible(False)
        elif state == 1:
            self.folder_needs_save_button.set_visible(True)
            self.folder_saved_button.set_visible(False)


@Gtk.Template(filename=f"{get_file_loc()}/_service_content.ui")
class ServiceContent(Gtk.Box):
    __gtype_name__ = "ServiceContent"

    enable_disable_explainer_label = Gtk.Template.Child()
    enable_disable_choices_hbox = Gtk.Template.Child()
    enabled_toggle_button = Gtk.Template.Child()
    disabled_toggle_button = Gtk.Template.Child()
    start_stop_restart_explainer_label = Gtk.Template.Child()
    start_stop_restart_vbox = Gtk.Template.Child()
    start_button = Gtk.Template.Child()
    stop_button = Gtk.Template.Child()
    restart_button = Gtk.Template.Child()

    def __init__(self, revealer=None, label=None):
        Gtk.Box.__init__(self)
        self.notification_revealer = revealer
        self.notification_label = label
        self.enabled_toggle_button.set_group(self.disabled_toggle_button)
        self.set_toggle_buttons_to_service_state()
        self.enable_disable_explainer_label.set_label(enable_disable_explainer_blurb)
        self.start_stop_restart_explainer_label.set_label(start_stop_restart_explainer_blurb)

    @Gtk.Template.Callback()
    def on_start_service_button_clicked(self, start_service_widget):
        subprocess.run(['pkexec', 'umtp-responder-control', 'start'])
        self.show_notification("Service Started")

    @Gtk.Template.Callback()
    def on_stop_service_button_clicked(self, stop_service_widget):
        subprocess.run(['pkexec', 'umtp-responder-control', 'stop'])
        self.show_notification("Service Stopped")

    @Gtk.Template.Callback()
    def on_restart_service_button_clicked(self, restart_service_widget):
        subprocess.run(['pkexec', 'umtp-responder-control', 'restart'])
        self.show_notification("Service Restarted")

    @Gtk.Template.Callback()
    def on_enable_service_button_clicked(self, enable_service_widget):
        # check if the service_state_has_loaded is true (meaning the gui has launched and
        # we're have already set the toggle button state) if so run the subprocess
        if service_state_has_loaded:
            subprocess.run(['pkexec', 'umtp-responder-control', 'enable'])
            self.show_notification("Service Enabled")
            self.save_service_state_to_user_prefs(self, "enabled")

    @Gtk.Template.Callback()
    def on_disable_service_button_clicked(self, disable_service_widget):
        # check if the service_state_has_loaded is true (meaning the gui has launched and
        # we're have already set the toggle button state) if so run the subprocess
        if service_state_has_loaded:
            subprocess.run(['pkexec', 'umtp-responder-control', 'disable'])
            self.show_notification("Service Disabled")
            self.save_service_state_to_user_prefs(self, "disabled")

    def set_toggle_buttons_to_service_state(self):
        # set the toggle buttons for the service state
        if service_state == "enabled":
            self.enabled_toggle_button.set_active(True)
        elif service_state == "disabled":
            self.disabled_toggle_button.set_active(True)
        # set the has_loaded var to true
        global service_state_has_loaded
        service_state_has_loaded = True

    @staticmethod
    def save_service_state_to_user_prefs(self, newstate):
        # set the path
        user_home = Path.home()
        upj = Path(f"{user_home}/.config/umtp-responder-gui/user_prefs.json")
        prefs_to_save = {
            "service_state": newstate,
            "exposed_folders": exposed_folders
        }
        # write that to disk
        with open(upj, "w") as f:
            prefs = json.dumps(prefs_to_save)
            f.write(prefs)

    def show_notification(self, message):
        self.notification_label.set_label(message)
        self.notification_revealer.set_reveal_child(True)


@Gtk.Template(filename=f"{get_file_loc()}/_folder_chooser_dialog.ui")
class FolderChooser(Gtk.Dialog):
    __gtype_name__ = "FolderChooser"

    folder_chooser_fcwidget = Gtk.Template.Child()
    folder_chooser_cancel_button = Gtk.Template.Child()
    folder_chooser_select_button = Gtk.Template.Child()

    def __init__(self, folder_content_widget):
        Gtk.Dialog.__init__(self)
        self.fcw = folder_content_widget

    @Gtk.Template.Callback()
    def on_folder_chooser_cancel_button_clicked(self, cancel_button_widget):
        self.hide()

    @Gtk.Template.Callback()
    def on_folder_chooser_select_button_clicked(self, select_button_widget):
        # get the currently selected folder
        # since the FileChooserWidget is set to action == select_folder
        # get_file() actually returns the selected /folder/
        cf = self.folder_chooser_fcwidget.get_file()
        # pass the path to the folder_content_widget
        # which will add the folder to the listbox
        self.fcw.add_single_item_to_list(cf.get_path())
        self.hide()


@Gtk.Template(filename=f'{get_file_loc()}/_main_window.ui')
class MainWindow(Adw.ApplicationWindow):
    __gtype_name__ = "MainWindow"

    main_window_overlay = Gtk.Template.Child()
    main_window_scrolled_window = Gtk.Template.Child()
    main_window_viewport = Gtk.Template.Child()
    main_window_overall_box = Gtk.Template.Child()
    notification_revealer = Gtk.Template.Child()
    notification_label = Gtk.Template.Child()
    header_bar = Gtk.Template.Child()
    main_window_flap = Gtk.Template.Child()
    flap_buttons_vbox = Gtk.Template.Child()
    folder_button = Gtk.Template.Child()
    service_button = Gtk.Template.Child()
    content_tmp_vbox = Gtk.Template.Child()

    def __init__(self, **kwargs):
        Adw.ApplicationWindow.__init__(self, **kwargs)
        self.load_user_preferences()
        self.plus_button = Gtk.Button.new_with_label("+")
        self.plus_button.connect("clicked", self.on_plus_button_clicked)
        self.header_bar.pack_end(self.plus_button)
        self.folder_content = FolderContent(revealer=self.notification_revealer, label=self.notification_label)
        self.service_content = ServiceContent(revealer=self.notification_revealer, label=self.notification_label)
        self.folder_chooser = FolderChooser(self.folder_content)
        self.set_folder_content_displayed()

    @staticmethod
    def load_user_preferences():
        # test if the $HOME/.config/umtp-responder-gui/user_prefs.json file exists
        # make it if not, load the prefs if it is
        user_home = Path.home()
        urg_upd = Path(f"{user_home}/.config/umtp-responder-gui/")
        upj = Path(f"{user_home}/.config/umtp-responder-gui/user_prefs.json")
        if not Path.exists(upj):
            if not Path.exists(urg_upd):
                # make the directory
                Path.mkdir(urg_upd)
            else:
                # create the file and store defaults
                defaults_json = json.dumps(user_prefs_defaults)
                with open(upj, "w") as f:
                    f.write(defaults_json)
        else:
            with open(upj, "r") as f:
                data = f.read()
                saved_prefs = json.loads(data)
                global service_state
                global exposed_folders
                service_state = saved_prefs["service_state"]
                exposed_folders = saved_prefs["exposed_folders"]

    @Gtk.Template.Callback()
    def set_folder_content_displayed(self, widget=None):
        self.main_window_flap.set_content(self.folder_content)

    @Gtk.Template.Callback()
    def set_service_content_displayed(self, widget=None):
        self.main_window_flap.set_content(self.service_content)

    def on_plus_button_clicked(self, plus_button_widget):
        self.folder_chooser.set_transient_for(self)
        self.folder_chooser.show()

    @Gtk.Template.Callback()
    def on_close_notification_button_clicked(self, notification_close_button_widget):
        self.notification_revealer.set_reveal_child(False)