from math import ldexp
import struct
import sys


if sys.version_info.major < 3:
    from collections import Mapping  # noqa: F401
    from datetime import tzinfo, timedelta
    from binascii import unhexlify

    class timezone(tzinfo):
        def __init__(self, offset):
            self.offset = offset

        def utcoffset(self, dt):
            return self.offset

        def dst(self, dt):
            return timedelta(0)

        def tzname(self, dt):
            return 'UTC+00:00'

    def as_unicode(string):
        return string.decode('utf-8')

    def iteritems(self):
        return self.iteritems()

    def int2bytes(i):
        hexstr = '%x' % i
        n = len(hexstr)
        pad = ('', '0')[n & 1]
        return unhexlify(pad + hexstr)

    def recursive_repr(fillvalue='...'):
        # Back-ported from Python 3.2
        from thread import get_ident

        def decorating_function(user_function):
            repr_running = set()

            def wrapper(self):
                key = id(self), get_ident()
                if key in repr_running:
                    return fillvalue
                repr_running.add(key)
                try:
                    result = user_function(self)
                finally:
                    repr_running.discard(key)
                return result
            return wrapper
        return decorating_function

    byte_as_integer = ord
    timezone.utc = timezone(timedelta(0))
    range = xrange  # noqa: F821
    long = long  # noqa: F821
    unicode = unicode  # noqa: F821
else:
    from collections.abc import Mapping  # noqa: F401
    from datetime import timezone
    from reprlib import recursive_repr  # noqa: F401

    def byte_as_integer(bytestr):
        return bytestr[0]

    def as_unicode(string):
        return string

    def iteritems(self):
        return self.items()

    def int2bytes(i):
        bits = i.bit_length()
        return i.to_bytes((bits + 7) // 8, 'big')

    range = range
    long = int
    unicode = str


if sys.version_info.major >= 3 and sys.version_info.minor >= 6:  # pragma: no cover
    # Python 3.6 added 16 bit floating point to struct

    def pack_float16(value):
        pass

    def unpack_float16(payload):
        pass

else:
    def pack_float16(value):
        # Based on node-cbor by hildjj
        # which was based in turn on Carsten Borman's cn-cbor
        try:
            u32 = struct.pack('>f', value)
        except OverflowError:
            return False

        u = struct.unpack('>I', u32)[0]
        if u & 0x1FFF != 0:
            return False

        s16 = (u >> 16) & 0x8000
        exponent = (u >> 23) & 0xff
        mantissa = u & 0x7fffff

        if 113 <= exponent <= 142:
            s16 += ((exponent - 112) << 10) + (mantissa >> 13)
        elif 103 <= exponent < 113:
            if mantissa & ((1 << (126 - exponent)) - 1):
                return False

            s16 += ((mantissa + 0x800000) >> (126 - exponent))
        else:
            return False

        return struct.pack('>BH', 0xf9, s16)

    def unpack_float16(payload):
        # Code adapted from RFC 7049, appendix D
        def decode_single(single):
            return struct.unpack("!f", struct.pack("!I", single))[0]

        payload = struct.unpack('>H', payload)[0]
        value = (payload & 0x7fff) << 13 | (payload & 0x8000) << 16
        if payload & 0x7c00 != 0x7c00:
            return ldexp(decode_single(value), 112)

        return decode_single(value | 0x7f800000)
