# Copyright (c) 2014-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, GLib, Gio

import string

from passbook.define import App
from passbook.keepass_secret import KeepassSecret
from passbook.helper_secrets import SecretsHelper


# Broken, signals from headerbar not working
# @Gtk.Template.from_resource
class AddPasswordDialog:
    """
        Dialog to add a new collection
    """

    # __gtype_name__ = "AddPasswordDialog"
    # name_entry = Gtk.Template.Child()
    # password_entry = Gtk.Template.Child()
    # headerbar = Gtk.Template.Child()

    def __init__(self):
        """
            Init dialog
            @param collection as Secret.Collection
        """
        builder = Gtk.Builder()
        builder.add_from_resource(
            "/org/gnome/Passbook/AddPasswordDialog.ui")
        builder.connect_signals(self)
        self.__collection = None
        self.__dialog = builder.get_object("dialog")
        self.__create_button = builder.get_object("create_button")
        self.__desc_entry = builder.get_object("desc_entry")
        self.__login_entry = builder.get_object("login_entry")
        self.__group_entry = builder.get_object("group_entry")
        self.__password_entry = builder.get_object("password_entry")
        self.__password_levelbar = builder.get_object("password_levelbar")
        self.__keyring_combo = builder.get_object("keyring_combo")
        self.__group_label = builder.get_object("group_label")
        self.__group_entry = builder.get_object("group_entry")
        self.__dialog.set_transient_for(App().window)
        # 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)

    def run(self):
        """
            Configure and run dialog
            @param default_collection as Secret.Collection
        """
        if App().window.default_collection is None:
            return
        default_path = App().window.default_collection.get_object_path()
        for collection in App().window.collections:
            collection_path = collection.get_object_path()
            # Path is not an URI (Keepass)
            if collection_path.startswith("/"):
                name = collection.get_label()
            else:
                f = Gio.File.new_for_uri(collection_path)
                name = f.get_basename()
            if default_path != collection_path:
                self.__keyring_combo.append(collection_path, name)
        self.__keyring_combo.set_active(0)
        self.__dialog.run()
        self.__dialog.destroy()

#######################
# PROTECTED           #
#######################
    def _on_random_clicked(self, button):
        """
            Generate a password
            @param button as Gtk.Button
        """
        import string
        import random
        ponctuation = "".join(
            random.choice(string.punctuation) for i in range(10))
        chars = string.ascii_letters + string.digits + ponctuation
        password = "".join(random.choice(chars) for i in range(10))
        self.__password_entry.set_text(password)
        self.__set_password_levelbar()
        self.__password_entry.set_visibility(True)
        sensitive = self.__password_entry.get_text() and\
            self.__desc_entry.get_text()
        self.__create_button.set_sensitive(sensitive)
        GLib.timeout_add(1000,
                         lambda: self.__password_entry.set_visibility(False))

    def _on_key_release_event(self, entry, event):
        """
            Update button visibility and set default response on Enter
            @param entry as Gtk.Entry
            @param event as Gdk.Event
        """
        if event.keyval in [Gdk.KEY_Return, Gdk.KEY_KP_Enter]:
            if self.__create_button.get_sensitive():
                self.__dialog.response(Gtk.ResponseType.OK)
        else:
            sensitive = self.__password_entry.get_text() and\
                self.__desc_entry.get_text()
            self.__create_button.set_sensitive(sensitive)
            if entry == self.__password_entry:
                self.__set_password_levelbar()

    def _on_response(self, dialog, response_id):
        """
            Add collection
            @param dialog as Gtk.Dialog
            @param response_id as int
        """
        def add_password(collection, helper, desc, login, password, group):
            """
                Add password to collection
                @param collection as Secret.Collection
                @param helper as SecretsHelper
                @param desc as str
                @param login as str
                @param password as str
                @param group as str
            """
            helper.add_password(collection, desc, login, password,
                                self.__on_add_password)

        def on_unlock(collection, helper, desc, login, password, group):
            """
                Replace locked collection by unlocked one
                @param collection as Secret.Collection
                @param helper as SecretsHelper
                @param desc as str
                @param login as str
                @param password as str
                @param group as str
            """
            # Works but don't know why :-/
            # How to properly reload a collection after an unlock?
            # Help wanted
            helper.get_collection(
                "",
                lambda x: add_password(
                             collection, helper, desc, login, password, group))

        if response_id in [Gtk.ResponseType.CANCEL,
                           Gtk.ResponseType.DELETE_EVENT]:
            return
        desc = self.__desc_entry.get_text()
        login = self.__login_entry.get_text()
        password = self.__password_entry.get_text()
        collection_path = self.__keyring_combo.get_active_id()
        for collection in App().window.collections:
            if collection.get_object_path() == collection_path:
                break
        if collection.get_object_path().startswith("/"):
            helper = SecretsHelper()
            group = self.__group_entry.get_text()
            if collection.get_locked():
                helper.unlock(collection, on_unlock,
                              helper, desc, login, password, group)
            else:
                add_password(collection, helper, desc, login, password, group)
        else:
            group = self.__group_entry.get_text()
            item = collection.add_password(desc, login, password, group)
            if item is not None:
                self.__on_add_password(collection,
                                       KeepassSecret(collection, item))

    def _on_password_entry_icon_press_event(self, entry, icon_pos, event):
        """
            Show password
            @param entry as Gtk.entry
            @param icon_pos as Gtk.EntryIconPosition
            @param event as Gdk.EventButton
        """
        entry.set_icon_from_icon_name(
            Gtk.EntryIconPosition.PRIMARY,
            "changes-prevent-symbolic")
        self.__password_entry.set_visibility(True)

    def _on_password_entry_icon_release_event(self, entry, icon_pos, event):
        """
            Hide password
            @param entry as Gtk.entry
            @param icon_pos as Gtk.EntryIconPosition
            @param event as Gdk.EventButton
        """
        entry.set_icon_from_icon_name(
            Gtk.EntryIconPosition.PRIMARY,
            "changes-allow-symbolic")
        self.__password_entry.set_visibility(False)

    def _on_cancel_clicked(self, button):
        """
            Close dialog
            @param button as Gtk.Button
        """
        self.__dialog.response(Gtk.ResponseType.CANCEL)

    def _on_create_clicked(self, button):
        """
            Create collection
            @param button as Gtk.Button
        """
        self.__dialog.response(Gtk.ResponseType.OK)

    def _on_keyring_combo_changed(self, combo):
        """
            Update widget internals
            @param combo as Gtk.ComboBoxText
        """
        active = combo.get_active_text()
        self.__update_completion_model(active)
        if active is not None and active.endswith(".kdbx"):
            self.__group_label.show()
            self.__group_entry.show()
        else:
            self.__group_label.hide()
            self.__group_entry.hide()

    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()

#######################
# PRIVATE             #
#######################
    def __update_completion_model(self, name):
        """
            Update completion model
            @param name as str
        """
        groups = []
        self.__completion_model.clear()
        from passbook.keepass_collection import KeepassCollection
        for collection in App().window.collections:
            if isinstance(collection, KeepassCollection):
                if collection.uri.endswith("/%s" %
                                           GLib.uri_escape_string(
                                            name, None, True)):
                    self.__collection = collection
                    if collection.kp is None:
                        from passbook.dialog_unlock import UnlockDialog
                        dialog = UnlockDialog(collection.uri, self.__dialog)
                        kp = dialog.run()
                        collection.set_kp(kp)
                    if collection.kp is not None:
                        groups += collection.kp.groups
                    break
        for group in groups:
            if group.path == "/":
                continue
            self.__completion_model.append([group.path])

    def __set_password_levelbar(self):
        """
            Update password levelbar
        """
        password = self.__password_entry.get_text()
        score = self.__get_password_score(password)
        context = self.__password_levelbar.get_style_context()
        for cls in context.list_classes():
            if cls.startswith("password-"):
                context.remove_class(cls)
        context.add_class("password-%s" % int(score))
        self.__password_levelbar.set_value(score)

    def __get_password_score(self, password):
        """
            Calculate password score
            @return score as float
        """
        score = 0
        password_set = "".join(set(password))
        # First calculate password length (different chars length)
        length = len(password_set)
        if length > 7:
            score = 2
        elif length > 5:
            score = 1
        lowercase = False
        uppercase = False
        digits = False
        others = False
        for c in password_set:
            if c in string.ascii_lowercase:
                lowercase = True
            elif c in string.ascii_uppercase:
                uppercase = True
            elif c in string.digits:
                digits = True
            else:
                others = True
        if lowercase and uppercase:
            score += 1
        if digits:
            score += 1
        if others:
            score += 1
        return score

    def __on_add_password(self, collection, item):
        """
            Set password on new collection
            @param collection as Secret.Collection
            @param item as Secret.Item
        """
        App().window.add_password(collection, item)
