##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################

import sys, time
from ZODB import TimeStamp

import struct
from struct import pack, unpack
from types import StringType
from binascii import hexlify

z64 = '\0'*8
t32 = 1L << 32

if sys.hexversion >= 0x02020000:

    # Note that the distinction between ints and longs is blurred in
    # Python 2.2.  So make u64() and U64() the same.

    def p64(v):
        """Pack an integer or long into a 8-byte string"""
        return pack(">Q", v)

    def u64(v):
        """Unpack an 8-byte string into a 64-bit long integer."""
        return unpack(">Q", v)[0]

    U64 = u64

else:

    def p64(v):
        """Pack an integer or long into a 8-byte string"""
        if v < t32:
            h = 0
        else:
            h, v = divmod(v, t32)
        return pack(">II", h, v)

    def u64(v):
        """Unpack an 8-byte string into a 64-bit (or long) integer."""
        h, v = unpack(">ii", v)
        if v < 0:
            v = t32 + v
        if h:
            if h < 0:
                h= t32 + h
            v = (long(h) << 32) + v
        return v

    def U64(v):
        """Same as u64 but always returns a long."""
        h, v = unpack(">II", v)
        if h:
            v = (long(h) << 32) + v
        return v

def cp(f1, f2, l):
    read = f1.read
    write = f2.write
    n = 8192

    while l > 0:
        if n > l:
            n = l
        d = read(n)
        if not d:
            break
        write(d)
        l = l - len(d)


def newTimeStamp(old=None,
                 TimeStamp=TimeStamp.TimeStamp,
                 time=time.time, gmtime=time.gmtime):
    t = time()
    ts = TimeStamp(gmtime(t)[:5]+(t%60,))
    if old is not None:
        return ts.laterThan(old)
    return ts

def oid_repr(oid):
    if isinstance(oid, StringType) and len(oid) == 8:
        # Convert to hex and strip leading zeroes.
        as_hex = hexlify(oid).lstrip('0')
        # Ensure two characters per input byte.
        if len(as_hex) & 1:
            as_hex = '0' + as_hex
        elif as_hex == '':
            as_hex = '00'
        return '0x' + as_hex
    else:
        return repr(oid)

serial_repr = oid_repr
tid_repr = serial_repr

# For example, produce
#     '0x03441422948b4399 2002-04-14 20:50:34.815000'
# for 8-byte string tid '\x03D\x14"\x94\x8bC\x99'.
def readable_tid_repr(tid):
    result = tid_repr(tid)
    if isinstance(tid, StringType) and len(tid) == 8:
        result = "%s %s" % (result, TimeStamp.TimeStamp(tid))
    return result

# Addresses can "look negative" on some boxes, some of the time.  If you
# feed a "negative address" to an %x format, Python 2.3 displays it as
# unsigned, but produces a FutureWarning, because Python 2.4 will display
# it as signed.  So when you want to prodce an address, use positive_id() to
# obtain it.
# _ADDRESS_MASK is 2**(number_of_bits_in_a_native_pointer).  Adding this to
# a negative address gives a positive int with the same hex representation as
# the significant bits in the original.

_ADDRESS_MASK = 256 ** struct.calcsize('P')
def positive_id(obj):
    """Return id(obj) as a non-negative integer."""

    result = id(obj)
    if result < 0:
        result += _ADDRESS_MASK
        assert result > 0
    return result
