"""test serialization tools"""

#-------------------------------------------------------------------------------
#  Copyright (C) 2011  The IPython Development Team
#
#  Distributed under the terms of the BSD License.  The full license is in
#  the file COPYING, distributed as part of this software.
#-------------------------------------------------------------------------------

#-------------------------------------------------------------------------------
# Imports
#-------------------------------------------------------------------------------

import pickle
from collections import namedtuple

import nose.tools as nt

# from unittest import TestCaes
from IPython.kernel.zmq.serialize import serialize_object, unserialize_object
from IPython.testing import decorators as dec
from IPython.utils.pickleutil import CannedArray, CannedClass
from IPython.parallel import interactive

#-------------------------------------------------------------------------------
# Globals and Utilities
#-------------------------------------------------------------------------------

def roundtrip(obj):
    """roundtrip an object through serialization"""
    bufs = serialize_object(obj)
    obj2, remainder = unserialize_object(bufs)
    nt.assert_equals(remainder, [])
    return obj2

class C(object):
    """dummy class for """
    
    def __init__(self, **kwargs):
        for key,value in kwargs.iteritems():
            setattr(self, key, value)

SHAPES = ((100,), (1024,10), (10,8,6,5), (), (0,))
DTYPES = ('uint8', 'float64', 'int32', [('g', 'float32')], '|S10')
#-------------------------------------------------------------------------------
# Tests
#-------------------------------------------------------------------------------

@dec.parametric
def test_roundtrip_simple():
    for obj in [
        'hello',
        dict(a='b', b=10),
        [1,2,'hi'],
        (b'123', 'hello'),
    ]:
        obj2 = roundtrip(obj)
        yield nt.assert_equals(obj, obj2)

@dec.parametric
def test_roundtrip_nested():
    for obj in [
        dict(a=range(5), b={1:b'hello'}),
        [range(5),[range(3),(1,[b'whoda'])]],
    ]:
        obj2 = roundtrip(obj)
        yield nt.assert_equals(obj, obj2)

@dec.parametric
def test_roundtrip_buffered():
    for obj in [
        dict(a=b"x"*1025),
        b"hello"*500,
        [b"hello"*501, 1,2,3]
    ]:
        bufs = serialize_object(obj)
        yield nt.assert_equals(len(bufs), 2)
        obj2, remainder = unserialize_object(bufs)
        yield nt.assert_equals(remainder, [])
        yield nt.assert_equals(obj, obj2)

def _scrub_nan(A):
    """scrub nans out of empty arrays
    
    since nan != nan
    """
    import numpy
    if A.dtype.fields and A.shape:
        for field in A.dtype.fields.keys():
            try:
                A[field][numpy.isnan(A[field])] = 0
            except (TypeError, NotImplementedError):
                # e.g. str dtype
                pass

@dec.parametric
@dec.skip_without('numpy')
def test_numpy():
    import numpy
    from numpy.testing.utils import assert_array_equal
    for shape in SHAPES:
        for dtype in DTYPES:
            A = numpy.empty(shape, dtype=dtype)
            _scrub_nan(A)
            bufs = serialize_object(A)
            B, r = unserialize_object(bufs)
            yield nt.assert_equals(r, [])
            yield nt.assert_equals(A.shape, B.shape)
            yield nt.assert_equals(A.dtype, B.dtype)
            yield assert_array_equal(A,B)

@dec.parametric
@dec.skip_without('numpy')
def test_recarray():
    import numpy
    from numpy.testing.utils import assert_array_equal
    for shape in SHAPES:
        for dtype in [
            [('f', float), ('s', '|S10')],
            [('n', int), ('s', '|S1'), ('u', 'uint32')],
        ]:
            A = numpy.empty(shape, dtype=dtype)
            _scrub_nan(A)
            
            bufs = serialize_object(A)
            B, r = unserialize_object(bufs)
            yield nt.assert_equals(r, [])
            yield nt.assert_equals(A.shape, B.shape)
            yield nt.assert_equals(A.dtype, B.dtype)
            yield assert_array_equal(A,B)

@dec.parametric
@dec.skip_without('numpy')
def test_numpy_in_seq():
    import numpy
    from numpy.testing.utils import assert_array_equal
    for shape in SHAPES:
        for dtype in DTYPES:
            A = numpy.empty(shape, dtype=dtype)
            _scrub_nan(A)
            bufs = serialize_object((A,1,2,b'hello'))
            canned = pickle.loads(bufs[0])
            yield nt.assert_true(canned[0], CannedArray)
            tup, r = unserialize_object(bufs)
            B = tup[0]
            yield nt.assert_equals(r, [])
            yield nt.assert_equals(A.shape, B.shape)
            yield nt.assert_equals(A.dtype, B.dtype)
            yield assert_array_equal(A,B)

@dec.parametric
@dec.skip_without('numpy')
def test_numpy_in_dict():
    import numpy
    from numpy.testing.utils import assert_array_equal
    for shape in SHAPES:
        for dtype in DTYPES:
            A = numpy.empty(shape, dtype=dtype)
            _scrub_nan(A)
            bufs = serialize_object(dict(a=A,b=1,c=range(20)))
            canned = pickle.loads(bufs[0])
            yield nt.assert_true(canned['a'], CannedArray)
            d, r = unserialize_object(bufs)
            B = d['a']
            yield nt.assert_equals(r, [])
            yield nt.assert_equals(A.shape, B.shape)
            yield nt.assert_equals(A.dtype, B.dtype)
            yield assert_array_equal(A,B)

@dec.parametric
def test_class():
    @interactive
    class C(object):
        a=5
    bufs = serialize_object(dict(C=C))
    canned = pickle.loads(bufs[0])
    yield nt.assert_true(canned['C'], CannedClass)
    d, r = unserialize_object(bufs)
    C2 = d['C']
    yield nt.assert_equal(C2.a, C.a)

@dec.parametric
def test_class_oldstyle():
    @interactive
    class C:
        a=5
    
    bufs = serialize_object(dict(C=C))
    canned = pickle.loads(bufs[0])
    yield nt.assert_true(isinstance(canned['C'], CannedClass))
    d, r = unserialize_object(bufs)
    C2 = d['C']
    yield nt.assert_equal(C2.a, C.a)

@dec.parametric
def test_tuple():
    tup = (lambda x:x, 1)
    bufs = serialize_object(tup)
    canned = pickle.loads(bufs[0])
    yield nt.assert_true(isinstance(canned, tuple))
    t2, r = unserialize_object(bufs)
    yield nt.assert_equal(t2[0](t2[1]), tup[0](tup[1]))

point = namedtuple('point', 'x y')

@dec.parametric
def test_namedtuple():
    p = point(1,2)
    bufs = serialize_object(p)
    canned = pickle.loads(bufs[0])
    yield nt.assert_true(isinstance(canned, point))
    p2, r = unserialize_object(bufs, globals())
    yield nt.assert_equal(p2.x, p.x)
    yield nt.assert_equal(p2.y, p.y)

@dec.parametric
def test_list():
    lis = [lambda x:x, 1]
    bufs = serialize_object(lis)
    canned = pickle.loads(bufs[0])
    yield nt.assert_true(isinstance(canned, list))
    l2, r = unserialize_object(bufs)
    yield nt.assert_equal(l2[0](l2[1]), lis[0](lis[1]))

@dec.parametric
def test_class_inheritance():
    @interactive
    class C(object):
        a=5

    @interactive
    class D(C):
        b=10
    
    bufs = serialize_object(dict(D=D))
    canned = pickle.loads(bufs[0])
    yield nt.assert_true(canned['D'], CannedClass)
    d, r = unserialize_object(bufs)
    D2 = d['D']
    yield nt.assert_equal(D2.a, D.a)
    yield nt.assert_equal(D2.b, D.b)
