#
# Gajim 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; version 3 only.
#
# Gajim 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 Gajim. If not, see <http://www.gnu.org/licenses/>.

from typing import Any
from typing import Optional

import logging

from gi.repository import Gtk

from nbxmpp.const import Affiliation
from nbxmpp.task import Task
from nbxmpp.errors import StanzaError
from nbxmpp.structs import MucSubject
from nbxmpp.modules.vcard_temp import VCard
from nbxmpp.namespaces import Namespace

from gajim.common import app
from gajim.common.const import AvatarSize
from gajim.common.helpers import validate_jid
from gajim.common.i18n import _
from gajim.common.modules.contacts import GroupchatContact

from .avatar_selector import AvatarSelector
from .builder import get_builder
from .dialogs import ErrorDialog
from .filechoosers import AvatarChooserDialog

log = logging.getLogger('gajim.gui.groupchat_manage')


class GroupchatManage(Gtk.Box):
    def __init__(self,
                 account: str,
                 contact: GroupchatContact,
                 ) -> None:
        Gtk.Box.__init__(self)
        self._account = account
        self._client = app.get_client(account)
        self._contact = contact
        self._contact.connect('room-subject', self._on_room_subject)

        self._room_config_form = None

        self._ui = get_builder('groupchat_manage.ui')
        self.add(self._ui.stack)
        self._ui.connect_signals(self)

        self._avatar_selector = AvatarSelector()
        self._avatar_selector.set_size_request(500, 500)
        self._ui.avatar_selector_grid.attach(self._avatar_selector, 0, 1, 1, 1)

        self._prepare_subject()
        self._prepare_manage()

        self._ui.subject_textview.get_buffer().connect(
            'changed', self._on_subject_text_changed)
        self.connect('destroy', self._on_destroy)

        self.show_all()

    def _on_destroy(self, *args: Any) -> None:
        del self._avatar_selector
        app.check_finalize(self)

    @property
    def disco_info(self):
        return app.storage.cache.get_last_disco_info(self._contact.jid)

    def _prepare_subject(self) -> None:
        text = ''
        if self._contact.subject is not None:
            text = self._contact.subject.text

        self._ui.subject_textview.get_buffer().set_text(text)

        joined = self._contact.is_joined
        change_allowed = self._is_subject_change_allowed()
        self._ui.subject_textview.set_sensitive(joined and change_allowed)

    def _is_subject_change_allowed(self) -> bool:
        contact = self._contact.get_self()
        if contact is None:
            return False

        if contact.affiliation in (Affiliation.OWNER, Affiliation.ADMIN):
            return True

        if self.disco_info is None:
            return False
        return self.disco_info.muc_subjectmod or False

    def _on_subject_text_changed(self, buffer_: Gtk.TextBuffer) -> None:
        text = buffer_.get_text(buffer_.get_start_iter(),
                                buffer_.get_end_iter(),
                                False)

        assert self._contact.subject is not None

        self._ui.subject_change_button.set_sensitive(
            text != self._contact.subject.text)

    def _on_subject_change_clicked(self, _button: Gtk.Button) -> None:
        buffer_ = self._ui.subject_textview.get_buffer()
        subject = buffer_.get_text(buffer_.get_start_iter(),
                                   buffer_.get_end_iter(),
                                   False)
        self._client.get_module('MUC').set_subject(self._contact.jid, subject)

    def _on_room_subject(self,
                         _contact: GroupchatContact,
                         _signal_name: str,
                         subject: Optional[MucSubject]
                         ) -> None:
        if subject is None:
            return

        self._ui.subject_textview.get_buffer().set_text(subject.text)

    def _prepare_manage(self) -> None:
        joined = self._contact.is_joined
        vcard_support = False
        self_contact = self._contact.get_self()
        assert self_contact

        if self.disco_info is not None:
            vcard_support = self.disco_info.supports(Namespace.VCARD)

            self._ui.muc_name_entry.set_text(self.disco_info.muc_name or '')
            self._ui.muc_description_entry.set_text(
                self.disco_info.muc_description or '')

        if (joined and vcard_support and
                self_contact.affiliation.is_owner):
            self._ui.avatar_select_button.show()

        self.update_avatar()

        if self_contact.affiliation.is_owner:
            self._client.get_module('MUC').request_config(
                self._contact.jid, callback=self._on_manage_form_received)
            self._ui.destroy_muc_button.set_sensitive(True)

    def _on_manage_form_received(self, task: Task) -> None:
        try:
            result = task.finish()
        except StanzaError as error:
            log.info(error)
            return

        self._ui.muc_name_entry.set_sensitive(True)
        self._ui.muc_description_entry.set_sensitive(True)
        self._room_config_form = result.form

    def _on_name_desc_changed(self, _entry: Gtk.Entry) -> None:
        if self.disco_info is not None:
            disco_name = self.disco_info.muc_name or ''
            disco_desc = self.disco_info.muc_description or ''
            name = self._ui.muc_name_entry.get_text()
            desc = self._ui.muc_description_entry.get_text()

            self._ui.manage_save_button.set_sensitive(
                desc != disco_desc or name != disco_name)

    def _on_manage_save_clicked(self, _button: Gtk.Button) -> None:
        if self._room_config_form is not None:
            name = self._ui.muc_name_entry.get_text()
            desc = self._ui.muc_description_entry.get_text()
            try:
                name_field = self._room_config_form['muc#roomconfig_roomname']
                desc_field = self._room_config_form['muc#roomconfig_roomdesc']
            except KeyError:
                pass
            else:
                name_field.value = name
                desc_field.value = desc

            self._client.get_module('MUC').set_config(
                self._contact.jid, self._room_config_form)

    def _on_avatar_cancel_clicked(self, _button: Gtk.Button) -> None:
        self._ui.stack.set_visible_child_name('manage')

    def _on_change_avatar_clicked(self, _button: Gtk.Button) -> None:
        def _on_accept(path: str) -> None:
            self._avatar_selector.prepare_crop_area(path)
            self._ui.avatar_update_button.set_sensitive(
                self._avatar_selector.get_prepared())
            self._ui.stack.set_visible_child_name('avatar')
            self._ui.avatar_update_button.grab_default()

        AvatarChooserDialog(_on_accept,
                            transient_for=self.get_toplevel(),
                            modal=True)

    def _on_avatar_select_file_clicked(self, _button: Gtk.Button) -> None:
        def _on_accept(path: str) -> None:
            self._avatar_selector.prepare_crop_area(path)
            self._ui.avatar_update_button.set_sensitive(
                self._avatar_selector.get_prepared())

        AvatarChooserDialog(_on_accept,
                            transient_for=self.get_toplevel(),
                            modal=True)

    def _on_upload_avatar_result(self, task: Task) -> None:
        try:
            task.finish()
        except Exception as error:
            ErrorDialog(_('Uploading Avatar Failed'),
                        _('Uploading avatar image failed: %s' % error))

    def _on_avatar_update_clicked(self, _button: Gtk.Button) -> None:
        success, data, _w, _h = self._avatar_selector.get_avatar_bytes()
        if not success:
            ErrorDialog(_('Loading Avatar Failed'),
                        _('Loading avatar image failed'))
            return

        assert data
        sha = app.app.avatar_storage.save_avatar(data)
        if sha is None:
            ErrorDialog(_('Saving Avatar Failed'),
                        _('Saving avatar image failed'))
            return

        vcard = VCard()
        vcard.set_avatar(data, 'image/png')

        self._client.get_module('VCardTemp').set_vcard(
            vcard,
            jid=self._contact.jid,
            callback=self._on_upload_avatar_result)
        self._ui.stack.set_visible_child_name('manage')

    def update_avatar(self):
        surface = app.app.avatar_storage.get_muc_surface(
            self._account,
            self._contact.jid,
            AvatarSize.GROUP_INFO,
            self.get_scale_factor())
        self._ui.avatar_button_image.set_from_surface(surface)

    def _on_destroy_clicked(self, _button: Gtk.Button) -> None:
        self._ui.stack.set_visible_child_name('destroy')
        self._ui.destroy_reason_entry.grab_focus()
        self._ui.destroy_button.grab_default()

    def _on_destroy_alternate_changed(self, entry: Gtk.Entry) -> None:
        jid = entry.get_text()
        if jid:
            try:
                jid = validate_jid(jid)
            except Exception:
                icon = 'dialog-warning-symbolic'
                text = _('Invalid XMPP Address')
                self._ui.destroy_alternate_entry.set_icon_from_icon_name(
                    Gtk.EntryIconPosition.SECONDARY, icon)
                self._ui.destroy_alternate_entry.set_icon_tooltip_text(
                    Gtk.EntryIconPosition.SECONDARY, text)
                self._ui.destroy_button.set_sensitive(False)
                return
        self._ui.destroy_alternate_entry.set_icon_from_icon_name(
            Gtk.EntryIconPosition.SECONDARY, None)
        self._ui.destroy_button.set_sensitive(True)

    def _on_destroy_confirmed(self, _button: Gtk.Button) -> None:
        reason = self._ui.destroy_reason_entry.get_text()
        alternate_jid = self._ui.destroy_alternate_entry.get_text()
        self._client.get_module('MUC').destroy(
            self._contact.jid, reason, alternate_jid)
        window = self.get_toplevel()
        window.destroy()

    def _on_destroy_cancelled(self, _button: Gtk.Button) -> None:
        self._ui.stack.set_visible_child_name('manage')
