#!/usr/bin/python3
import gi, os, jwmkit_utils, tarfile, io, sys, re, shutil
from datetime import datetime
import xml.etree.ElementTree as ET
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk

# JWM Kit - A set of Graphical Apps to simplify use of JWM (Joe's Window Manager) <https://codeberg.org/JWMKit/JWM_Kit>
# Copyright © 2020-2022 Calvin Kent McNabb <apps.jwmkit@gmail.com>
#
# This file is part of JWM Kit.
#
# JWM Kit is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, VERSIONS 2,
# as published by the Free Software Foundation.
#
# JWM Kit is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with JWM Kit.  If not, see <https://www.gnu.org/licenses/>.


def date_to_text(date):
    home = os.path.expanduser("~")
    # convert the date based restore name to 'text' date
    # 2021-04-11_at_10:30  ---->  April 4, 2021 at 10:30

    def convert(date):
        time = date[-5:].replace('-', ':')
        date = datetime(int(date[:4]), int(date[5:7]), int(date[8:10]), 0, 0)
        date = '{:%a, %b %d %Y}'.format(date, time)
        return date, time

    try:
        date = convert(date)
    except ValueError:
        try:
            if os.path.isfile('{}/.config/jwmkit/restore/original.restore'.format(home)):
                date = os.path.getmtime('{}/.config/jwmkit/restore/original.restore'.format(home))
            else:
                date = os.path.getmtime('/etc/jwmkit/restore/original.restore')
            date = datetime.fromtimestamp(date).isoformat()[:16].replace('T', '_at_')
            date = convert(date)
        except (ValueError, FileNotFoundError, TypeError):
            return 'Unknown'
    time = date[1]
    date = date[0]
    return '{} at {}'.format(date, time)


def get_restore_path(r_point):
    home = os.path.expanduser('~')
    if not r_point[:3].isdigit():
        if os.path.isfile('{}/.config/jwmkit/restore/original.restore'.format(home)):
            r_path = '{}/.config/jwmkit/restore/original.restore'.format(home)
        else:
            r_path = '/etc/jwmkit/restore/original.restore'
    else:
        r_path = os.path.join(home, '.config/jwmkit/restore/{}.restore'.format(r_point))
    return r_path


def get_restore_points():
    # create a list of restore points from predefined locations.
    r_tmp = []
    home = os.path.expanduser('~')
    try:
        tmp = os.listdir(os.path.join(home, '.config/jwmkit/restore/'))
        for f in tmp:
            if f.startswith('20') and f.endswith('.restore'):
                if tarfile.is_tarfile(os.path.join(home, '.config/jwmkit/restore/', f)):
                    with tarfile.open(os.path.join(home, '.config/jwmkit/restore/', f), 'r:') as rp_tar:
                        r_list = rp_tar.getmembers()
                    r_list = [f.name for f in r_list]
                    if '_FILE_PATHS_.RFL' in r_list:
                        r_tmp.append(f[:-8])
        r_tmp.reverse()
    except FileNotFoundError:
        pass
    r_tmp.sort(reverse=True)
    try:
        if os.path.isfile('/etc/jwmkit/restore/original.restore'):
            r_tmp.append('Original  Install')
            # The string 'Original  Install' is used for comparison. If changed, you must change all occurrences
    except FileNotFoundError:
        pass
    return r_tmp


def get_restore_targets(r_point):
    # return 2 lists: target paths for all restore point, and missing files
    # path list returns empty if no restore file
    # list2 should return empty, if not files are missing
    r_files, missing = [], []
    home = os.path.expanduser('~')
    r_path = get_restore_path(r_point)
    try:
        if os.path.isfile(r_path):
            with tarfile.open(r_path, 'r:') as rp_tar:
                r_list = rp_tar.getmembers()
                r_files = rp_tar.extractfile('_FILE_PATHS_.RFL')
                r_files = r_files.read().decode('UTF-8')
            r_files = r_files.split('\n')
            r_list = [f.name for f in r_list]
            while '' in r_files: r_files.remove('')
            missing = [f for f in r_files if os.path.basename(f) not in r_list]
            for i, filename in enumerate(r_files):
                if filename[:5] == '$HOME':
                    r_files[i] = os.path.join(home, filename[6:])
    except (FileNotFoundError, tarfile.ReadError):
        pass
    return [r_files, missing]


def compare(file1, file2, r_path):
    home = os.path.expanduser('~')
    if file2[:5] == '$HOME':
        file2 = os.path.join(home, file1[6:])
    file1 = os.path.basename(file1)
    with tarfile.open(r_path, 'r:') as rp_tar:
       file1 = rp_tar.extractfile(file1)
       file1 = file1.read().decode('UTF-8')
    with open(file2) as f:
        file2 = f.read()
        if file1 != file2:
            return False
        return True


def rp_update_status(sources, r_point):
    # Determine if Restore point current config are the same
    targets = get_restore_targets(r_point)
    if targets[1]:
        # Return False if restore archive is corrupt or missing file(s).
        return False
    targets = targets[0]
    results = True
    r_path = get_restore_path(r_point)
    # return False if restore point does not have same number of files and current config
    if len(targets) != len(sources):
        return False
    # Sort lists so they will be in the same order.
    # files with identical names could be swapped. exception will be added
    targets = sorted(targets, key=lambda t: os.path.basename(t)[4:])
    sources = sorted(sources, key=lambda t: os.path.basename(t))
    for target, source in zip(targets, sources):
        results = compare(target, source, r_path)
        if not results:
            return results
    return results


def create_restore(f_names):
    # Create a restore point archive containing all config files
    home = os.path.expanduser('~')
    rp_name = '{}/.config/jwmkit/restore/{}.restore'\
        .format(home, str(datetime.now())[:16].replace(' ', '_at_').replace(':', '-'))
    rfl_data = ''
    rp_dir = os.path.join(home, '.config/jwmkit/restore/')
    if not os.path.exists(rp_dir):
        os.makedirs(rp_dir)
    try:
        with tarfile.open(rp_name, 'x:') as rp_tar:
            for i, f_name in enumerate(f_names):
                name = '{:003d}-{}'.format(i, os.path.basename(f_name))
                rp_tar.add(f_name, name)
                if f_name.startswith(home):
                    f_name = '$HOME{}'.format(f_name[len(home):])
                rfl_data = '{}{}/{}\n'.format(rfl_data, os.path.dirname(f_name), name)
            # Create and add a file with info used to restoring files to their original path
            rtl_info = tarfile.TarInfo(name='_FILE_PATHS_.RFL')
            rtl_info.size = len(rfl_data)
            rtl_info.mtime = datetime.timestamp(datetime.now())
            rp_tar.addfile(rtl_info, io.BytesIO(rfl_data.encode('utf8')))
    except Exception as exception_message:
        return str(exception_message)
    return 'Restore Point Created Successfully'


def do_restore(r_point):
    def extract(tmp_path):
        base = os.path.basename(f_path)
        dir = os.path.dirname(tmp_path)
        with tarfile.open(r_path, 'r:') as rp_tar:
            rp_tar.extract(os.path.basename(f_path), os.path.dirname(tmp_path))
        if version < 2.4:
            if tmp_path == '{}/.config/jwm/jwmrc'.format(home):
                tmp_path = '{}/.jwmrc'.format(home)
        elif tmp_path == '{}/.jwmrc'.format(home):
                tmp_path = '{}/.config/jwm/jwmrc'.format(home)
        os.rename(os.path.join(dir, base), tmp_path)

    def update_references():
        # update references to files with new path or filename in JWM config
        for config in compare_list:
            if config.startswith(home):
                for old, new in new_files:
                    if new.startswith(home):
                        new = '$HOME{}'.format(new[len(home):])
                    try:
                        tree = ET.parse(config)
                        root = tree.getroot()
                        for node in root.findall('.//'):
                            if node.tag in ['Include', 'Dynamic']:
                                try:
                                    node.text = node.text.replace(old, new)
                                except AttributeError:
                                    pass
                            elif node.tag == 'RootMenu':
                                if node.get('dynamic') == old:
                                    node.attrib['dynamic'] = new
                    except (ET.ParseError, IndexError):
                        pass
                    tree.write(config, encoding="utf-8", xml_declaration=True)

    version = os.popen('jwm -v').read()
    version = version[5:8]
    try:
        version = float(version)
    except ValueError:
        # assume version 2.3 if the version number can not be converted to a float
        version = 2.3
    home = os.path.expanduser('~')
    targets = get_restore_targets(r_point)
    compare_list = [os.path.join(os.path.dirname(f), os.path.basename(f)[4:]) for f in targets[0]]
    new_files = []
    if targets[1]:
        return 'Files missing from Restore Point'
    r_path = get_restore_path(r_point)
    if not os.path.isfile(r_path):
        return 'Restore point is missing'

    for f_path in targets[0]:
        if f_path[:5] == '$HOME':
            f_path = os.path.join(home, f_path[6:])
        tmp_path = os.path.join(os.path.dirname(f_path), os.path.basename(f_path)[4:])
        # ensure all files are in user's home if a file is not in the user home move it.
        # if needed rename the file to prevent conflicts
        if not tmp_path.startswith(home):
            tmp_path = os.path.join('{}/.config/jwm/'.format(home), os.path.basename(tmp_path))
            if tmp_path in compare_list:
                i = 0
                f = tmp_path
                while f in compare_list:
                    f = '{}-redit{}'.format(tmp_path, i)
                    i += 1
                tmp_path = f
            compare_list.append(tmp_path)
            # list of old filename, new filename
            new_files.append([os.path.join(os.path.dirname(f_path), os.path.basename(f_path)[4:]), tmp_path])
        extract(tmp_path)
    update_references()
    return "Restore was Successful"


def gather_config():
    # Find all files that make up the JWM config and validate the files
    # Create a list of the discovered config files
    # Create a list of discovered errors
    # Note: errors in the config can prevent this function from locating & validating files
    def read_xml(config):
        tmp = []
        try:
            tree = ET.parse(config)
            root = tree.getroot()
            if root.tag != 'JWM':
                error_files.append(config)
                error_status.append('JWM tag is missing')
            for node in root.findall('.//'):
                if node.tag in ['Include', 'Dynamic']:
                    node = node.text
                    if node:
                        if node.startswith('$HOME'):
                            node = home + node[5:]
                        if not node.startswith('exec:'):
                            if os.path.isfile(node):
                                if not os.access(node, os.X_OK) and node[0] in ['/', '$']:
                                    tmp.append(node)
                                else:
                                    error_files.append(node)
                                    error_status.append('Possible executable without leading "exec:"')

                            else:
                                if os.path.isfile(node.split(' ')[0]):
                                    error_files.append(node)
                                    error_status.append('Possible executable without leading "exec:"')
                                else:
                                    error_files.append(node)
                                    error_status.append('No such file or directory')
                elif node.tag == 'RootMenu':
                    d = node.get('Dynamic')
                    if d:
                        if not d.startswith('exec:'):
                            if not os.access(node, os.X_OK) and node[0] in ['/', '$']:
                                tmp.append(d)
        except Exception as exception_message:
            return str(exception_message)
        return tmp

    # use the read_xml function above to find files and errors in the .jwmrc then
    # each newly discovered file will also be feed back to the function until it returns empty
    # data is organized into 3 list. tmp = list of validated files,  error_status and error_file are as name suggest
    error_status, error_files = [], []
    home = os.path.expanduser('~')
    jwmrc = jwmkit_utils.get_jwmrc()
    tmp = read_xml(jwmrc)
    if isinstance(tmp, str):
        return [[], [jwmrc], [tmp]]
    tmp1 = tmp
    while tmp1:
        tmp2 = []
        for config in tmp1:
            result = read_xml(config)
            if isinstance(result, str):
                error_files.append(config)
                error_status.append(result)
            else:
                tmp2.extend(result)
        tmp.extend(tmp2)
        if tmp2:
            tmp1 = tmp2
        else:
            tmp1 = []

    tmp.append(jwmrc)
    tmp = sorted(list(set(tmp)))
    for f in error_files:
        if f in tmp:
            tmp.remove(f)
    jwmkit_dir = os.path.join(os.path.expanduser('~'), '.config/jwmkit/')
    jwmkit_settings = os.path.join(jwmkit_dir, 'settings')

    # add the jwm_settings file to the list
    if os.path.isfile(jwmkit_settings):
        tmp.append(jwmkit_settings)
    else:
        tmp.append('NEW_FILE_CREATED')
        # Create ~/.config/jwmkit directory if it does not exist
        if not os.path.exists(jwmkit_dir):
            os.makedirs(jwmkit_dir)
        # Create ~/.config/jwmkit/settings if it does not exist
        open(jwmkit_settings, 'x')
    return [tmp, error_files, error_status]


def clear_report(listbox):
    ok = True
    while ok:
        try:
            listbox.remove(listbox.get_row_at_index(0))
        except (IndexError, TypeError):
            ok = False


class RestoreWindow(Gtk.Window):

    def __init__(self):
        Gtk.Window.__init__(self, title="JWM Kit Repair & Restore")
        try:
            self.set_icon_from_file('/usr/share/pixmaps/jwmkit/config.svg')
        except gi.repository.GLib.Error:
            self.set_icon_name('edit-paste')
        self.set_border_width(20)
        main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=15)
        self.set_default_size(610, 610)
        self.add(main_box)
        self.restore_points = get_restore_points()
        self.helpers = jwmkit_utils.read_setting_su()

        # Top area with title and tray combo box next to buttons
        box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
        main_box.add(box)
        label = Gtk.Label()
        label.set_markup('<big><b>Repair &amp; Restore</b></big>\nFind &amp; Repair Errors in JWM Config')

        box.add(jwmkit_utils.create_image('/usr/share/pixmaps/jwmkit/config.svg', 48, 48, True))
        box.add(label)
        image = jwmkit_utils.create_image('/usr/share/pixmaps/jwmkit/restoregray.svg', 26, 26, True)
        restore_config_button = Gtk.Button(image=image)
        restore_config_button.set_always_show_image(True)
        restore_config_button.set_property("width-request", 50)
        restore_config_button.set_tooltip_text('Restore your Configuration\nand Manage Restore Points')
        image = jwmkit_utils.create_image('/usr/share/pixmaps/jwmkit/menugray.svg', 26, 26, True)
        repair_settings_button = Gtk.Button(image=image)
        repair_settings_button.set_always_show_image(True)
        repair_settings_button.set_property("width-request", 50)
        repair_settings_button.set_tooltip_text('Repair or Rebuild the JWM Kit Settings File')
        reload_button = Gtk.Button(label=' Reload', image=Gtk.Image(stock=Gtk.STOCK_REFRESH))
        reload_button.set_always_show_image(True)
        reload_button.set_property("width-request", 125)
        reload_button.set_tooltip_text('Update the status report')

        self.status_icon = Gtk.Image()
        self.status_button = Gtk.Button()
        self.status_button.set_always_show_image(True)
        self.status_button.connect("clicked", self.edit_command, '/usr/share/doc/jwmkit/error_help.txt', 'no-edit')
        self.status_button.set_tooltip_text('Tips to understand error messages')

        about_button = Gtk.Button(image=Gtk.Image(stock=Gtk.STOCK_ABOUT))
        about_button.set_always_show_image(True)
        about_button.set_property("width-request", 50)
        about_button.set_tooltip_text("About")
        about_button.connect("clicked", jwmkit_utils.get_about, self)

        box.pack_end(about_button, False, False, 10)
        box.pack_end(repair_settings_button, False, False, 10)

        box.pack_end(restore_config_button, False, False, 5)
        box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
        box.pack_start(Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL), True, True, 50)
        main_box.add(box)
        box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
        main_box.add(box)
        box.pack_end(reload_button, False, False, 10)

        self.status_message = Gtk.Label()
        box.pack_start(self.status_button, False, False, 0)
        box.pack_start(self.status_message, False, False, 0)

        scroll = Gtk.ScrolledWindow()
        main_box.pack_start(scroll, True, True, 0)
        listbox = Gtk.ListBox()
        listbox.set_selection_mode(Gtk.SelectionMode.SINGLE)
        scroll.add(listbox)

        restore_config_button.connect("clicked", self.reload_report, listbox)
        restore_config_button.connect("clicked", self.restore_dialog, 'global', [])
        repair_settings_button.connect("clicked", self.repair_settings)
        self.reload_report("button", listbox)
        reload_button.connect("clicked", self.reload_report, listbox)

        if self.sources[-1] == "NEW_FILE_CREATED":
            self.repair_settings('')

    def reload_report(self, button, listbox):
        self.restore_points = get_restore_points()
        include_files = gather_config()
        error_status = include_files[2]
        error_files = include_files[1]
        include_files = include_files[0]
        self.sources = include_files
        if error_status:
            self.error_free = False
            self.status_message.set_text('Status Report:  Errors found')
            self.status_button.set_image(jwmkit_utils.create_image('/usr/share/pixmaps/jwmkit/info_red.svg', 24, 24, True))
        else:
            self.error_free = True
            self.status_message.set_text('Status Report:  No errors found')
            self.status_button.set_image(jwmkit_utils.create_image('/usr/share/pixmaps/jwmkit/ok_check.svg', 24, 24, True))
        clear_report(listbox)
        self.make_report(error_files, listbox, error_status)
        self.make_report(include_files, listbox, 'File appears to be OK')
        listbox.select_row(listbox.get_row_at_index(0))
        listbox.show_all()

    def make_report(self, include_files, listbox, status):
        index = 0
        for name in include_files:
            if status == 'File appears to be OK':
                stat = status
                image = jwmkit_utils.create_image('/usr/share/pixmaps/jwmkit/ok_check.svg', 24, 24, True)
            else:
                image = jwmkit_utils.create_image('/usr/share/pixmaps/jwmkit/info_red.svg', 24, 24, True)
                stat = status[index]
            box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=20)

            edit_button = Gtk.Button(image=Gtk.Image(stock=Gtk.STOCK_EDIT))
            edit_button.set_always_show_image(True)
            edit_button.connect("clicked", self.edit_command, name, 'edit')
            edit_button.set_tooltip_text('Open with text editor')
            try:
                if status[index].startswith('Possible executable'):
                    edit_button.set_sensitive(False)
                    edit_button.set_tooltip_text('Edit the file that references this file.')
                elif not os.path.isfile(name):
                    edit_button.set_sensitive(False)
                    edit_button.set_tooltip_text('File not available for editing')
            except IndexError:
                pass
            label = Gtk.Label(label=os.path.basename(name))
            label.set_tooltip_text(name)
            list_row = Gtk.ListBoxRow()
            listbox.add(list_row)
            list_row.add(box)
            box.pack_start(label, False, False, 10)
            box.pack_end(Gtk.Label(), False, False, 0)
            box.pack_end(edit_button, False, False, 0)
            box.pack_end(image, False, False, 0)
            box.pack_end(Gtk.Label(label=stat), False, False, 0)
            index += 1

    def restore_dialog(self, button, dialog_mode, data_list):
        def page_switch(notebook, label, page):
            apply_button.set_sensitive(True)
            if page == 1:
                if not self.error_free:
                    apply_button.set_sensitive(False)
            images = [Gtk.STOCK_APPLY, Gtk.STOCK_APPLY, Gtk.STOCK_DELETE]
            text = ['Restore', 'Create', 'Delete']
            message = ['<big>Restore</big>\nRevert JWM config to a previous state',
                       '<big>Create</big>\nCreate a new restore point',
                       '<big>Remove</big>\nDelete old restore points']

            message_label.set_markup("\n<b>{}</b>\n".format(message[page]))
            apply_button.set_label(text[page])
            apply_button.set_image(image=Gtk.Image(stock=images[page]))

        def close_action(button):
            dialog.destroy()

        def del_restore(rpoint):
            rpath = get_restore_path(rpoint)
            if os.access(rpath, os.W_OK):
                os.remove(rpath)
                return 'Restore Point deleted Successfully'
            else:
                return 'You do not have permission to delete this file'

        def apply_action(button):
            i = notebook.get_current_page()
            title = ['Restore', 'Create', 'Remove'][i]
            if self.restore_points:
                try:
                    del_point = self.restore_points[del_listbox.get_selected_rows()[0].get_index()]
                except IndexError:
                    del_point = ''
                rpoints = [self.restore_points[r_listbox.get_selected_rows()[0].get_index()], '',
                           del_point]
            else:
                rpoints = [[], [], []]

            response = self.confirm_dialog(title, rpoints[i])
            if response == -8:
                if title == 'Restore':
                    response = do_restore(rpoints[i])
                elif title == 'Create':
                    response = create_restore(self.sources)
                else:
                    response = del_restore(rpoints[i])
                dialog.destroy()
                # refresh status report
                response2 = self.confirm_dialog('Report', response)
                if response2:
                    dialog.destroy()
                if response == 'Restore was Successful':
                    os.system('jwm -restart')

        def restore_listbox(listbox, page, hide_original):
            row = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0)
            row.pack_start(Gtk.Label(label="Select a Restore Point"), True, False, 0)
            page.pack_start(row, True, True, 0)
            row = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0)
            row.pack_start(Gtk.Label(), False, False, 0)
            row.pack_start(Gtk.Separator(), True, True, 0)
            page.add(row)
            scroll = Gtk.ScrolledWindow()
            scroll.set_min_content_height(100)
            page.pack_start(scroll, True, True, 0)
            listbox.set_selection_mode(Gtk.SelectionMode.SINGLE)
            scroll.add(listbox)

            for point in self.restore_points:
                if hide_original:
                    if not point[:3].isdigit():
                        break
                list_row = Gtk.ListBoxRow()
                open_button = Gtk.Button(image=Gtk.Image(stock=Gtk.STOCK_OPEN))
                open_button.set_always_show_image(True)
                open_button.set_tooltip_text('View the contents of this restore point')
                open_button.connect("clicked", self.archive_command, point)
                row = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=20)
                row.pack_start(open_button, False, False, 0)
                row.pack_start(Gtk.Label(label=point.replace(point[14:], point[14:].replace('-', ':')).replace('_', ' ')), False, False, 0)
                row.pack_end(Gtk.Label(label=date_to_text(point)), False, False, 10)
                list_row.add(row)
                listbox.add(list_row)
            listbox.select_row(listbox.get_row_at_index(0))

        message = '\n'
        title = 'Restore'
        # dialog_mode can be file (to restore single file), or global for all
        dialog = Gtk.Dialog(title, self, 0)
        box = dialog.get_content_area()
        box.set_border_width(20)

        message_label = Gtk.Label(xalign=0)
        message_label.set_markup("\n<b>{}</b>\n".format(message))
        dialog.set_default_size(500, -1)
        page1 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5)
        page2 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5)
        page2.set_border_width(20)
        page3 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5)
        row = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=20)
        row.add(jwmkit_utils.create_image('/usr/share/pixmaps/jwmkit/restoregray.svg', 48, 48, True))
        row.add(message_label)
        box.add(row)

        if dialog_mode != 'file':
            notebook = Gtk.Notebook()
            notebook.append_page(page1, Gtk.Label(label='Restore'))
            notebook.append_page(page2, Gtk.Label(label='Create'))
            notebook.append_page(page3, Gtk.Label(label='Remove'))
            box.add(notebook)

            # Page 2 of Notebook (Create a Restore Point)
            if self.restore_points:
                update_status = rp_update_status(self.sources, self.restore_points[0])
            else:
                update_status = False

            def add_label_image(index):
                if self.error_free:
                    image = '/usr/share/pixmaps/jwmkit/ok_check.svg'
                    labels = ['Status:  No errors found in configuration',
                              'No change since last restore point',
                              'Ready to create a new restore point']

                    if not update_status:
                        if index == 1:
                            image = '/usr/share/pixmaps/jwmkit/info_yellow.svg'
                            labels[1] = 'Configuration has changed since last restore point'
                        else:
                            labels[2] = 'Creation of restore point advised. Ready.'
                else:
                    image = '/usr/share/pixmaps/jwmkit/info_red.svg'
                    labels = ['Warning!:  Errors found. Review status report for info\n'
                              'Repair configuration before creating a restore point']

                image = jwmkit_utils.create_image(image, 32, 32, True)
                label = Gtk.Label(label=labels[index])
                row = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5)
                page2.pack_start(row, True, False, 0)
                row.pack_start(image, False, False, 10)
                row.pack_start(label, False, False, 0)

            for i in range(3):
                add_label_image(i)
                if not self.error_free:
                    break

            # Page 3 of Notebook (Manage Restore Points)
            del_listbox = Gtk.ListBox()
            restore_listbox(del_listbox, page3, True)
        else:
            box.add(page1)

        # Page 1 of Notebook (Restore) (also Restore a single file)
        r_listbox = Gtk.ListBox()
        restore_listbox(r_listbox, page1, False)

        # Buttons at the bottom of dialog
        row = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5)
        row.add(Gtk.Label())
        box.add(row)
        cancel_button = Gtk.Button(label='Cancel', image=Gtk.Image(stock=Gtk.STOCK_CANCEL))
        apply_button = Gtk.Button()
        cancel_button.set_always_show_image(True)
        apply_button.set_always_show_image(True)
        cancel_button.set_property("width-request", 90)
        apply_button.set_property("width-request", 90)
        cancel_button.connect("clicked", close_action)
        apply_button.connect("clicked", apply_action)
        if dialog_mode != 'file':
            notebook.connect("switch-page", page_switch)
        row = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5)
        row.pack_end(apply_button, False, False, 0)
        row.pack_end(cancel_button, False, False, 0)
        box.add(row)
        dialog.show_all()

    def confirm_dialog(self, title, r_point):
        confirm_dialog = Gtk.Dialog('Confirm {}'.format(title), self, 0)
        short_point = str(r_point).replace('_', ' ')
        if title == 'Restore':
            message = "Revert jwm's configuration to  {} \n" \
                      "Changes made since that time will be lost.".format(short_point)
            stock_img = Gtk.STOCK_REFRESH
        elif title == 'Create':
            message = "Create a new restore point"
            stock_img = Gtk.STOCK_NEW
        elif title == 'Remove':
            message = "Delete the restore point  {} ".format(short_point)
            stock_img = Gtk.STOCK_DELETE
        elif title == 'Report':
            message = r_point
            stock_img = Gtk.STOCK_DELETE
        elif title == 'Save Settings':
            message = r_point
            stock_img = Gtk.STOCK_SAVE
        elif title == 'Warning':
            message = r_point
            stock_img = Gtk.STOCK_ABOUT
        box = confirm_dialog.get_content_area()

        label = Gtk.Label(label=message, xalign=0)
        confirm_dialog.set_default_size(370, -1)
        box.set_border_width(20)
        box.set_spacing(20)
        row = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5)
        box.add(row)
        if title not in ['Report', 'Warning']:
            confirm_dialog.add_buttons(Gtk.STOCK_NO, Gtk.ResponseType.NO, Gtk.STOCK_YES, Gtk.ResponseType.YES)
            row.add(Gtk.Image.new_from_icon_name(stock_img, size=Gtk.IconSize.DIALOG))
        else:
            confirm_dialog.add_buttons(Gtk.STOCK_OK, Gtk.ResponseType.OK)
        row.add(label)

        confirm_dialog.show_all()
        response = confirm_dialog.run()
        confirm_dialog.destroy()
        return response

    def edit_command(self, button, name, type):
        editor = self.helpers[2]
        if shutil.which(str(editor)) and type == 'edit':
            os.system('{} {} &'.format(editor, name))
        else:
            jwmkit_utils.viewer(self, name, type)

    def archive_command(self, button, name):
        name = get_restore_path(name)
        archiver = self.helpers[3]
        if shutil.which(str(archiver)):
            os.system('{} {} &'.format(archiver, name))
            return
        if tarfile.is_tarfile(name):
            with tarfile.open(name, 'r:') as rp_tar:
                r_list = rp_tar.getmembers()
        file_list = 'Contents of the Restore Point:\n{}\n\n'.format(name)
        for f in r_list:

            file_list = '{}\n{}'.format(file_list, f.name[4:])

        jwmkit_utils.viewer(self, file_list.replace('E_PATHS_.RFL', '_FILE_PATHS_.RFL'), 'string')

    def repair_settings(self, button):

        def find_missing(button):
            # find unassigned purpose values & give warning
            purposes = []
            for combo in combos:
                purpose = values[combo.get_active()]
                purposes.append(purpose)
            purposes.extend(['Unknown', 'Tray', 'Empty',  'Menu'])
            missing = set(values) - set(purposes)
            if len(missing) == 0:
                missing_label.set_text('')
                missing_image.set_visible(False)
            else:
                missing_label.set_text('Unassigned: {}'.format(', '.join(missing)))
                missing_image.set_visible(True)

        def close_action(button):
            set_dialog.destroy()

        def save_action(button):
            home = os.path.expanduser('~')
            flat = sum(configs, [])
            output = ''
            miss_message = missing_label.get_text()
            unknown_message = ''
            if miss_message:
                miss_message = miss_message.split(':')[1][1:]
                if ' ' in miss_message:
                    miss_message = re.sub(', ([^ ]+?)$', ' and \\1', miss_message)
                    miss_message = '{} file(s) are unassigned\n'.format(miss_message)
                else:
                    miss_message = 'The {} file is unassigned\n'.format(miss_message)
            for combo, path in zip(combos, flat):
                value = values[combo.get_active()].lower()
                if value == 'unknown':
                    unknown_message = 'Warning: Files marked as unknown will be remove from settings\n'
                if value not in ['menu', 'tray', 'empty', 'unknown']:
                    if path.startswith(home):
                        path = '$HOME{}'.format(path[len(home):])
                    output = '{}{}={}\n'.format(output, value, path)
            message = '{}{}Overwrite the Settings file'.format(miss_message, unknown_message)
            response = self.confirm_dialog('Save Settings', message)
            if response == -8:
                dir = os.path.join(home, '.config/jwmkit/')
                if not os.path.isdir(dir):
                    os.makedirs(dir)
                path = os.path.join(home, '.config/jwmkit/settings')
                with open(path, "w+") as f:
                    f.write(output)
                set_dialog.destroy()
            else:
                print("Not Saved!!")

        if not self.error_free:
            message = "Repair JWM's configuration before creating a settings file"
            response = self.confirm_dialog('Warning', message)
            return
        values = ['Startup', 'Icons', 'Group', 'Theme', 'Keys', 'Preferences', 'Menu', 'Tray', 'Buttons', 'Empty', 'Unknown']
        configs = jwmkit_utils.find_configs()
        set_dialog = Gtk.Dialog('Repair Settings', self, 0)
        set_dialog.set_default_size(610, 610)
        mainbox = set_dialog.get_content_area()
        mainbox.set_spacing(15)
        scroll = Gtk.ScrolledWindow()
        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=20)
        scroll.add(vbox)
        label = Gtk.Label()
        label.set_markup("<big><b>Repair JWM Kit's Setting File</b></big>\nCreate a new settings files")
        box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=20)
        mainbox.pack_start(box, False, False, 0)
        box.pack_start(jwmkit_utils.create_image('/usr/share/pixmaps/jwmkit/menugray.svg', 48, 48, True), False, False, 20)
        box.pack_start(label, False, False, 20)
        label = Gtk.Label(label="Confirm the purpose of each file then save\nFiles marked as unknown will not be added!")
        box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=20)
        box.pack_start(label, False, False, 130)
        mainbox.pack_start(box, False, False, 0)
        box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5)
        mainbox.pack_start(box, False, False, 0)
        combos = []
        for i, paths in enumerate(configs):
            for path in paths:
                box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5)
                combo = Gtk.ComboBoxText()
                for v in values:
                    combo.append_text(v)
                combo.set_active(i)
                if i not in [9, 10]:
                    combo.set_sensitive(False)
                    combo.set_tooltip_text('Value is locked. Other value are not compliant')
                combos.append(combo)
                combo.connect('changed', find_missing)
                vbox.add(box)
                box.pack_start(combo, False, False, 30)
                box.pack_start(Gtk.Label(label='{}'.format(path)), False, False, 0)

        box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5)
        mainbox.pack_end(box, False, False, 0)
        close_button = Gtk.Button(label='Cancel', image=Gtk.Image(stock=Gtk.STOCK_CANCEL))
        close_button.set_always_show_image(True)
        close_button.set_property("width-request", 90)
        close_button.connect("clicked", close_action)

        save_button = Gtk.Button(label='Save', image=Gtk.Image(stock=Gtk.STOCK_SAVE))
        save_button.set_always_show_image(True)
        save_button.set_property("width-request", 90)
        save_button.connect("clicked", save_action)

        image = jwmkit_utils.create_image('/usr/share/pixmaps/jwmkit/info_yellow.svg', 24, 24, True)
        missing_image = jwmkit_utils.create_image('/usr/share/pixmaps/jwmkit/info_red.svg', 24, 24, True)
        missing_image.set_no_show_all(True)

        box.pack_start(image, False, False, 15)
        box.pack_start(Gtk.Label(label='Accurate verification of file purpose is critical'), False, False, 0)
        box.pack_end(save_button, False, False, 20)
        box.pack_end(close_button, False, False, 0)

        box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5)
        mainbox.pack_end(box, False, False, 0)
        missing_label = Gtk.Label()
        find_missing('')
        box.pack_start(missing_image, False, False, 15)
        box.pack_start(missing_label, False, False, 0)

        mainbox.pack_end(scroll, True, True, 0)
        set_dialog.show_all()
        set_dialog.run()


if len(sys.argv) > 1:
    if sys.argv[1].lower() in ['restore_default', '-restore_default', '--restore_default']:
        print(do_restore('Original  Install'))
    elif sys.argv[1].lower() in ['bk_restore', '-bk_restore', '--bk_restore']:
        files = gather_config()[0]
        create_restore(files)
else:
    win = RestoreWindow()
    win.connect("delete-event", Gtk.main_quit)
    win.set_position(Gtk.WindowPosition.CENTER)
    win.show_all()
    Gtk.main()

# TODO Restore Points
#  export / import restore (import copies to restore folder / export copies to selected path)
#  Option to add inactive trays/menus from settings file to restore point
#  option to manually add a file to restore point (warn if it is already part of restore point or not a text file)

# TODO provide more info to user
#  info button at bottom of restore window with the following:
#  ----Inform user that files are migrated to user's home (change of path or filename)
#      * Report: "To ensure proper functionality,"\
#                 "Some files maybe moved to a new path with in the user's home directory.\n"\
#                 "configuration files will been altered to represents these changes"
#  ----Path of restore points if user wishes to manually restore a files
#  ----Explain basics of RP archive.  _FILE_PATHS_.RFL,  leading 000-, config validation, etc
#  RP Out of Date Notice's: list of files that have changed

# TODO Deep Scans
#  function to increase accuracy of error messages. Discover more errors, and give more precise location (lines/columns)
#  compare tags to approved tags list report, report tags not on the list with line column position
