# encoding: utf-8
# -*- test-case-name: IPython.kernel.test.test_newserialized -*-

"""Refactored serialization classes and interfaces."""

__docformat__ = "restructuredtext en"

# Tell nose to skip this module
__test__ = {}

#-------------------------------------------------------------------------------
#  Copyright (C) 2008  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 cPickle as pickle

from twisted.python import components
from zope.interface import Interface, implements

try:
    import numpy
except ImportError:
    pass

from IPython.kernel.error import SerializationError

#-----------------------------------------------------------------------------
# Classes and functions
#-----------------------------------------------------------------------------

class ISerialized(Interface):
    
    def getData():
        """"""

    def getDataSize(units=10.0**6):
        """"""

    def getTypeDescriptor():
        """"""

    def getMetadata():
        """"""
        
        
class IUnSerialized(Interface):
    
    def getObject():
        """"""
        
class Serialized(object):
    
    implements(ISerialized)
    
    def __init__(self, data, typeDescriptor, metadata={}):
        self.data = data
        self.typeDescriptor = typeDescriptor
        self.metadata = metadata
        
    def getData(self):
        return self.data
        
    def getDataSize(self, units=10.0**6):
        return len(self.data)/units
        
    def getTypeDescriptor(self):
        return self.typeDescriptor
        
    def getMetadata(self):
        return self.metadata

        
class UnSerialized(object):
    
    implements(IUnSerialized)
        
    def __init__(self, obj):
        self.obj = obj
        
    def getObject(self):
        return self.obj

        
class SerializeIt(object):
    
    implements(ISerialized)
    
    def __init__(self, unSerialized):
        self.data = None
        self.obj = unSerialized.getObject()
        if globals().has_key('numpy'):
            if isinstance(self.obj, numpy.ndarray):
                if len(self.obj) == 0:         # length 0 arrays can't be reconstructed
                    raise SerializationError("You cannot send a length 0 array")
                self.obj = numpy.ascontiguousarray(self.obj, dtype=None)
                self.typeDescriptor = 'ndarray'
                self.metadata = {'shape':self.obj.shape,
                                 'dtype':self.obj.dtype.str}
            else:
                self.typeDescriptor = 'pickle'
                self.metadata = {}
        else:
            self.typeDescriptor = 'pickle'
            self.metadata = {}
        self._generateData()            
    
    def _generateData(self):
        if self.typeDescriptor == 'ndarray':
            self.data = numpy.getbuffer(self.obj)
        elif self.typeDescriptor == 'pickle':
            self.data = pickle.dumps(self.obj, 2)
        else:
            raise SerializationError("Really wierd serialization error.")
        del self.obj
        
    def getData(self):
        return self.data
        
    def getDataSize(self, units=10.0**6):
        return len(self.data)/units
        
    def getTypeDescriptor(self):
        return self.typeDescriptor
        
    def getMetadata(self):
        return self.metadata


class UnSerializeIt(UnSerialized):
    
    implements(IUnSerialized)
    
    def __init__(self, serialized):
        self.serialized = serialized
        
    def getObject(self):
        typeDescriptor = self.serialized.getTypeDescriptor()
        if globals().has_key('numpy'):
            if typeDescriptor == 'ndarray':
                result = numpy.frombuffer(self.serialized.getData(), dtype = self.serialized.metadata['dtype'])
                result.shape = self.serialized.metadata['shape']
                # This is a hack to make the array writable.  We are working with
                # the numpy folks to address this issue.
                result = result.copy()
            elif typeDescriptor == 'pickle':
                result = pickle.loads(self.serialized.getData())
            else:
                raise SerializationError("Really wierd serialization error.")
        elif typeDescriptor == 'pickle':
            result = pickle.loads(self.serialized.getData())
        else:
            raise SerializationError("Really wierd serialization error.")
        return result

components.registerAdapter(UnSerializeIt, ISerialized, IUnSerialized)

components.registerAdapter(SerializeIt, IUnSerialized, ISerialized)
    
def serialize(obj):
    return ISerialized(UnSerialized(obj))
    
def unserialize(serialized):
    return IUnSerialized(serialized).getObject()
