# KInterbasDB Python Package - Request Buffer Builder
#
# Version 3.3
#
# The following contributors hold Copyright (C) over their respective
# portions of code (see license.txt for details):
#
# [Original Author (maintained through version 2.0-0.3.1):]
#   1998-2001 [alex]  Alexander Kuznetsov   <alexan@users.sourceforge.net>
# [Maintainers (after version 2.0-0.3.1):]
#   2001-2002 [maz]   Marek Isalski         <kinterbasdb@maz.nu>
#   2002-2006 [dsr]   David Rushby          <woodsplitter@rocketmail.com>
# [Contributors:]
#   2001      [eac]   Evgeny A. Cherkashin  <eugeneai@icc.ru>
#   2001-2002 [janez] Janez Jere            <janez.jere@void.si>

# This module is private.

import struct

# __init__.py will fill this with a function reference when it imports this
# module:
portable_int = None

class RequestBufferBuilder(object):
    def __init__(self, clusterIdentifier=None):
        self.clear()

        if clusterIdentifier:
            self._addCode(clusterIdentifier)

    def render(self):
        # Convert the RequestBufferBuilder's components to a binary Python str.
        return ''.join(self._buffer)

    def clear(self):
        self._buffer = []

    def _extend(self, otherRequestBuilder):
        self._buffer.append(otherRequestBuilder.render())

    def _addRaw(self, rawBuf):
        assert isinstance(rawBuf, str)
        self._buffer.append(rawBuf)

    def _addCode(self, code):
        _code2reqbuf(self._buffer, code)

    def _addString(self, code, s):
        _string2reqbuf(self._buffer, code, s)

    def _addNumeric(self, code, n, numCType='I'):
        _numeric2reqbuf(self._buffer, code, n, numCType=numCType)

def _vax_inverse(i, format):
    # Apply the inverse of isc_vax_integer to a Python integer; return
    # the raw bytes of the resulting value.
    iRaw = struct.pack(format, i)
    iConv = portable_int(iRaw)
    iConvRaw = struct.pack(format, iConv)
    return iConvRaw

def _renderSizedIntegerForSPB(i, format):
    #   In order to prepare the Python integer i for inclusion in a request
    # buffer, the byte sequence of i must be reversed, which will make i
    # unrepresentible as a normal Python integer.
    #   Therefore, the rendered version of i must be stored in a raw byte
    # buffer.
    #   This function returns a 2-tuple containing:
    # 1. the calculated struct.pack-compatible format string for i
    # 2. the Python string containing the SPB-compatible raw binary rendering
    #    of i
    #
    # Example:
    # To prepare the Python integer 12345 for storage as an unsigned int in a
    # request buffer, use code such as this:
    #   (iPackFormat, iRawBytes) = _renderSizedIntegerForSPB(12345, 'I')
    #   spbBytes = struct.pack(iPackFormat, iRawBytes)
    #
    destFormat = '%ds' % struct.calcsize(format)
    destVal = _vax_inverse(i,  format)
    return (destFormat, destVal)

def _string2reqbuf(reqBuf, code, s):
    sLen = len(s)

    _numeric2reqbuf(reqBuf, code, sLen, numCType='H')

    format = str(sLen) + 's' # The length, then 's'.
    reqBuf.append( struct.pack(format, s) )

def _numeric2reqbuf(reqBuf, code, num, numCType='I'):
    # numCType is one of the pack format characters specified by the Python
    # standard library module 'struct'.
    _code2reqbuf(reqBuf, code)

    (numericFormat, numericBytes) = _renderSizedIntegerForSPB(num, numCType)
    reqBuf.append( struct.pack(numericFormat, numericBytes) )

def _code2reqbuf(reqBuf, code):
    if isinstance(code, str):
        assert len(code) == 1
        code = ord(code)

    # The database engine considers little-endian integers "portable"; they
    # need to have been converted to little-endianness before being sent across
    # the network.
    reqBuf.append(struct.pack('<b', code))
