
# slixmpp: The Slick XMPP Library
# Copyright (C) 2011  Nathanael C. Fritz
# This file is part of slixmpp.
# See the file LICENSE for copying permission.
import logging
import zlib


from slixmpp.stanza import StreamFeatures
from slixmpp.xmlstream import RestartStream, register_stanza_plugin, ElementBase, StanzaBase
from slixmpp.xmlstream.matcher import *
from slixmpp.xmlstream.handler import *
from slixmpp.plugins import BasePlugin, register_plugin

log = logging.getLogger(__name__)


class Compression(ElementBase):
    name = 'compression'
    namespace = 'http://jabber.org/features/compress'
    interfaces = {'methods'}
    plugin_attrib = 'compression'
    plugin_tag_map = {}
    plugin_attrib_map = {}

    def get_methods(self):
        methods = []
        for method in self.xml.findall('{%s}method' % self.namespace):
            methods.append(method.text)
        return methods


class Compress(StanzaBase):
    name = 'compress'
    namespace = 'http://jabber.org/protocol/compress'
    interfaces = {'method'}
    sub_interfaces = interfaces
    plugin_attrib = 'compress'
    plugin_tag_map = {}
    plugin_attrib_map = {}

    def setup(self, xml):
        StanzaBase.setup(self, xml)
        self.xml.tag = self.tag_name()


class Compressed(StanzaBase):
    name = 'compressed'
    namespace = 'http://jabber.org/protocol/compress'
    interfaces = set()
    plugin_tag_map = {}
    plugin_attrib_map = {}

    def setup(self, xml):
        StanzaBase.setup(self, xml)
        self.xml.tag = self.tag_name()




class ZlibSocket(object):

    def __init__(self, socketobj):
        self.__socket = socketobj
        self.compressor = zlib.compressobj()
        self.decompressor = zlib.decompressobj(zlib.MAX_WBITS)

    def __getattr__(self, name):
        return getattr(self.__socket, name)

    def send(self, data):
        sentlen = len(data)
        data = self.compressor.compress(data)
        data += self.compressor.flush(zlib.Z_SYNC_FLUSH)
        log.debug(b'>>> (compressed)' + (data.encode("hex")))
        #return self.__socket.send(data)
        sentactuallen = self.__socket.send(data)
        assert(sentactuallen == len(data))

        return sentlen

    def recv(self, *args, **kwargs):
        data = self.__socket.recv(*args, **kwargs)
        log.debug(b'<<< (compressed)' + data.encode("hex"))
        return self.decompressor.decompress(self.decompressor.unconsumed_tail + data)


class XEP_0138(BasePlugin):
    """
    XEP-0138: Compression
    """
    name = "xep_0138"
    description = "XEP-0138: Compression"
    dependencies = {"xep_0030"}

    def plugin_init(self):
        self.xep = '0138'
        self.description = 'Stream Compression (Generic)'

        self.compression_methods = {'zlib': True}

        register_stanza_plugin(StreamFeatures, Compression)
        self.xmpp.register_stanza(Compress)
        self.xmpp.register_stanza(Compressed)

        self.xmpp.register_handler(
                Callback('Compressed',
                    StanzaPath('compressed'),
                    self._handle_compressed,
                    instream=True))

        self.xmpp.register_feature('compression',
                self._handle_compression,
                restart=True,
                order=self.config.get('order', 5))

    def register_compression_method(self, name, handler):
        self.compression_methods[name] = handler

    def _handle_compression(self, features):
        for method in features['compression']['methods']:
            if method in self.compression_methods:
                log.info('Attempting to use %s compression' % method)
                c = Compress(self.xmpp)
                c['method'] = method
                c.send(now=True)
                return True
        return False

    def _handle_compressed(self, stanza):
        self.xmpp.features.add('compression')
        log.debug('Stream Compressed!')
        compressed_socket = ZlibSocket(self.xmpp.socket)
        self.xmpp.set_socket(compressed_socket)
        raise RestartStream()

    def _handle_failure(self, stanza):
        pass

xep_0138 = XEP_0138
register_plugin(XEP_0138)
