# -*- python -*-
# -*- coding: utf-8 -*-
# SPDX-License-Identifier: GPL-2.0-only
from tuna import tuna, gui
import procfs
from gi.repository import Gdk
from gi.repository import Gtk
from gi.repository import GObject
import os
from functools import reduce
import tuna.new_eth as ethtool
import tuna.tuna_sched as tuna_sched

import gi
gi.require_version("Gtk", "3.0")


class irq_druid:

    def __init__(self, irqs, ps, irq, gladefile):
        self.irqs = irqs
        self.ps = ps
        self.irq = irq
        self.window = Gtk.Builder()
        self.window.add_objects_from_file(gladefile, ("set_irq_attributes", "tuna"))
        #self.window = Gtk.glade.XML(gladefile, "set_irq_attributes", "tuna")
        self.dialog = self.window.get_object("set_irq_attributes")
        pixbuf = self.dialog.render_icon(Gtk.STOCK_PREFERENCES,
                                         Gtk.IconSize.SMALL_TOOLBAR)
        self.dialog.set_icon(pixbuf)
        event_handlers = {
            "on_irq_affinity_text_changed": self.on_irq_affinity_text_changed,
            "on_sched_policy_combo_changed": self.on_sched_policy_combo_changed}
        # self.window.signal_autoconnect(event_handlers)
        self.window.connect_signals(event_handlers)

        self.sched_pri = self.window.get_object("irq_pri_spinbutton")
        self.sched_pri.set_range(0, 99)
        self.sched_policy = self.window.get_object("irq_policy_combobox")
        self.affinity = self.window.get_object("irq_affinity_text")
        text = self.window.get_object("irq_text")

        users = tuna.get_irq_users(irqs, irq)
        self.affinity_text = tuna.get_irq_affinity_text(irqs, irq)

        irq_re = tuna.threaded_irq_re(irq)
        pids = self.ps.find_by_regex(irq_re)
        if pids:
            pid = pids[0]
            prio = int(ps[pid]["stat"]["rt_priority"])
            self.create_policy_model(self.sched_policy)
            self.sched_policy.set_active(os.sched_getscheduler(pid))
            self.sched_pri.set_value(prio)
            text.set_markup(
                "IRQ <b>%u</b> (PID <b>%u</b>), pri <b>%u</b>, aff <b>%s</b>, <tt><b>%s</b></tt>" %
                (irq, pid, prio, self.affinity_text, ",".join(users)))
        else:
            self.sched_pri.set_sensitive(False)
            self.sched_policy.set_sensitive(False)
            text.set_markup(
                "IRQ <b>%u</b>, aff <b>%s</b>, <tt><b>%s</b></tt>" %
                (irq, self.affinity_text, ",".join(users)))
        self.affinity.set_text(self.affinity_text)

    def create_policy_model(self, policy):
        (COL_TEXT, COL_SCHED) = list(range(2))
        list_store = Gtk.ListStore(GObject.TYPE_STRING,
                                   GObject.TYPE_UINT)
        renderer = Gtk.CellRendererText()
        policy.pack_start(renderer, True)
        policy.add_attribute(renderer, "text", COL_TEXT)
        for pol in range(4):
            row = list_store.append()
            list_store.set(row, COL_TEXT, tuna_sched.sched_str(pol),
                           COL_SCHED, pol)
        policy.set_model(list_store)

    def on_sched_policy_combo_changed(self, button):
        new_policy = self.sched_policy.get_active()
        if new_policy in (os.SCHED_FIFO, os.SCHED_RR):
            can_change_pri = True
        else:
            can_change_pri = False
        self.sched_pri.set_sensitive(can_change_pri)

    def on_irq_affinity_text_changed(self, button):
        gui.on_affinity_text_changed(self)

    def run(self):
        changed = False
        if self.dialog.run() == Gtk.ResponseType.OK:
            new_policy = self.sched_policy.get_active()
            new_prio = int(self.sched_pri.get_value())
            new_affinity = self.affinity.get_text()
            irq_re = tuna.threaded_irq_re(self.irq)
            pids = self.ps.find_by_regex(irq_re)
            if pids:
                if gui.thread_set_attributes(self.ps[pids[0]],
                                             new_policy,
                                             new_prio,
                                             new_affinity,
                                             self.irqs.nr_cpus):
                    changed = True
            try:
                new_affinity = [int(a) for a in new_affinity.split(",")]
            except:
                try:
                    new_affinity = tuna.cpustring_to_list(new_affinity)
                except:
                    new_affinity = procfs.bitmasklist(new_affinity,
                                                      self.irqs.nr_cpus)

            new_affinity.sort()

            curr_affinity = self.irqs[self.irq]["affinity"]
            if curr_affinity != new_affinity:
                tuna.set_irq_affinity(
                    self.irq, procfs.hexbitmask(new_affinity,
                                                self.irqs.nr_cpus))
                changed = True

        self.dialog.destroy()
        return changed


class irqview:

    nr_columns = 7
    (COL_NUM, COL_PID, COL_POL, COL_PRI,
     COL_AFF, COL_EVENTS, COL_USERS) = list(range(nr_columns))
    columns = (gui.list_store_column(_("IRQ")),
               gui.list_store_column(_("PID"), GObject.TYPE_INT),
               gui.list_store_column(_("Policy"), GObject.TYPE_STRING),
               gui.list_store_column(_("Priority"), GObject.TYPE_INT),
               gui.list_store_column(_("Affinity"), GObject.TYPE_STRING),
               gui.list_store_column(_("Events")),
               gui.list_store_column(_("Users"), GObject.TYPE_STRING))

    def __init__(self, treeview, irqs, ps, cpus_filtered, gladefile):

        self.is_root = os.getuid() == 0
        self.irqs = irqs
        self.ps = ps
        self.treeview = treeview
        self.gladefile = gladefile
        self.has_threaded_irqs = tuna.has_threaded_irqs(ps)
        if not self.has_threaded_irqs:
            self.nr_columns = 4
            (self.COL_NUM,
             self.COL_AFF,
             self.COL_EVENTS,
             self.COL_USERS) = list(range(self.nr_columns))
            self.columns = (gui.list_store_column(_("IRQ")),
                            gui.list_store_column(
                                _("Affinity"), GObject.TYPE_STRING),
                            gui.list_store_column(_("Events")),
                            gui.list_store_column(_("Users"), GObject.TYPE_STRING))

        self.list_store = Gtk.ListStore(
            *gui.generate_list_store_columns_with_attr(self.columns))

        # Allow selecting multiple rows
        selection = treeview.get_selection()
        selection.set_mode(Gtk.SelectionMode.MULTIPLE)

        # Allow enable drag and drop of rows
        self.treeview.enable_model_drag_source(
            Gdk.ModifierType.BUTTON1_MASK,
            gui.DND_TARGETS,
            Gdk.DragAction.DEFAULT | Gdk.DragAction.MOVE)
        self.treeview.connect("drag_data_get", self.on_drag_data_get_data)
        self.renderer = Gtk.CellRendererText()

        for col in range(self.nr_columns):
            column = Gtk.TreeViewColumn(self.columns[col].name,
                                        self.renderer, text=col)
            column.set_sort_column_id(col)
            column.add_attribute(self.renderer, "weight",
                                 col + self.nr_columns)
            self.treeview.append_column(column)

        self.cpus_filtered = cpus_filtered
        self.refreshing = True

        self.treeview.set_model(self.list_store)

    def foreach_selected_cb(self, model, path, iter, irq_list):
        irq = model.get_value(iter, self.COL_NUM)
        irq_list.append(str(irq))

    def on_drag_data_get_data(self, treeview, context,
                              selection, target_id, etime):
        treeselection = treeview.get_selection()
        irq_list = []
        treeselection.selected_foreach(self.foreach_selected_cb, irq_list)
        selection.set(selection.target, 8, "irq:" + ",".join(irq_list))

    def set_irq_columns(self, iter, irq, irq_info, nics):
        new_value = [None] * self.nr_columns
        users = tuna.get_irq_users(self.irqs, irq, nics)
        if self.has_threaded_irqs:
            irq_re = tuna.threaded_irq_re(irq)
            pids = self.ps.find_by_regex(irq_re)
            if pids:
                pid = pids[0]
                prio = int(self.ps[pid]["stat"]["rt_priority"])
                sched = tuna_sched.sched_str(os.sched_getscheduler(pid))[6:]
            else:
                sched = ""
                pid = -1
                prio = -1
            new_value[self.COL_PID] = pid
            new_value[self.COL_POL] = sched
            new_value[self.COL_PRI] = prio

        new_value[self.COL_NUM] = irq
        new_value[self.COL_AFF] = tuna.get_irq_affinity_text(self.irqs, irq)
        new_value[self.COL_EVENTS] = reduce(
            lambda a, b: a + b, irq_info["cpu"])
        new_value[self.COL_USERS] = ",".join(users)

        gui.set_store_columns(self.list_store, iter, new_value)

    def show(self):
        new_irqs = []
        for sirq in list(self.irqs.keys()):
            try:
                new_irqs.append(int(sirq))
            except:
                continue

        nics = ethtool.get_active_devices()

        row = self.list_store.get_iter_first()
        while row:
            irq = self.list_store.get_value(row, self.COL_NUM)
            # IRQ was unregistered? I.e. driver unloaded?
            if irq not in self.irqs:
                if self.list_store.remove(row):
                    # removed and row now its the next one
                    continue
                # Was the last one
                break
            if tuna.irq_filtered(irq, self.irqs, self.cpus_filtered,
                                 self.is_root):
                new_irqs.remove(irq)
                if self.list_store.remove(row):
                    # removed and row now its the next one
                    continue
                # Was the last one
                break
            try:
                new_irqs.remove(irq)
                irq_info = self.irqs[irq]
                self.set_irq_columns(row, irq, irq_info, nics)
            except:
                if self.list_store.remove(row):
                    # removed and row now its the next one
                    continue
                # Was the last one
                break

            row = self.list_store.iter_next(row)

        new_irqs.sort()
        for irq in new_irqs:
            if tuna.irq_filtered(irq, self.irqs, self.cpus_filtered,
                                 self.is_root):
                continue
            row = self.list_store.append()
            irq_info = self.irqs[irq]
            try:
                self.set_irq_columns(row, irq, irq_info, nics)
            except:
                self.list_store.remove(row)

        self.treeview.show_all()

    def refresh(self):
        if not self.refreshing:
            return
        self.irqs.reload()
        self.show()

    def refresh_toggle(self, unused):
        self.refreshing = not self.refreshing

    def edit_attributes(self, a):
        ret = self.treeview.get_path_at_pos(self.last_x, self.last_y)
        if not ret:
            return
        path, col, xpos, ypos = ret
        if not path:
            return
        row = self.list_store.get_iter(path)
        irq = self.list_store.get_value(row, self.COL_NUM)
        if irq not in self.irqs:
            return

        dialog = irq_druid(self.irqs, self.ps, irq, self.gladefile)
        if dialog.run():
            self.refresh()

    def on_irqlist_button_press_event(self, treeview, event):
        if event.type != Gdk.EventType.BUTTON_PRESS or event.button != 3:
            return

        self.last_x = int(event.x)
        self.last_y = int(event.y)

        menu = Gtk.Menu()

        setattr = Gtk.MenuItem(_("_Set IRQ attributes"), use_underline = True)
        if self.refreshing:
            refresh = Gtk.MenuItem(_("Sto_p refreshing the IRQ list"), use_underline = True)
        else:
            refresh = Gtk.MenuItem(_("_Refresh the IRQ list"), use_underline = True)

        menu.add(setattr)
        menu.add(refresh)

        setattr.connect_object('activate', self.edit_attributes, event)
        refresh.connect_object('activate', self.refresh_toggle, event)

        setattr.show()
        refresh.show()

        menu.popup(None, None, None, event, event.button, event.time)

    def toggle_mask_cpu(self, cpu, enabled):
        if not enabled:
            if cpu not in self.cpus_filtered:
                self.cpus_filtered.append(cpu)
                self.show()
        else:
            if cpu in self.cpus_filtered:
                self.cpus_filtered.remove(cpu)
                self.show()
