# -*- coding: UTF-8 -*-
import datetime
from decimal import Decimal
from collections import namedtuple
try:
    import simplejson as json
except ImportError:
    import json
import base64


class JSONDecoder(object):

    decoders = {}

    @classmethod
    def register(cls, klass, decoder):
        assert klass not in cls.decoders
        cls.decoders[klass] = decoder

    def __call__(self, dct):
        if dct.get('__class__') in self.decoders:
            return self.decoders[dct['__class__']](dct)
        return dct


JSONDecoder.register(
    'datetime',
    lambda dct: datetime.datetime(
        dct['year'], dct['month'], dct['day'],
        dct['hour'], dct['minute'], dct['second'], dct['microsecond']
    )
)
JSONDecoder.register(
    'date',
    lambda dct: datetime.date(dct['year'], dct['month'], dct['day'])
)
JSONDecoder.register(
    'time',
    lambda dct: datetime.time(
        dct['hour'], dct['minute'], dct['second'], dct['microsecond']
    )
)
JSONDecoder.register(
    'buffer', lambda dct:
    buffer(base64.decodestring(dct['base64']))
)
JSONDecoder.register(
    'Decimal', lambda dct: Decimal(dct['decimal'])
)


dummy_record = namedtuple('Record', ['model_name', 'id', 'rec_name'])
JSONDecoder.register(
    'Model', lambda dct: dummy_record(
        dct['model_name'], dct['id'], dct['rec_name']
    )
)


def parse_async_result(dct):
    from client import AsyncResult
    return AsyncResult(dct['task_id'], dct['token'], None)


JSONDecoder.register(
    'AsyncResult', parse_async_result
)


class JSONEncoder(json.JSONEncoder):

    serializers = {}

    def __init__(self, *args, **kwargs):
        super(JSONEncoder, self).__init__(*args, **kwargs)
        # Force to use our custom decimal with simplejson
        self.use_decimal = False

    @classmethod
    def register(cls, klass, encoder):
        assert klass not in cls.serializers
        cls.serializers[klass] = encoder

    def default(self, obj):
        marshaller = self.serializers.get(
            type(obj),
            super(JSONEncoder, self).default
        )
        return marshaller(obj)


JSONEncoder.register(
    datetime.datetime,
    lambda o: {
        '__class__': 'datetime',
        'year': o.year,
        'month': o.month,
        'day': o.day,
        'hour': o.hour,
        'minute': o.minute,
        'second': o.second,
        'microsecond': o.microsecond,
        'iso_string': o.isoformat(),
    })
JSONEncoder.register(
    datetime.date,
    lambda o: {
        '__class__': 'date',
        'year': o.year,
        'month': o.month,
        'day': o.day,
        'iso_string': o.isoformat(),
    })
JSONEncoder.register(
    datetime.time,
    lambda o: {
        '__class__': 'time',
        'hour': o.hour,
        'minute': o.minute,
        'second': o.second,
        'microsecond': o.microsecond,
    })
JSONEncoder.register(
    buffer,
    lambda o: {
        '__class__': 'buffer',
        'base64': base64.encodestring(o),
    })
JSONEncoder.register(
    Decimal,
    lambda o: {
        '__class__': 'Decimal',
        'decimal': str(o),
    })

@@ -959,32 +959,88 @@
 second'%5D%0A    )%0A)
+%0A%0A# Legacy support. Newer versions use bytes, not buffer
 %0AJSONDecoder.reg
@@ -1117,24 +1117,215 @@
 ase64'%5D))%0A)%0A
+%0A%0Adef _bytes_decoder(dct):%0A    cast = bytearray if bytes == str else bytes%0A    return cast(base64.decodestring(dct%5B'base64'%5D.encode('utf-8')))%0A%0A%0AJSONDecoder.register('bytes', _bytes_decoder)%0A
 JSONDecoder.
@@ -3212,32 +3212,239 @@
 ring(o),%0A    %7D)%0A
+_bytes_encoder = lambda o: %7B%0A    '__class__': 'bytes',%0A    'base64': base64.encodestring(o).decode('utf-8'),%0A    %7D%0AJSONEncoder.register(bytes, _bytes_encoder)%0AJSONEncoder.register(bytearray, _bytes_encoder)%0A
 JSONEncoder.regi

Use bytes and bytearray for binary data