# This file is part of Gajim.
#
# 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/>.

# XEP-0153: vCard-Based Avatars

import os
import logging
from pathlib import Path

import nbxmpp
from nbxmpp.structs import StanzaHandler
from nbxmpp.const import AvatarState

from gajim.common import app
from gajim.common import helpers
from gajim.common import configpaths
from gajim.common.const import RequestAvatar

log = logging.getLogger('gajim.c.m.vcard.avatars')


class VCardAvatars:
    def __init__(self, con):
        self._con = con
        self._account = con.name
        self._requested_shas = []

        self.handlers = [
            StanzaHandler(name='presence',
                          callback=self._presence_received,
                          ns=nbxmpp.NS_VCARD_UPDATE,
                          priority=51),
        ]

        self.avatar_advertised = False
        self._find_own_avatar()

    def _find_own_avatar(self):
        sha = app.config.get_per('accounts', self._account, 'avatar_sha')
        if not sha:
            return
        path = Path(configpaths.get('AVATAR')) / sha
        if not path.exists():
            log.info('Missing own avatar, reset sha')
            app.config.set_per('accounts', self._account, 'avatar_sha', '')

    def _presence_received(self, _con, _stanza, properties):
        if properties.avatar_state in (AvatarState.IGNORE,
                                       AvatarState.NOT_READY):
            return

        if self._con.get_own_jid().bareMatch(properties.jid):
            if self._con.get_own_jid() == properties.jid:
                # Initial presence reflection
                if self._con.avatar_conversion:
                    # XEP-0398: Tells us the current avatar sha on the
                    # inital presence reflection
                    self._self_update_received(properties)
            else:
                # Presence from another resource of ours
                self._self_update_received(properties)
            return

        if properties.from_muc:
            self._gc_update_received(properties)
        else:
            # Check if presence is from a MUC service
            contact = app.contacts.get_groupchat_contact(self._account,
                                                         str(properties.jid))
            self._update_received(properties, room=contact is not None)

    def _self_update_received(self, properties):
        jid = properties.jid.getBare()
        if properties.avatar_state == AvatarState.EMPTY:
            # Empty <photo/> tag, means no avatar is advertised
            log.info('%s has no avatar published', properties.jid)
            app.config.set_per('accounts', self._account, 'avatar_sha', '')
            app.contacts.set_avatar(self._account, jid, None)
            app.interface.update_avatar(self._account, jid)
            return

        log.info('Update: %s %s', jid, properties.avatar_sha)
        current_sha = app.config.get_per(
            'accounts', self._account, 'avatar_sha')

        if properties.avatar_sha != current_sha:
            path = Path(configpaths.get('AVATAR')) / properties.avatar_sha
            if path.exists():
                app.config.set_per('accounts', self._account,
                                   'avatar_sha', properties.avatar_sha)
                app.contacts.set_avatar(self._account,
                                        jid,
                                        properties.avatar_sha)
                app.interface.update_avatar(self._account, jid)
            else:
                log.info('Request : %s', jid)
                self._con.get_module('VCardTemp').request_vcard(
                    RequestAvatar.SELF)
        else:
            log.info('Avatar already known: %s %s',
                     jid, properties.avatar_sha)

    def _update_received(self, properties, room=False):
        jid = properties.jid.getBare()
        if properties.avatar_state == AvatarState.EMPTY:
            # Empty <photo/> tag, means no avatar is advertised
            log.info('%s has no avatar published', properties.jid)

            # Remove avatar
            log.debug('Remove: %s', jid)
            app.contacts.set_avatar(self._account, jid, None)
            acc_jid = self._con.get_own_jid().getStripped()
            if not room:
                app.logger.set_avatar_sha(acc_jid, jid, None)
            app.interface.update_avatar(
                self._account, jid, room_avatar=room)
        else:
            log.info('Update: %s %s', properties.jid, properties.avatar_sha)
            current_sha = app.contacts.get_avatar_sha(self._account, jid)

            if properties.avatar_sha == current_sha:
                log.info('Avatar already known: %s %s',
                         jid, properties.avatar_sha)
                return

            if room:
                # We dont save the room avatar hash in our DB, so check
                # if we previously downloaded it
                if app.interface.avatar_exists(properties.avatar_sha):
                    app.contacts.set_avatar(self._account,
                                            jid,
                                            properties.avatar_sha)
                    app.interface.update_avatar(
                        self._account, jid, room_avatar=room)
                    return

            if properties.avatar_sha not in self._requested_shas:
                self._requested_shas.append(properties.avatar_sha)
                if room:
                    self._con.get_module('VCardTemp').request_vcard(
                        RequestAvatar.ROOM, jid, sha=properties.avatar_sha)
                else:
                    self._con.get_module('VCardTemp').request_vcard(
                        RequestAvatar.USER, jid, sha=properties.avatar_sha)

    def _gc_update_received(self, properties):
        nick = properties.jid.getResource()

        gc_contact = app.contacts.get_gc_contact(
            self._account, properties.jid.getBare(), nick)

        if gc_contact is None:
            log.error('no gc contact found: %s', nick)
            return

        if properties.avatar_state == AvatarState.EMPTY:
            # Empty <photo/> tag, means no avatar is advertised, remove avatar
            log.info('%s has no avatar published', nick)
            log.debug('Remove: %s', nick)
            gc_contact.avatar_sha = None
            app.interface.update_avatar(contact=gc_contact)
        else:
            log.info('Update: %s %s', nick, properties.avatar_sha)
            path = os.path.join(configpaths.get('AVATAR'),
                                properties.avatar_sha)
            if not os.path.isfile(path):
                if properties.avatar_sha not in self._requested_shas:
                    app.log('avatar').info('Request: %s', nick)
                    self._requested_shas.append(properties.avatar_sha)
                    self._con.get_module('VCardTemp').request_vcard(
                        RequestAvatar.USER, str(properties.jid),
                        room=True, sha=properties.avatar_sha)
                return

            if gc_contact.avatar_sha != properties.avatar_sha:
                log.info('%s changed their Avatar: %s',
                         nick, properties.avatar_sha)
                gc_contact.avatar_sha = properties.avatar_sha
                app.interface.update_avatar(contact=gc_contact)
            else:
                log.info('Avatar already known: %s', nick)

    def send_avatar_presence(self, force=False, after_publish=False):
        if self._con.avatar_conversion:
            if not after_publish:
                # XEP-0398: We only resend presence after we publish a
                # new avatar
                return
        else:
            if self.avatar_advertised and not force:
                log.debug('Avatar already advertised')
                return

        show = helpers.get_xmpp_show(app.SHOW_LIST[self._con.connected])

        self._con.get_module('Presence').send_presence(
            priority=self._con.priority,
            show=show,
            status=self._con.status)

        self.avatar_advertised = True

    def add_update_node(self, node):
        update = node.setTag('x', namespace=nbxmpp.NS_VCARD_UPDATE)
        if self._con.get_module('VCardTemp').own_vcard_received:
            sha = app.config.get_per('accounts', self._account, 'avatar_sha')
            own_jid = self._con.get_own_jid()
            log.info('Send avatar presence to: %s %s',
                     node.getTo() or own_jid, sha or 'no sha advertised')
            update.setTagData('photo', sha)


def get_instance(*args, **kwargs):
    return VCardAvatars(*args, **kwargs), 'VCardAvatars'