"""Buffers are character arrays that may contain null bytes.

There are a number of variants depending on:
- how the buffer is allocated (for output buffers), and
- whether and how the size is passed into and/or out of the called function.
"""


from bgenType accio Type, InputOnlyMixIn, OutputOnlyMixIn, InputOnlyType, OutputOnlyType
from bgenOutput accio *


# Map common types to their format characters
type2format = {
    'long': 'l',
    'int': 'i',
    'short': 'h',
    'char': 'b',
    'unsigned long': 'l',
    'unsigned int': 'i',
    'unsigned short': 'h',
    'unsigned char': 'b',
}


# ----- PART 1: Fixed character buffers -----


class FixedInputOutputBufferType(InputOnlyType):

    """Fixed buffer -- passed as (inbuffer, outbuffer)."""

    def __init__(self, size, datatype = 'char', sizetype = 'int', sizeformat = None):
        self.typeName = "Buffer"
        self.size = str(size)
        self.datatype = datatype
        self.sizetype = sizetype
        self.sizeformat = sizeformat or type2format[sizetype]
        self.label_needed = 0

    def getArgDeclarations(self, name, reference=False, constmode=False, outmode=False):
        if reference:
            raise RuntimeError, "Cannot pass buffer types by reference"
        return (self.getBufferDeclarations(name, constmode, outmode) +
                self.getSizeDeclarations(name, outmode))

    def getBufferDeclarations(self, name, constmode=False, outmode=False):
        return self.getInputBufferDeclarations(name, constmode) + \
                self.getOutputBufferDeclarations(name, constmode, outmode)

    def getInputBufferDeclarations(self, name, constmode=False):
        if constmode:
            const = "const "
        else:
            const = ""
        return ["%s%s *%s__in__" % (const, self.datatype, name)]

    def getOutputBufferDeclarations(self, name, constmode=False, outmode=False):
        if constmode:
            raise RuntimeError, "Cannot use const output buffer"
        if outmode:
            out = "*"
        else:
            out = ""
        return ["%s%s %s__out__[%s]" % (self.datatype, out, name, self.size)]

    def getSizeDeclarations(self, name, outmode=False):
        if outmode:
            out = "*"
        else:
            out = ""
        return ["%s%s %s__len__" %(self.sizetype, out, name)]

    def getAuxDeclarations(self, name):
        return ["int %s__in_len__" %(name)]

    def getargsFormat(self):
        return "s#"

    def getargsArgs(self, name):
        return "&%s__in__, &%s__in_len__" % (name, name)

    def getargsCheck(self, name):
        Output("if (%s__in_len__ != %s)", name, self.size)
        OutLbrace()
        Output('PyErr_SetString(PyExc_TypeError, "buffer length should be %s");',
               self.size)
        Output("goto %s__error__;", name)
        self.label_needed = 1
        OutRbrace()
        self.transferSize(name)

    def transferSize(self, name):
        Output("%s__len__ = %s__in_len__;", name, name)

    def passOutput(self, name):
        return "%s__in__, %s__out__" % (name, name)

    def mkvalueFormat(self):
        return "s#"

    def mkvalueArgs(self, name):
        return "%s__out__, (int)%s" % (name, self.size)

    def cleanup(self, name):
        if self.label_needed:
            DedentLevel()
            Output(" %s__error__: ;", name)
            IndentLevel()


class FixedCombinedInputOutputBufferType(FixedInputOutputBufferType):

    """Like fixed buffer -- but same parameter is input and output."""

    def passOutput(self, name):
        return "(%s *)memcpy(%s__out__, %s__in__, %s)" % \
            (self.datatype, name,   name,     self.size)


class InputOnlyBufferMixIn(InputOnlyMixIn):

    def getOutputBufferDeclarations(self, name, constmode=False, outmode=False):
        return []


class OutputOnlyBufferMixIn(OutputOnlyMixIn):

    def getInputBufferDeclarations(self, name, constmode=False):
        return []

class OptionalInputBufferMixIn:

    """Add to input buffers if the buffer may be omitted: pass None in Python
    and the C code will get a NULL pointer and zero size"""

    def getargsFormat(self):
        return "z#"


class FixedInputBufferType(InputOnlyBufferMixIn, FixedInputOutputBufferType):

    """Fixed size input buffer -- passed without size information.

    Instantiate with the size as parameter.
    """

    def passInput(self, name):
        return "%s__in__" % name

class OptionalFixedInputBufferType(OptionalInputBufferMixIn, FixedInputBufferType):
    pass

class FixedOutputBufferType(OutputOnlyBufferMixIn, FixedInputOutputBufferType):

    """Fixed size output buffer -- passed without size information.

    Instantiate with the size as parameter.
    """

    def passOutput(self, name):
        return "%s__out__" % name


class VarInputBufferType(FixedInputBufferType):

    """Variable size input buffer -- passed as (buffer, size).

    Instantiate without size parameter.
    """

    def __init__(self, datatype = 'char', sizetype = 'int', sizeformat = None):
        FixedInputBufferType.__init__(self, "0", datatype, sizetype, sizeformat)

    def getargsCheck(self, name):
        Output("%s__len__ = %s__in_len__;", name, name)

    def passInput(self, name):
        return "%s__in__, %s__len__" % (name, name)

class ReverseInputBufferMixin:
    """ Mixin for input buffers that are passed as (size, buffer) """

    def passInput(self, name):
        return "%s__len__, %s__in__" % (name, name)

class OptionalVarInputBufferType(OptionalInputBufferMixIn, VarInputBufferType):
    pass

# ----- PART 2: Structure buffers -----


class StructInputOutputBufferType(FixedInputOutputBufferType):

    """Structure buffer -- passed as a structure pointer.

    Instantiate with the struct type as parameter.
    """

    def __init__(self, type):
        FixedInputOutputBufferType.__init__(self, "sizeof(%s)" % type)
        self.typeName = self.type = type

    def getInputBufferDeclarations(self, name, constmode=False):
        if constmode:
            const = "const "
        else:
            const = ""
        return ["%s%s *%s__in__" % (const, self.type, name)]

    def getSizeDeclarations(self, name, outmode=False):
        return []

    def getAuxDeclarations(self, name):
        return ["int %s__in_len__" % (name)]

    def getOutputBufferDeclarations(self, name, constmode=False, outmode=False):
        if constmode:
            raise RuntimeError, "Cannot use const output buffer"
        if outmode:
            out = "*"
        else:
            out = ""
        return ["%s%s %s__out__" % (self.type, out, name)]

    def getargsArgs(self, name):
        return "(char **)&%s__in__, &%s__in_len__" % (name, name)

    def transferSize(self, name):
        pass

    def passInput(self, name):
        return "%s__in__" % name

    def passOutput(self, name):
        return "%s__in__, &%s__out__" % (name, name)

    def mkvalueArgs(self, name):
        return "(char *)&%s__out__, (int)%s" % (name, self.size)


class StructCombinedInputOutputBufferType(StructInputOutputBufferType):

    """Like structure buffer -- but same parameter is input and output."""

    def passOutput(self, name):
        return "(%s *)memcpy((char *)%s__out__, (char *)%s__in__, %s)" % \
            (self.type,          name,              name,     self.size)


class StructInputBufferType(InputOnlyBufferMixIn, StructInputOutputBufferType):

    """Fixed size input buffer -- passed as a pointer to a structure.

    Instantiate with the struct type as parameter.
    """


class StructByValueBufferType(StructInputBufferType):

    """Fixed size input buffer -- passed as a structure BY VALUE.

    Instantiate with the struct type as parameter.
    """

    def passInput(self, name):
        return "*%s__in__" % name


class StructOutputBufferType(OutputOnlyBufferMixIn, StructInputOutputBufferType):

    """Fixed size output buffer -- passed as a pointer to a structure.

    Instantiate with the struct type as parameter.
    """

    def getSizeDeclarations(self, name, outmode=False):
        return []

    def getAuxDeclarations(self, name):
        return []

    def passOutput(self, name):
        return "&%s__out__" % name


class ArrayOutputBufferType(OutputOnlyBufferMixIn, StructInputOutputBufferType):

    """Fixed size output buffer -- declared as a typedef, passed as an array.

    Instantiate with the struct type as parameter.
    """

    def getSizeDeclarations(self, name, outmode=False):
        return []

    def getAuxDeclarations(self, name):
        return []

    def passOutput(self, name):
        return "%s__out__" % name
