# Copyright (c) 2017-2018 Cedric Bellegarde <cedric.bellegarde@adishatz.org>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program 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 this program. If not, see <http://www.gnu.org/licenses/>.

from gi.repository import Gtk, Gdk, Secret

from passbook.define import App
from passbook.logger import Logger
from passbook.keepass_secret import KeepassSecret


class SecretsListBoxRow(Gtk.ListBoxRow):
    """
        An image and a label
    """

    __USERNAME_KEYS = ["username", "login"]
    __INFO_KEYS = ["uri", "url", "host"]

    def __init__(self, item, collection):
        """
            Init row
            @param item as Secret.item/KeepassSecret
        """
        Gtk.ListBoxRow.__init__(self)
        self.__item = item
        self.__collection = collection
        self.set_property("halign", Gtk.Align.CENTER)
        self.get_style_context().add_class("secret-listbox-row")
        self.get_style_context().add_class("no-padding")
        builder = Gtk.Builder()
        builder.add_from_resource(
            "/org/gnome/Passbook/SecretsListBoxRow.ui")
        builder.connect_signals(self)
        self.__header_stack = builder.get_object("header_stack")
        self.__label = builder.get_object("label")
        self.__title_entry = builder.get_object("title_entry")
        self.__secret_entry = builder.get_object("secret_entry")
        self.__username_label = builder.get_object("username_label")
        self.__username_entry = builder.get_object("username_entry")
        self.__revealer = builder.get_object("revealer")
        self.__group_entry = builder.get_object("group_entry")
        self.__set_attributes(item)
        if not isinstance(item, Secret.Item):
            # Init new tag completion model
            self.__completion_model = Gtk.ListStore(str)
            self.__completion = Gtk.EntryCompletion.new()
            self.__completion.set_model(self.__completion_model)
            self.__completion.set_text_column(0)
            self.__completion.set_inline_completion(False)
            self.__completion.set_popup_completion(True)
            self.__group_entry.set_completion(self.__completion)
            builder.get_object("group_label").show()
            self.__group_entry.show()
            group = item.kp_item.parentgroup.path
            if group != "/":
                self.__group_entry.set_text(group)
                self.__group_entry.set_tooltip_text(group)
            for group in collection.kp.groups:
                if group.path == "/":
                    continue
                self.__completion_model.append([group.path])
        self.add(builder.get_object("widget"))

    def reveal(self, reveal):
        """
            Reveal row
            @param reveal as bool
        """
        if reveal:
            self.__header_stack.set_visible_child_name("title_edit")
            self.__revealer.set_reveal_child(True)
            secret = self.__item.get_secret()
            if secret is None:
                self.__item.load_secret(None, self.__on_load_secret)
            else:
                self.__secret_entry.set_text(secret.get().decode('utf-8'))
        else:
            self.__header_stack.set_visible_child_name("title")
            self.__revealer.set_reveal_child(False)
            self.__secret_entry.set_visibility(False)

    def do_get_preferred_width(self):
        """
            Return preferred width
            @return (int, int)
        """
        width = Gtk.ListBoxRow.do_get_preferred_width(self)[0]
        return (width, width * 2)

    @property
    def is_reveal(self):
        """
            True if reveal
            @return bool
        """
        return self.__revealer.get_child_revealed()

    @property
    def collection(self):
        """
            Get collection
            @return Secret.Collection/KeepassCollection
        """
        return self.__collection

    @property
    def item(self):
        """
            Get item
            @return Secret.Item/KeepassSecret
        """
        return self.__item

    @property
    def label(self):
        """
            Get row label
        """
        return self.__label.get_text()

#######################
# PROTECTED           #
#######################
    def _on_header_button_release_event(self, eventbox, event):
        """
            Reveal/Unreveal widget
            @param eventbox as Gtk.EventBox
            @param event as Gdk.ButtonEvent
        """
        self.reveal(not self.is_reveal)

    def _on_header_entry_button_press_event(self, entry, event):
        """
            Block event
            @param entry as Gtk.Entry
            @param event as Gdk.ButtonEvent
        """
        entry.do_button_press_event(entry, event)
        return True

    def _on_realize(self, eventbox):
        """
            Set cursor
            @param eventbox as Gtk.EventBox
        """
        window = eventbox.get_window()
        if window is not None:
            window.set_cursor(Gdk.Cursor(Gdk.CursorType.HAND2))

    def _on_save_clicked(self, button):
        """
            Save current item
            @param button as gtk.Button
        """
        def on_attributes_set(item, result):
            try:
                item.set_attributes_finish(result)
            except Exception as e:
                Logger.error("SecretsListBoxRow::on_attributes_set(): %s" % e)

        def on_password_set(item, result):
            try:
                item.set_secret_finish(result)
            except Exception as e:
                Logger.error("SecretsListBoxRow::on_password_set(): %s" % e)

        self.reveal(False)
        self.__label.set_text(self.__title_entry.get_text())
        # Recreate item (pykeepass does not handle group modification)
        if self.__group_entry.is_visible():
            group = self.__group_entry.get_text()
            desc = self.__title_entry.get_text()
            login = self.__username_entry.get_text()
            password = self.__secret_entry.get_text()
            self.__item.delete(None, None, False)
            kp_item = self.__collection.add_password(desc, login,
                                                     password, group)
            if kp_item is not None:
                self.__item = KeepassSecret(self.__collection, kp_item)
        else:
            attributes = self.__item.get_attributes()
            # First update login
            if "login" in attributes.keys():
                attributes["login"] = self.__username_entry.get_text()
                # Rebuild schema
                attributes_types = {}
                name = self.__item.get_name()
                for key in attributes.keys():
                    if key == "xdg:schema":
                        name = attributes[key]
                    _type = type(attributes[key])
                    if _type is str:
                        attributes_types[key] =\
                            Secret.SchemaAttributeType.STRING
                    elif _type is int:
                        attributes_types[key] =\
                            Secret.SchemaAttributeType.INTEGER
                    else:
                        attributes_types[key] =\
                            Secret.SchemaAttributeType.BOOLEAN
                schema = Secret.Schema.new(name,
                                           Secret.SchemaFlags.NONE,
                                           attributes_types)
                self.__item.set_attributes(schema, attributes,
                                           None, on_attributes_set)
            self.__item.set_label(self.__title_entry.get_text(),
                                  None, None)
            # Save password
            password = self.__secret_entry.get_text()
            value = Secret.Value(password, len(password), "text/plain")
            self.__item.set_secret(value, None, on_password_set)

    def _on_delete_clicked(self, button):
        """
            Delete current item
            @param button as Gtk.Button
        """
        if button.get_style_context().has_class("menu-button-destructive"):
            self.__item.delete(None, None)
            self.destroy()
            App().window.delete_password(self.__item)
        else:
            button.get_style_context().remove_class("menu-button")
            button.get_style_context().add_class("menu-button-destructive")

    def _on_icon_release(self, entry, icon_pos, event):
        """
            Show a popover with groups
            @param entry as Gtk.Entry
            @param icon_pos as Gtk.EntryIconPosition
            @param event as Gdk.EventButton
        """
        from passbook.pop_groups import GroupsPopover
        popover = GroupsPopover(self.__collection, entry)
        popover.set_relative_to(entry)
        popover.set_pointing_to(entry.get_icon_area(icon_pos))
        popover.set_position(Gtk.PositionType.RIGHT)
        popover.popup()

    def _on_password_entry_icon_press_event(self, entry, icon_pos, event):
        """
            Show/Hide password
            @param entry as Gtk.entry
            @param icon_pos as Gtk.EntryIconPosition
            @param event as Gdk.EventButton
        """
        visibility = self.__secret_entry.get_visibility()
        if visibility:
            entry.set_icon_from_icon_name(
                Gtk.EntryIconPosition.PRIMARY,
                "changes-allow-symbolic")
        else:
            entry.set_icon_from_icon_name(
                Gtk.EntryIconPosition.PRIMARY,
                "changes-prevent-symbolic")
        self.__secret_entry.set_visibility(not visibility)

#######################
# PRIVATE             #
#######################
    def __set_attributes(self, item):
        """
            Set attributes on widget (login, info)
        """
        text = item.get_label()
        attributes = item.get_attributes()
        keys = attributes.keys()
        for key in keys:
            if key in self.__USERNAME_KEYS:
                if attributes[key] is not None:
                    self.__username_entry.set_text(attributes[key])
                    self.__username_entry.set_tooltip_text(attributes[key])
                    if attributes[key] not in text:
                        text = "%s - %s" % (text, attributes[key])
            elif key in self.__INFO_KEYS:
                if attributes[key] not in text:
                    text = "%s - %s" % (text, attributes[key])
        self.__label.set_text(text)
        self.__title_entry.set_text(item.get_label())
        if not self.__username_entry.get_text() and len(keys) > 1:
            self.__username_entry.hide()
            self.__username_label.hide()

    def __on_load_secret(self, source, result):
        """
            Set username/password input
            @param source as GObject.Object
            @param result as Gio.AsyncResult
        """
        secret = source.get_secret()
        if secret is not None:
            self.__secret_entry.set_text(secret.get().decode('utf-8'))
