"""
    SleekXMPP: The Sleek XMPP Library
    Copyright (C) 2010  Nathanael C. Fritz
    This file is part of SleekXMPP.

    See the file LICENSE for copying permission.
"""

from __future__ import with_statement, unicode_literals

import copy
import logging
import socket as Socket
import ssl
import sys
import threading
import time
import types
import signal
try:
    import queue
except ImportError:
    import Queue as queue

from sleekxmpp.thirdparty.statemachine import StateMachine
from sleekxmpp.xmlstream import Scheduler, tostring
from sleekxmpp.xmlstream.stanzabase import StanzaBase, ET

# In Python 2.x, file socket objects are broken. A patched socket
# wrapper is provided for this case in filesocket.py.
if sys.version_info < (3, 0):
    from sleekxmpp.xmlstream.filesocket import FileSocket, Socket26


# The time in seconds to wait before timing out waiting for response stanzas.
RESPONSE_TIMEOUT = 10

# The number of threads to use to handle XML stream events. This is not the
# same as the number of custom event handling threads. HANDLER_THREADS must
# be at least 1.
HANDLER_THREADS = 1

# Flag indicating if the SSL library is available for use.
SSL_SUPPORT = True


log = logging.getLogger(__name__)


class RestartStream(Exception):
    """
    Exception to restart stream processing, including
    resending the stream header.
    """


class XMLStream(object):
    """
    An XML stream connection manager and event dispatcher.

    The XMLStream class abstracts away the issues of establishing a
    connection with a server and sending and receiving XML "stanzas".
    A stanza is a complete XML element that is a direct child of a root
    document element. Two streams are used, one for each communication
    direction, over the same socket. Once the connection is closed, both
    streams should be complete and valid XML documents.

    Three types of events are provided to manage the stream:
        Stream    -- Triggered based on received stanzas, similar in concept
                     to events in a SAX XML parser.
        Custom    -- Triggered manually.
        Scheduled -- Triggered based on time delays.

    Typically, stanzas are first processed by a stream event handler which
    will then trigger custom events to continue further processing,
    especially since custom event handlers may run in individual threads.


    Attributes:
        address       -- The hostname and port of the server.
        default_ns    -- The default XML namespace that will be applied
                         to all non-namespaced stanzas.
        event_queue   -- A queue of stream, custom, and scheduled
                         events to be processed.
        filesocket    -- A filesocket created from the main connection socket.
                         Required for ElementTree.iterparse.
        namespace_map -- Optional mapping of namespaces to namespace prefixes.
        scheduler     -- A scheduler object for triggering events
                         after a given period of time.
        send_queue    -- A queue of stanzas to be sent on the stream.
        socket        -- The connection to the server.
        ssl_support   -- Indicates if a SSL library is available for use.
        ssl_version   -- The version of the SSL protocol to use.
                         Defaults to ssl.PROTOCOL_TLSv1.
        state         -- A state machine for managing the stream's
                         connection state.
        stream_footer -- The start tag and any attributes for the stream's
                         root element.
        stream_header -- The closing tag of the stream's root element.
        use_ssl       -- Flag indicating if SSL should be used.
        use_tls       -- Flag indicating if TLS should be used.
        stop          -- threading Event used to stop all threads.
        auto_reconnect-- Flag to determine whether we auto reconnect.

    Methods:
        add_event_handler    -- Add a handler for a custom event.
        add_handler          -- Shortcut method for registerHandler.
        connect              -- Connect to the given server.
        del_event_handler    -- Remove a handler for a custom event.
        disconnect           -- Disconnect from the server and terminate
                                processing.
        event                -- Trigger a custom event.
        get_id               -- Return the current stream ID.
        incoming_filter      -- Optionally filter stanzas before processing.
        new_id               -- Generate a new, unique ID value.
        process              -- Read XML stanzas from the stream and apply
                                matching stream handlers.
        reconnect            -- Reestablish a connection to the server.
        register_handler     -- Add a handler for a stream event.
        register_stanza      -- Add a new stanza object type that may appear
                                as a direct child of the stream's root.
        remove_handler       -- Remove a stream handler.
        remove_stanza        -- Remove a stanza object type.
        schedule             -- Schedule an event handler to execute after a
                                given delay.
        send                 -- Send a stanza object on the stream.
        send_raw             -- Send a raw string on the stream.
        send_xml             -- Send an XML string on the stream.
        set_socket           -- Set the stream's socket and generate a new
                                filesocket.
        start_stream_handler -- Perform any stream initialization such
                                as handshakes.
        start_tls            -- Establish a TLS connection and restart
                                the stream.
    """

    def __init__(self, socket=None, host='', port=0):
        """
        Establish a new XML stream.

        Arguments:
            socket -- Use an existing socket for the stream.
                      Defaults to None to generate a new socket.
            host   -- The name of the target server.
                      Defaults to the empty string.
            port   -- The port to use for the connection.
                      Defaults to 0.
        """
        # To comply with PEP8, method names now use underscores.
        # Deprecated method names are re-mapped for backwards compatibility.
        self.startTLS = self.start_tls
        self.registerStanza = self.register_stanza
        self.removeStanza = self.remove_stanza
        self.registerHandler = self.register_handler
        self.removeHandler = self.remove_handler
        self.setSocket = self.set_socket
        self.sendRaw = self.send_raw
        self.getId = self.get_id
        self.getNewId = self.new_id
        self.sendXML = self.send_xml

        self.ssl_support = SSL_SUPPORT
        self.ssl_version = ssl.PROTOCOL_TLSv1

        self.state = StateMachine(('disconnected', 'connected'))
        self.state._set_state('disconnected')

        self.address = (host, int(port))
        self.filesocket = None
        self.set_socket(socket)

        if sys.version_info < (3, 0):
            self.socket_class = Socket26
        else:
            self.socket_class = Socket.socket

        self.use_ssl = False
        self.use_tls = False

        self.default_ns = ''
        self.stream_header = "<stream>"
        self.stream_footer = "</stream>"

        self.stop = threading.Event()
        self.stream_end_event = threading.Event()
        self.stream_end_event.set()
        self.event_queue = queue.Queue()
        self.send_queue = queue.Queue()
        self.scheduler = Scheduler(self.event_queue, self.stop)

        self.namespace_map = {}

        self.__thread = {}
        self.__root_stanza = []
        self.__handlers = []
        self.__event_handlers = {}
        self.__event_handlers_lock = threading.Lock()

        self._id = 0
        self._id_lock = threading.Lock()

        self.auto_reconnect = True
        self.is_client = False

        try:
            if hasattr(signal, 'SIGHUP'):
                signal.signal(signal.SIGHUP, self._handle_kill)
            if hasattr(signal, 'SIGTERM'):
                # Used in Windows
                signal.signal(signal.SIGTERM, self._handle_kill)
        except:
            log.debug("Can not set interrupt signal handlers. " + \
                          "SleekXMPP is not running from a main thread.")

    def _handle_kill(self, signum, frame):
        """
        Capture kill event and disconnect cleanly after first
        spawning the "killed" event.
        """
        self.event("killed", direct=True)
        self.disconnect()

    def new_id(self):
        """
        Generate and return a new stream ID in hexadecimal form.

        Many stanzas, handlers, or matchers may require unique
        ID values. Using this method ensures that all new ID values
        are unique in this stream.
        """
        with self._id_lock:
            self._id += 1
            return self.get_id()

    def get_id(self):
        """
        Return the current unique stream ID in hexadecimal form.
        """
        return "%X" % self._id

    def connect(self, host='', port=0, use_ssl=False,
                use_tls=True, reattempt=True):
        """
        Create a new socket and connect to the server.

        Setting reattempt to True will cause connection attempts to be made
        every second until a successful connection is established.

        Arguments:
            host      -- The name of the desired server for the connection.
            port      -- Port to connect to on the server.
            use_ssl   -- Flag indicating if SSL should be used.
            use_tls   -- Flag indicating if TLS should be used.