# Copyright (C) 2018 Red Hat, Inc.
#
# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.

from gi.repository import Gdk, Gtk

import virtinst
from virtinst import xmlutil

from .baseclass import vmmGObjectUI


def _always_show(osobj):
    return bool(osobj.is_generic() or osobj.is_linux_generic())


class vmmOSList(vmmGObjectUI):
    __gsignals__ = {"os-selected": (vmmGObjectUI.RUN_FIRST, None, [object])}

    def __init__(self):
        vmmGObjectUI.__init__(self, "oslist.ui", "vmm-oslist")
        self._cleanup_on_app_close()

        self._filter_name = None
        self._filter_eol = True
        self._selected_os = None
        self.search_entry = self.widget("os-name")
        self.search_entry.set_placeholder_text(_("Type to start searching..."))
        self.eol_text = self.widget("eol-warn").get_text()

        self.builder.connect_signals(
            {
                "on_include_eol_toggled": self._eol_toggled_cb,
                "on_os_name_activate": self._entry_activate_cb,
                "on_os_name_key_press_event": self._key_press_cb,
                "on_os_name_search_changed": self._search_changed_cb,
                "on_os_name_stop_search": self._stop_search_cb,
                "on_os_list_row_activated": self._os_selected_cb,
            }
        )

        self._init_state()

    def _cleanup(self):
        pass

    ###########
    # UI init #
    ###########

    def _init_state(self):
        os_list = self.widget("os-list")

        # (os object, label)
        os_list_model = Gtk.ListStore(object, str)

        all_os = virtinst.OSDB.list_os(sortkey="label")
        # Always put the generic entries at the end of the list
        all_os = list(sorted(all_os, key=_always_show))

        for os in all_os:
            os_list_model.append([os, "%s (%s)" % (os.label, os.name)])

        model_filter = Gtk.TreeModelFilter(child_model=os_list_model)
        model_filter.set_visible_func(self._filter_os_cb)

        os_list.set_model(model_filter)

        nameCol = Gtk.TreeViewColumn(_("Name"))
        nameCol.set_spacing(6)

        text = Gtk.CellRendererText()
        nameCol.pack_start(text, True)
        nameCol.add_attribute(text, "text", 1)
        os_list.append_column(nameCol)

        markup = "<small>%s</small>" % xmlutil.xml_escape(self.widget("eol-warn").get_text())
        self.widget("eol-warn").set_markup(markup)

    ###################
    # Private helpers #
    ###################

    def _set_default_selection(self):
        os_list = self.widget("os-list")
        sel = os_list.get_selection()
        if not self.is_visible():
            return
        if not len(os_list.get_model()):
            return  # pragma: no cover
        sel.select_iter(os_list.get_model()[0].iter)

    def _refilter(self):
        os_list = self.widget("os-list")
        sel = os_list.get_selection()
        sel.unselect_all()
        os_list.get_model().refilter()
        self._set_default_selection()

    def _filter_by_name(self, partial_name):
        self._filter_name = partial_name.lower()
        self._refilter()

    def _clear_filter(self):
        self._filter_by_name("")
        self.widget("os-scroll").get_vadjustment().set_value(0)

    def _sync_os_selection(self):
        model, titer = self.widget("os-list").get_selection().get_selected()
        self._selected_os = None
        if titer:
            self._selected_os = model[titer][0]
            self.search_entry.set_text(self._selected_os.label)

        self.emit("os-selected", self._selected_os)

    def _show_popover(self):
        # Match width to the search_entry width. Height is based on
        # whatever we can fit into the hardcoded create wizard sizes
        r = self.search_entry.get_allocation()
        self.topwin.set_size_request(r.width, 350)

        self.topwin.set_relative_to(self.search_entry)
        self.topwin.popup()
        self._set_default_selection()

    ################
    # UI Callbacks #
    ################

    def _entry_activate_cb(self, src):
        os_list = self.widget("os-list")
        if not os_list.is_visible():
            return  # pragma: no cover

        sel = os_list.get_selection()
        model, rows = sel.get_selected_rows()
        if rows:
            self.select_os(model[rows[0]][0])

    def _key_press_cb(self, src, event):
        if Gdk.keyval_name(event.keyval) != "Down":
            return
        self._show_popover()
        self.widget("os-list").grab_focus()

    def _eol_toggled_cb(self, src):
        self._filter_eol = not src.get_active()
        self._refilter()

    def _search_changed_cb(self, src):
        """
        Called text in search_entry is changed
        """
        searchname = src.get_text().strip()
        selected_label = None
        if self._selected_os:
            selected_label = self._selected_os.label

        if not src.get_sensitive() or not searchname or selected_label == searchname:
            self.topwin.popdown()
            self._clear_filter()
            return

        self._filter_by_name(searchname)
        self._show_popover()

    def _stop_search_cb(self, src):
        """
        Called when the search window is closed, like with Escape key
        """
        if self._selected_os:
            self.search_entry.set_text(self._selected_os.label)
        else:
            self.search_entry.set_text("")

    def _os_selected_cb(self, src, path, column):
        self._sync_os_selection()

    def _filter_os_cb(self, model, titer, ignore1):
        osobj = model.get(titer, 0)[0]
        if self._filter_eol:
            if osobj.eol:
                return False

        if _always_show(osobj):
            return True

        if self._filter_name is not None and self._filter_name != "":
            label = osobj.label.lower()
            name = osobj.name.lower()
            if label.find(self._filter_name) == -1 and name.find(self._filter_name) == -1:
                return False

        return True

    ###############
    # Public APIs #
    ###############

    def reset_state(self):
        self._selected_os = None
        self.search_entry.set_text("")
        self._clear_filter()
        self._sync_os_selection()

    def select_os(self, vmosobj):
        self._clear_filter()

        os_list = self.widget("os-list")
        if vmosobj.eol and not self.widget("include-eol").get_active():
            self.widget("include-eol").set_active(True)

        for row in os_list.get_model():
            osobj = row[0]
            if osobj.name != vmosobj.name:
                continue

            os_list.get_selection().select_iter(row.iter)
            self._sync_os_selection()
            return

    def get_selected_os(self):
        return self._selected_os

    def set_sensitive(self, sensitive):
        if sensitive == self.search_entry.get_sensitive():
            return

        if not sensitive:
            self.search_entry.set_sensitive(False)
            self.reset_state()
        else:
            if self._selected_os:
                self.select_os(self._selected_os)
            else:
                self.reset_state()
            self.search_entry.set_sensitive(True)
