"""Session object for building, serializing, sending, and receiving messages in
IPython. The Session object supports serialization, HMAC signatures, and
metadata on messages.

Also defined here are utilities for working with Sessions:
* A SessionFactory to be used as a base class for configurables that work with
Sessions.
* A Message object for convenience that allows attribute-access to the msg dict.
"""

# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.

import hashlib
import hmac
import logging
import os
import pprint
import random
import uuid
import warnings
from datetime import datetime

try:
    import cPickle
    pickle = cPickle
except:
    cPickle = None
    import pickle

try:
    # We are using compare_digest to limit the surface of timing attacks
    from hmac import compare_digest
except ImportError:
    # Python < 2.7.7: When digests don't match no feedback is provided,
    # limiting the surface of attack
    def compare_digest(a, b):
        return a == b

import zmq
from zmq.utils import jsonapi
from zmq.eventloop.ioloop import IOLoop
from zmq.eventloop.zmqstream import ZMQStream

from IPython.core.release import kernel_protocol_version
from IPython.config.configurable import Configurable, LoggingConfigurable
from IPython.utils import io
from IPython.utils.importstring import import_item
from IPython.utils.jsonutil import extract_dates, squash_dates, date_default
from IPython.utils.py3compat import (str_to_bytes, str_to_unicode, unicode_type,
                                     iteritems)
from IPython.utils.traitlets import (CBytes, Unicode, Bool, Any, Instance, Set,
                                     DottedObjectName, CUnicode, Dict, Integer,
                                     TraitError,
                                     )
from IPython.utils.pickleutil import PICKLE_PROTOCOL
from IPython.kernel.adapter import adapt
from IPython.kernel.zmq.serialize import MAX_ITEMS, MAX_BYTES

#-----------------------------------------------------------------------------
# utility functions
#-----------------------------------------------------------------------------


def squash_unicode(obj):
    """coerce unicode back to bytestrings."""
    if isinstance(obj, dict):
        for key in obj.keys():
            obj[key] = squash_unicode(obj[key])
            if isinstance(key, unicode_type):
                obj[squash_unicode(key)] = obj.pop(key)
    elif isinstance(obj, list):
        for i, v in enumerate(obj):
            obj[i] = squash_unicode(v)
    elif isinstance(obj, unicode_type):
        obj = obj.encode('utf8')
    return obj

#-----------------------------------------------------------------------------
# globals and defaults
#-----------------------------------------------------------------------------

# ISO8601-ify datetime objects
# allow unicode
# disallow nan, because it's not actually valid JSON
json_packer = lambda obj: jsonapi.dumps(obj, default=date_default,
                                        ensure_ascii=False, allow_nan=False,
                                        )
json_unpacker = lambda s: jsonapi.loads(s)

pickle_packer = lambda o: pickle.dumps(squash_dates(o), PICKLE_PROTOCOL)
pickle_unpacker = pickle.loads

default_packer = json_packer
default_unpacker = json_unpacker

DELIM = b"<IDS|MSG>"