class JID(object):
    """JID.

    :Ivariables:
        - `node`: node part of the JID
        - `domain`: domain part of the JID
        - `resource`: resource part of the JID

    JID objects are immutable. They are also cached for better performance.
    """
    cache=weakref.WeakValueDictionary()
    __slots__=["node","domain","resource","__weakref__"]
    def __new__(cls,node_or_jid=None,domain=None,resource=None,check=True):
        """Create a new JID object or take one from the cache.

        :Parameters:
            - `node_or_jid`: node part of the JID, JID object to copy or
              Unicode representation of the JID.
            - `domain`: domain part of the JID
            - `resource`: resource part of the JID
            - `check`: if `False` then JID is not checked for specifiaction
              compliance.
        """

        if isinstance(node_or_jid,JID):
            return node_or_jid

        if domain is None and resource is None:
            obj=cls.cache.get(node_or_jid)
            if obj:
                return obj
        else:
            obj=None
        if obj is None:
            obj=object.__new__(cls)

        if node_or_jid:
            node_or_jid = unicode(node_or_jid)
        if (node_or_jid and
                ((u"@" in node_or_jid) or (u"/" in node_or_jid))):
            obj.__from_unicode(node_or_jid)
            cls.cache[node_or_jid]=obj
        else:
            if domain is None and resource is None:
                if node_or_jid is None:
                    raise JIDError,"At least domain must be given"
                domain=node_or_jid
                node_or_jid=None
            if check:
                obj.__set_node(node_or_jid)
                obj.__set_domain(domain)
                obj.__set_resource(resource)
            else:
                object.__setattr__(obj,"node",node_or_jid)
                object.__setattr__(obj,"domain",domain)
                object.__setattr__(obj,"resource",resource)
        return obj

    def __setattr__(self,name,value):
        raise RuntimeError,"JID objects are immutable!"

    def __from_unicode(self,s,check=True):
        """Initialize JID object from Unicode string.

        :Parameters:
            - `s`: the JID string
            - `check`: when `False` then the JID is not checked for
              specification compliance."""
        s1=s.split(u"/",1)
        s2=s1[0].split(u"@",1)
        if len(s2)==2:
            if check:
                self.__set_node(s2[0])
                self.__set_domain(s2[1])
            else:
                object.__setattr__(self,"node",s2[0])
                object.__setattr__(self,"domain",s2[1])
        else:
            if check:
                self.__set_domain(s2[0])
            else:
                object.__setattr__(self,"domain",s2[0])
            object.__setattr__(self,"node",None)
        if len(s1)==2:
            if check:
                self.__set_resource(s1[1])
            else:
                object.__setattr__(self,"resource",s1[1])
        else:
            object.__setattr__(self,"resource",None)
        if not self.domain:
            raise JIDError,"Domain is required in JID."

    def __set_node(self,s):
        """Initialize `self.node`

        :Parameters:
            - `s`: Node part of the JID
        :Types:
            - `s`: unicode

        :raise JIDError: if the node name is too long.
        :raise pyxmpp.xmppstringprep.StringprepError: if the
            node name fails Nodeprep preparation."""
        if s:
            s = unicode(s)
            s=nodeprep.prepare(s)
            if len(s.encode("utf-8"))>1023:
                raise JIDError,"Node name too long"
        else:
            s=None
        object.__setattr__(self,"node",s)

    def __set_domain(self,s):
        """Initialize `self.domain`

        :Parameters:
            - `s`: Unicode or UTF-8 domain part of the JID

        :raise JIDError: if the domain name is too long."""

        if s is None:
            raise JIDError,"Domain must be given"
        if s:
            s = unicode(s)
        s=idna.nameprep(s)
        if len(s.encode("utf-8"))>1023:
            raise JIDError,"Domain name too long"
        object.__setattr__(self,"domain",s)

    def __set_resource(self,s):
        """Initialize `self.resource`

        :Parameters:
            - `s`: Unicode or UTF-8 resource part of the JID

        :raise JIDError: if the resource name is too long.
        :raise pyxmpp.xmppstringprep.StringprepError: if the
            node name fails Resourceprep preparation."""
        if s:
            s = unicode(s)
            s=resourceprep.prepare(s)
            if len(s.encode("utf-8"))>1023:
                raise JIDError,"Resource name too long"
        else:
            s=None
        object.__setattr__(self,"resource",s)

    def __str__(self):
        warnings.warn("JIDs should not be used as strings", DeprecationWarning, stacklevel=2)
        return self.as_utf8()

    def __unicode__(self):
        return self.as_unicode()

    def __repr__(self):
        return "<JID: %r>" % (self.as_unicode())

    def as_utf8(self):
        """UTF-8 encoded JID representation.

        :return: UTF-8 encoded JID."""
        return self.as_unicode().encode("utf-8")

    def as_string(self):
        """UTF-8 encoded JID representation.

        *Deprecated* Always use Unicode objects, or `as_utf8` if you really want.

        :return: UTF-8 encoded JID."""
        warnings.warn("JID.as_string() is deprecated. Use unicode() or `as_utf8` instead.",
                 DeprecationWarning, stacklevel=1)
        return self.as_utf8()

    def as_unicode(self):
        """Unicode string JID representation.

        :return: JID as Unicode string."""
        r=self.domain
        if self.node:
            r=self.node+u'@'+r
        if self.resource:
            r=r+u'/'+self.resource
        if not JID.cache.has_key(r):
            JID.cache[r]=self
        return r

    def bare(self):
        """Make bare JID made by removing resource from current `self`.

        :return: new JID object without resource part."""
        return JID(self.node,self.domain,check=False)

    def __eq__(self,other):
        if other is None:
            return False
        elif type(other) in (str, unicode):
            try:
                other=JID(other)
            except:
                return False
        elif not isinstance(other,JID):
            return False

        return (self.node==other.node
            and are_domains_equal(self.domain,other.domain)
            and self.resource==other.resource)

    def __ne__(self,other):
        return not self.__eq__(other)

    def __cmp__(self,other):
        a=self.as_unicode()
        return cmp(a,other)

    def __hash__(self):
        return hash(self.node)^hash(self.domain)^hash(self.resource) def to_utf8(s):
    """
    Convevert `s` to UTF-8 if it is Unicode, leave unchanged
    if it is string or None and convert to string overwise
    """
    if s is None:
        return None
    elif type(s) is unicode:
        return s.encode("utf-8")
    elif type(s) is str:
        return s
    else:
        return unicode(s).encode("utf-8") def from_utf8(s):
    """
    Convert `s` to Unicode or leave unchanged if it is None.

    Regular strings are assumed to be UTF-8 encoded
    """
    if s is None:
        return None
    elif type(s) is unicode:
        return s
    elif type(s) is str:
        return unicode(s,"utf-8")
    else:
        return unicode(s) def get_node_ns_uri(xmlnode):
    """Return namespace URI of an XML node.

    :Parameters:
        - `xmlnode`: the XML node to query.
    :Types:
        - `xmlnode`: `libxml2.xmlNode`

    :return: namespace URI of the node or `None`
    :returntype: `unicode`"""
    ns=get_node_ns(xmlnode)
    if ns:
        return unicode(ns.getContent(),"utf-8")
    else:
        return None def datetime_utc_to_local(utc):
    """
    An ugly hack to convert naive `datetime.datetime` object containing
    UTC time to a naive `datetime.datetime` object with local time.
    It seems standard Python 2.3 library doesn't provide any better way to
    do that.
    """
    ts=time.time()
    cur=datetime.datetime.fromtimestamp(ts)
    cur_utc=datetime.datetime.utcfromtimestamp(ts)

    offset=cur-cur_utc
    t=utc

    d=datetime.timedelta(hours=2)
    while d>minute:
        local=t+offset
        tm=local.timetuple()
        tm=tm[0:8]+(0,)
        ts=time.mktime(tm)
        u=datetime.datetime.utcfromtimestamp(ts)
        diff=u-utc
        if diff<minute and diff>-minute:
            break
        if diff>nulldelta:
            offset-=d
        else:
            offset+=d
        d/=2
    return local def datetime_local_to_utc(local):
    """
    Simple function to convert naive `datetime.datetime` object containing
    local time to a naive `datetime.datetime` object with UTC time.
    """
    ts=time.mktime(local.timetuple())
    return datetime.datetime.utcfromtimestamp(ts) class StanzaPayloadObject(object):
    """Base class for objects that may be used as XMPP stanza payload and don't keep
    internal XML representation, only parsed values.

    Provides `as_xml` method. Derived classes must override `xml_element_name` and
    `xml_element_namespace` class attributes and the `complete_xml_element` method.

    Please note that not all classes derived from `StanzaPayloadObject` should be
    used directly as stanza payload. Some of them are parts of higher level objects.

    :Cvariables:
        - `xml_element_name`: name for the XML element provided by the class.
        - `xml_element_namespace`: namespace URI for the XML element provided
          by the class.
    :Types:
        - `xml_element_name`: `unicode`
        - `xml_element_namespace`: `unicode`
    """
    xml_element_name = None
    xml_element_namespace = None

    def as_xml(self, parent = None, doc = None):
        """Get the XML representation of `self`.

        New document will be created if no `parent` and no `doc` is given.

        :Parameters:
            - `parent`: the parent for the XML element.
            - `doc`: the document where the element should be created. If not
              given and `parent` is provided then autodetection is attempted.
              If that fails, then `common_doc` is used.
        :Types:
            - `parent`: `libxml2.xmlNode`
            - `doc`: `libxml2.xmlDoc`
        :return: the new XML element or document created.
        :returntype: `libxml2.xmlNode` or `libxml2.xmlDoc`"""
        if parent:
            if not doc:
                n = parent
                while n:
                    if n.type == "xml_document":
                        doc = n
                        break
                    n = n.parent
                if not doc:
                    doc = common_doc
            try:
                ns = parent.searchNsByHref(doc, self.xml_element_namespace)
            except libxml2.treeError:
                ns = None
            xmlnode = parent.newChild(ns,self.xml_element_name,None)
            if not ns:
                ns = xmlnode.newNs(self.xml_element_namespace,None)
                xmlnode.setNs(ns)
            doc1 = doc
        else:
            if doc:
                doc1 = doc
            else:
                doc1 = libxml2.newDoc("1.0")
            xmlnode = doc1.newChild(None,self.xml_element_name, None)
            ns = xmlnode.newNs(self.xml_element_namespace, None)
            xmlnode.setNs(ns)

        self.complete_xml_element(xmlnode, doc1)

        if doc or parent:
            return xmlnode
        doc1.setRootElement(xmlnode)
        return doc1

    def complete_xml_element(self, xmlnode, doc):
        """Complete the XML node with `self` content.

        Should be overriden in classes derived from `StanzaPayloadObject`.

        :Parameters:
            - `xmlnode`: XML node with the element being built. It has already
              right name and namespace, but no attributes or content.
            - `doc`: document to which the element belongs.
        :Types:
            - `xmlnode`: `libxml2.xmlNode`
            - `doc`: `libxml2.xmlDoc`"""
        pass class BadRequestProtocolError(ProtocolError):
    """Raised when invalid stanza is processed and 'bad-request' error should be reported."""
    def __init__(self, message):
        ProtocolError.__init__(self, "bad-request", message) class JIDMalformedProtocolError(ProtocolError, JIDError):
    """Raised when invalid JID is encountered."""
    def __init__(self, message):
        ProtocolError.__init__(self, "jid-malformed", message) class JIDError(Error, ValueError):
    "Exception raised when invalid JID is used"
    pass
import libxml2
import time
import datetime
from pyxmpp.jid import JID
from pyxmpp.utils import to_utf8,from_utf8
from pyxmpp.xmlextra import get_node_ns_uri
from pyxmpp.utils import datetime_utc_to_local,datetime_local_to_utc
from pyxmpp.objects import StanzaPayloadObject
from pyxmpp.exceptions import BadRequestProtocolError, JIDMalformedProtocolError, JIDError
  - `XEP 0203 <http://xmpp.org/extensions/xep-0203.html>`__
"""

__docformat__="restructuredtext en"




DELAY_NS = "urn:xmpp:delay"
LEGACY_DELAY_NS = "jabber:x:delay"

def _parse_ts(timestamp):
    if "." in timestamp and timestamp.endswith("Z"):
        # strip miliseconds
        timestamp = timestamp.split(".", 1)[0] + "Z"
    for fmt in ("%Y-%m-%dT%H:%M:%SZ", "%Y%m%dT%H:%M:%S"):
        try:
            result = time.strptime(timestamp, fmt)
            return result
        except ValueError:
            continue
    raise BadRequestProtocolError, "Bad timestamp: " + repr(timestamp)

class Delay(StanzaPayloadObject):
    """
    Delayed delivery tag.

    Represents 'urn:xmpp:delay' (XEP-0203) element of a Jabber stanza.

    :Ivariables:
        - `delay_from`: the "from" value of the delay element
        - `reason`: the "reason" (content) of the delay element
        - `timestamp`: the UTC timestamp as naive datetime object
    """

    xml_element_name = "delay"
    xml_element_namespace = DELAY_NS

    _sort_order = 1
    _time_format = "%Y-%m-%dT%H:%M:%SZ"

    def __init__(self,node_or_datetime,delay_from=None,reason=None,utc=True):
        """
        Initialize the Delay object.

        :Parameters:
            - `node_or_datetime`: an XML node to parse or the timestamp.
            - `delay_from`: JID of the entity which adds the delay mark
              (when `node_or_datetime` is a timestamp).
            - `reason`: reason of the delay (when `node_or_datetime` is a
              timestamp).
            - `utc`: if `True` then the timestamp is assumed to be UTC,
              otherwise it is assumed to be local time.
        :Types:
            - `node_or_datetime`: `libxml2.xmlNode` or `datetime.datetime`
            - `delay_from`: `pyxmpp.JID`
            - `reason`: `unicode`
            - `utc`: `bool`"""
        if isinstance(node_or_datetime,libxml2.xmlNode):
            self.from_xml(node_or_datetime)
        else:
            if utc:
                self.timestamp=node_or_datetime
            else:
                self.timestamp=datetime_local_to_utc(node_or_datetime)
            self.delay_from=JID(delay_from)
            self.reason=unicode(reason)

    def from_xml(self,xmlnode):
        """Initialize Delay object from an XML node.

        :Parameters:
            - `xmlnode`: the jabber:x:delay XML element.
        :Types:
            - `xmlnode`: `libxml2.xmlNode`"""
        if xmlnode.type!="element":
            raise ValueError,"XML node is not a jabber:x:delay element (not an element)"
        ns=get_node_ns_uri(xmlnode)
        if ns and (ns != self.xml_element_namespace
                or xmlnode.name != self.xml_element_name):
            raise ValueError,"XML node is not a " + self.xml_element_namespace + " element"
        stamp=xmlnode.prop("stamp")
        tm = _parse_ts(stamp)
        tm=tm[0:8]+(0,)
        self.timestamp=datetime.datetime.fromtimestamp(time.mktime(tm))
        delay_from=from_utf8(xmlnode.prop("from"))
        if delay_from:
            try:
                self.delay_from = JID(delay_from)
            except JIDError:
                raise JIDMalformedProtocolError, "Bad JID in the " + self.xml_element_namespace + " 'from' attribute"
        else:
            self.delay_from = None
        self.reason = from_utf8(xmlnode.getContent())

    def complete_xml_element(self, xmlnode, _unused):
        """Complete the XML node with `self` content.

        Should be overriden in classes derived from `StanzaPayloadObject`.

        :Parameters:
            - `xmlnode`: XML node with the element being built. It has already
              right name and namespace, but no attributes or content.
            - `_unused`: document to which the element belongs.
        :Types:
            - `xmlnode`: `libxml2.xmlNode`
            - `_unused`: `libxml2.xmlDoc`"""
        tm=self.timestamp.strftime(self._time_format)
        xmlnode.setProp("stamp",tm)
        if self.delay_from:
            xmlnode.setProp("from",self.delay_from.as_utf8())
        if self.reason:
            xmlnode.setContent(to_utf8(self.reason))

    def get_datetime_local(self):
        """Get the timestamp as a local time.

        :return: the timestamp of the delay element represented in the local
          timezone.
        :returntype: `datetime.datetime`"""

        r=datetime_utc_to_local(self.timestamp)