#!/usr/bin/python
"""
    gui Gtk for Multi-Branch-Navigator
"""

import subprocess
import configparser
import time
from pathlib import Path
from datetime import datetime
import lib

import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk, Gio, GObject, GLib

from lib.alpm import AlpmBranch
from pyalpm import vercmp

#from memory_profiler import profile
#from memory_profiler import memory_usage
import gc

class GioConfig():
    # https://lazka.github.io/pgi-docs/Gio-2.0/classes/Settings.html ?
    KEY = 'GENERAL'
    inifile = Path.home() / f".config/manjaro/{lib.name()}.ini"

    def __init__(self):
        self.inifile.parent.mkdir(parents=True, exist_ok=True)
        self.config = configparser.ConfigParser()
        self.config.read(self.inifile)
        try:
            self.config[self.KEY]
        except KeyError:
            self.config[self.KEY] = {}

    def get_window_size(self):
        try:
            w, h = self.config['size'].get('w', 670), self.config['size'].get('h', 250)
            return int(w), int(h)
        except (KeyError, ValueError):
            return 670, 250

    def set_window_size(self, w: int, h: int):
        self.config['size'] = {}
        self.config['size']['w'] = str(w)
        self.config['size']['h'] = str(h)
        self.save()

    def get_fields(self):
        try:
            return self.config['FIELDS']
        except KeyError:
            return {'desc': True}

    def set_fields(self, fields: dict):
        self.config['FIELDS'] = {}
        for field in fields:
            self.config['FIELDS'][field] = '1'
        if not self.config['FIELDS']:
            self.config['FIELDS'] = {'desc': '1'}
        self.save()

    def save(self):
        with open(self.inifile, 'w') as configfile:
            self.config.write(configfile)


class PopupFields(Gtk.MenuButton):
    """ package field list """
    def __init__(self):
        super(PopupFields, self).__init__()
        self.set_property("tooltip-text", "search fields")
        self.field = 'name'

        self.menu = Gtk.Menu()
        self.set_popup(self.menu)

        branch = AlpmBranch('unstable')
        pkg = branch.pkg_get('pacman')
        fields = (a for a in sorted(dir(pkg)) if not '_' in a)
        group = None
        for field in fields:
            if field in ('md5sum', 'size', 'isize', 'files', 'deltas', 'backup', 'reason', 'sha256sum'):
                continue
            item = Gtk.RadioMenuItem(label=field)
            if not group:
                group = item
            else:
                item.set_property("group", group)
            if field == 'name':
                self.field = field
                item.set_active(True) # check
            item.show()
            item.connect('toggled', self.pop_action, field)
            self.menu.append(item)

    def pop_action(self, radiomenuitem, parameter):
        self.field = radiomenuitem.get_label()

class RightPopMenu(Gtk.Menu):
    """ popup menu for select columns in treeview """
    def __init__(self):
        super(RightPopMenu, self).__init__()
        fields_config = GioConfig().get_fields()
        self.fields = {}
        for i, item in enumerate(MainApp.FIELDS):
            if i < 3:
                continue
            menuitem = Gtk.CheckMenuItem(label=item)
            #menuitem.show()
            self.append(menuitem)
            is_active = fields_config.get(item, '')
            menuitem.set_active(is_active)
            if is_active:
                self.fields[item] = True
            menuitem.connect('toggled', self.pop_action, item)

    def pop_action(self, menuitem, parameter):
        if menuitem.get_active():
            self.fields[parameter] = True
        else:
            self.fields.pop(parameter, None)
        self.send_change(self.fields)

    def send_change(self, fields: dict):
        raise NotImplementedError()

class InfoDlg(Gtk.InfoBar):
    """ dialog informations """
    def __init__(self):
        super().__init__()
        self.message = ''
        self.pkg = None
        self.set_message_type(Gtk.MessageType.OTHER)
        self.set_show_close_button(True)
        self.set_default_response(0)
        self.set_revealed(True)
        self.connect("response", self.on_remove_info)
        self.info_label = Gtk.Label()
        self.info_label.set_line_wrap(True)
        vbox = Gtk.VBox(spacing=6)
        vbox.pack_start(self.info_label, expand=True, fill=True, padding=0)
        self.info_url = Gtk.Label()
        vbox.pack_start(self.info_url, expand=True, fill=True, padding=0)

        self.hbox = Gtk.HBox(spacing=6)
        self.pkgs = dict()
        for branchname in AlpmBranch.BRANCHES:
            data = {
                'contener': Gtk.Frame(label=branchname),
                'version' : Gtk.Label(label=''),
                'date' : Gtk.Label(label='')
            }
            self.pkgs[branchname] = data
            xbox = Gtk.VBox(spacing=4)
            xbox.pack_start(data['version'], expand=False, fill=True, padding=0)
            xbox.pack_end(data['date'], expand=False, fill=True, padding=0)
            data['contener'].add(xbox)
            self.hbox.pack_start(data['contener'], expand=True, fill=True, padding=0)
        vbox.pack_end(self.hbox, expand=True, fill=True, padding=0)
        self.get_content_area().pack_start(vbox, expand=True, fill=True, padding=0)
        self.hide()

    def on_remove_info(self, panel: Gtk.InfoBar, btnid: str):
        panel.hide()

    def set_message(self, msg: str, typed=None):
        """ type: Gtk.MessageType.WARNING ERROR, INFO, OTHER  """
        if not typed:
            typed = Gtk.MessageType.OTHER
        self.set_message_type(typed)
        self.info_label.set_markup(msg)
        self.show()
        self.info_url.hide()
        self.hbox.hide()

    def set_pkg(self, pkg_name):
        """ make pacman -Qi """
        # return no bug memory
        '''
        powerpill 40 Mo not in arch -> 2eme pass = 0 !!?
        pacui(community) 0Mo not in arch
        update-notifier(community) 0Mo not in arch
        trizen(community) 0Mo not in arch
        python3-xcpf 0Mo not in arch
        pkgfile(extra) 20Mo in all  > 2eme pass = 0 !!?
        pacman(core) 1Mo in all
        expac(extra) 0 Mo in all
        pacredir(community) 0Mo in all
        '''
        desc = ''
        url = ''

        for branchname in AlpmBranch.BRANCHES:
            branch = AlpmBranch(branchname)
            #mem_usage = memory_usage(-1, interval=.02, timeout=0.1, max_usage=True)
            #print(pkg_name , mem_usage)
            pkg = branch.pkg_get(pkg_name)
            #mem_usage = memory_usage(-1, interval=.02, timeout=0.1, max_usage=True)
            #print(pkg_name, mem_usage,"\n")
            if pkg:
                self.pkgs[branchname]['version'].set_label(pkg.version)
                self.pkgs[branchname]['date'].set_label(datetime.fromtimestamp(pkg.builddate).strftime('%Y-%m-%d'))
                self.pkgs[branchname]['contener'].show()
                desc = pkg.desc
                url = pkg.url
            else:
                self.pkgs[branchname]['contener'].hide()
            branch = None
            pkg = None
            gc.collect()
        self.set_message(f"<b>{pkg_name}</b>\n<i>{desc}</i>\n", Gtk.MessageType.INFO)
        self.info_url.set_markup(f'<a href="{url}">{url}</a>\n')
        self.info_url.show()
        self.hbox.show()


class MainApp:
    """ main window """

    BRANCHES = AlpmBranch.BRANCHES
    FIELDS = ('name', 'version', 'ver', 'desc', 'builddate', 'url', 'db', 'packager')

    def __init__(self, title: str = 'pacman'):
        self.setup = GioConfig()
        self.window = None
        self.store = None
        self.create_store('')
        # Use fixed-height-mode to get better model load and display performance.
        #view = Gtk.TreeView(model=store, fixed_height_mode=True, headers_visible=True)
        self.treeview = Gtk.TreeView(model=self.store, headers_visible=True, fixed_height_mode=False)
        self.treeview.connect("button-press-event", self.on_tree_click)

        cell = Gtk.CellRendererText()
        column = Gtk.TreeViewColumn('package', cell, text=0)
        column.set_sort_order(Gtk.SortType.DESCENDING)
        column.set_sort_column_id(0)
        column.set_resizable(True)
        self.treeview.append_column(column)

        cell = Gtk.CellRendererText()
        column = Gtk.TreeViewColumn('version', cell, markup=1)
        column.set_resizable(True)
        self.treeview.append_column(column)
        cell = Gtk.CellRendererText()
        column = Gtk.TreeViewColumn('version', cell, markup=2)
        column.set_sort_order(Gtk.SortType.DESCENDING)
        column.set_sort_column_id(2)
        column.set_resizable(True)
        column.set_visible(False)
        self.treeview.append_column(column)

        for i, item in enumerate(self.FIELDS):
            if i < 3:
                continue
            cell = Gtk.CellRendererText()
            column = Gtk.TreeViewColumn(item, cell, text=i)
            column.set_sort_column_id(i)
            self.treeview.append_column(column)

        scrolled = Gtk.ScrolledWindow()
        scrolled.add(self.treeview)

        self.window = Gtk.Window(title='Multi-Branch-Navigator')
        self.window.connect('delete-event', self.on_close)
        self.window.connect('destroy', Gtk.main_quit)
        self.window.set_border_width(4)
        window_size = self.setup.get_window_size()
        self.window.set_default_size(window_size[0], window_size[1])
        self.window.set_position(Gtk.WindowPosition.CENTER_ALWAYS)

        hb = Gtk.HeaderBar()
        hb.set_show_close_button(True)
        hb.props.title = self.window.get_title()
        self.window.set_titlebar(hb)

        self.update_process = None
        self.button_update = Gtk.Button(label='🗘')
        self.button_update.set_property("tooltip-text", "Update")
        self.button_update.connect("clicked", self.on_update)
        hb.pack_end(self.button_update)

        box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)

        button = Gtk.Button()
        icon = Gio.ThemedIcon(name="go-home")
        button.set_property("tooltip-text", "clear list")
        image = Gtk.Image.new_from_gicon(icon, Gtk.IconSize.BUTTON)
        button.add(image)
        button.connect("clicked", self.on_branch_combo_changed)
        box.add(button)

        self.branch_combo = Gtk.ComboBoxText()
        self.branch_combo.set_entry_text_column(0)
        for branch in self.BRANCHES:
            self.branch_combo.append_text(branch)
        self.branch_combo.set_active(int(self.setup.config['GENERAL'].get('branch', '1')))
        self.branch_combo.connect("changed", self.on_branch_combo_changed)
        self.branch_combo.set_property("tooltip-text", "branch")
        box.pack_start(self.branch_combo, False, False, True)

        self.branch_combo_two = Gtk.ComboBoxText()
        self.branch_combo_two.set_entry_text_column(0)
        self.branch_combo_two.append_text('')
        for branch in self.BRANCHES:
            self.branch_combo_two.append_text(branch)
        self.branch_combo_two.set_active(0)
        self.branch_combo_two.connect("changed", self.on_branch_combo_changed)
        self.branch_combo_two.set_property("tooltip-text", "branch")
        box.pack_start(self.branch_combo_two, False, False, True)

        self.entry = Gtk.Entry()
        self.entry.set_property("tooltip-text", "to search")
        self.entry.connect("activate", self.on_search)
        self.entry.set_text(self.setup.config['GENERAL'].get('entry', ''))
        box.pack_start(self.entry, expand=True, fill=False, padding=0)

        self.fields = PopupFields()
        box.pack_start(self.fields, expand=False, fill=False, padding=0)

        button = Gtk.Button()
        icon = Gio.ThemedIcon(name="search")
        image = Gtk.Image.new_from_gicon(icon, Gtk.IconSize.BUTTON)
        button.add(image)
        button.set_property("tooltip-text", "Search")
        button.connect("clicked", self.on_search)
        box.pack_start(button, expand=True, fill=False, padding=0)

        hb.pack_start(box)

        vbox = Gtk.VBox(spacing=4)
        self.info = InfoDlg()
        vbox.pack_end(self.info, expand=False, fill=False, padding=0)
        vbox.pack_start(scrolled, expand=True, fill=True, padding=0)
        self.window.add(vbox)

        self.rmenu = RightPopMenu()
        self.rmenu.send_change = self.on_change_colums
        self.rmenu.send_change(self.rmenu.fields) # update from .config
        self.window.show_all()
        self.info.hide()

    def main(self):
        try:
            self.window.set_icon_from_file(f"/usr/share/{lib.PKGNAME}/{lib.name()}.svg")
        except gi.repository.GLib.Error:
            pass
        return Gtk.main()

    def set_title(self):
        """ display count packages after search """
        if self.window and self.store:
            nb_pkg = f" {len(self.store)}" if len(self.store)>0 else ''
            self.window.set_title(f"Multi-Branch-Navigator {nb_pkg}")

    def on_close(self, widget, event=None, data=None):
        """ save pos/setups before close app """
        self.setup.config['GENERAL']['entry'] = self.entry.get_text().strip()
        self.setup.config['GENERAL']['branch'] = str(self.branch_combo.get_active())
        self.setup.set_window_size(self.window.get_size().width, self.window.get_size().height)
        self.setup.set_fields(self.rmenu.fields)
        Gtk.main_quit()

    def create_store(self, branchname: str = 'unstable'):
        """ set datas in store """

        def set_versions(v1, v2, compare):
            if compare > 0:
                return v1, f'<span foreground="red">{v2}</span>'
            elif compare < 0:
                return f'<span foreground="red">{v1}</span>', v2
            return v1, v2

        if self.store:
            self.store.clear()
        else:
            self.store = Gtk.ListStore(str, str, str, str, str, str, str, str, int)
        self.set_title()
        if not branchname:
            return self.store

        to_find = self.entry.get_text().strip()
        print(to_find)
        branchname_two = self.branch_combo_two.get_active_text()

        branch = AlpmBranch(branchname)
        if self.fields.field == 'name':
            pkgs = branch.pkg_search(to_find)
        else:
            pkgs = branch.pkg_search_by_field(to_find, self.fields.field)
        if branchname_two:
            branch = AlpmBranch(branchname_two)
        for pkg in pkgs:
            version_one = pkg.version
            version_two = ''
            compare = 99
            if branchname_two and branchname_two != branchname:
                pkgb = branch.pkg_get(pkg.name)
                if pkgb:
                    version_two = pkgb.version
                    compare = vercmp(pkg.version, version_two)
                    version_one, version_two = set_versions(version_one, version_two, compare)
                    pkgb = None
            if not branchname_two or (branchname_two and compare != 0):
                self.store.append([
                    pkg.name,
                    version_one,
                    version_two,
                    pkg.desc,
                    time.strftime('%Y-%m-%d', time.localtime(pkg.builddate)),
                    pkg.url,
                    pkg.db.name,
                    pkg.packager.split('<')[0],
                    compare])
        self.set_title()
        return self.store

    def on_branch_combo_changed(self, widget):
        """ user change branch """
        if self.branch_combo_two.get_active() == self.branch_combo.get_active() + 1:
            self.branch_combo_two.set_active(0)
            return
        self.create_store('')
        gc.collect()
        self.treeview.get_column(2).set_visible(self.branch_combo_two.get_active() > 0)
        if self.branch_combo_two.get_active() == 0:
            for i in range(1, 3):
                self.treeview.get_column(i).set_title('version')
        else:
            self.treeview.get_column(1).set_title(self.branch_combo.get_active_text())
            self.treeview.get_column(2).set_title(self.branch_combo_two.get_active_text())

    def on_change_colums(self, fields):
        """ show/hide tree colums from menu and .config """
        for i, item in enumerate(MainApp.FIELDS):
            if i < 3:
                continue
            self.treeview.get_column(i).set_visible(fields.get(item, ''))

    def on_search(self, btn):
        text = self.branch_combo.get_active_text()
        print(text)
        watch_cursor = Gdk.Cursor(Gdk.CursorType.WATCH)
        self.treeview.get_window().set_cursor(watch_cursor)
        try:
            self.create_store(text)
            gc.collect()
        finally:
            self.treeview.get_window().set_cursor(None)

    def on_tree_click(self, treeview: Gtk.TreeView, event):
        """ double clic on tree view """
        if event.button == 1 and int(event.type == 5):  # Gdk.EventType.GDK_2BUTTON_PRESS:
            model, itertree = treeview.get_selection().get_selected()
            if not itertree:
                return
            pkg_name = model.get(itertree, 0)[0]
            if pkg_name:
                self.info.set_pkg(pkg_name)
            gc.collect()
        """ right mouse click """
        if event.button == 3:
            self.rmenu.show_all()
            self.rmenu.popup(None, None, None, None, event.button, event.time)


    def on_update(self, btn):
        """ update 4 branches """
        self.update_process = subprocess.Popen([
            'pkexec',
            f"/usr/share/{lib.PKGNAME}/__main__.py",
            'update',
            '--gui'
        ], stdout=None, stderr=None)
        self.button_update.set_sensitive(False)
        GLib.timeout_add(1000*20, self.on_timer_update_check) # check result in 20 seconds

    def on_timer_update_check(self):
        if self.update_process:
            if self.update_process.poll() == None:
                GLib.timeout_add(1000*20, self.on_timer_update_check)
                self.button_update.set_sensitive(False)
                return
            else:
                ret_code = self.update_process.poll()
                print('End update, returncode: ', ret_code)
                if ret_code > 0:
                    self.info.set_message(f"update ERROR: {ret_code}", Gtk.MessageType.WARNING)
                else:
                    self.info.set_message(f"databases updated", Gtk.MessageType.OTHER)
                self.update_process = None
        self.button_update.set_sensitive(True)


if __name__ == "__main__":
    print(f"{lib.name()} {lib.version_str()} {lib.DESCRIPTION} ")
    app = MainApp(title=lib.DESCRIPTION)
    #mem_usage = memory_usage(-1, interval=.02, timeout=0.1)
    #print('begin',mem_usage,"\n")
    app.main()
    #mem_usage = memory_usage(-1, interval=.02, timeout=0.1)
    #print('begin',mem_usage,"\n")
    #nb = gc.collect()
    #mem_usage = memory_usage(-1, interval=.02, timeout=0.1)
    #print('gc released=',nb,mem_usage,"\n")
