from bgenOutput import *
from bgenType import *
from bgenVariable import *


Error = "bgenGenerator.Error"

DEBUG=0

# Strings to specify argument transfer modes in generator calls
IN = "in"
OUT = "out"
INOUT = IN_OUT = "in-out"


class BaseFunctionGenerator:

    def __init__(self, name, condition=None, callname=None, modifiers=None):
        if DEBUG: print "<--", name
        self.name = name
        if callname:
            self.callname = callname
        else:
            self.callname = name
        self.prefix = name
        self.objecttype = "PyObject" # Type of _self argument to function
        self.condition = condition
        self.modifiers = modifiers

    def setprefix(self, prefix):
        self.prefix = prefix

    def checkgenerate(self):
        return True

    def generate(self):
        if not self.checkgenerate():
            return
        if DEBUG: print "-->", self.name
        if self.condition:
            Output()
            Output(self.condition)
        self.functionheader()
        self.functionbody()
        self.functiontrailer()
        if self.condition:
            Output("#endif")

    def functionheader(self):
        Output()
        Output("static PyObject *%s_%s(%s *_self, PyObject *_args)",
               self.prefix, self.name, self.objecttype)
        OutLbrace()
        Output("PyObject *_res = NULL;")

    def functionbody(self):
        Output("/* XXX To be provided */")

    def functiontrailer(self):
        OutRbrace()

    def reference(self, name = None):
        if not self.checkgenerate():
            return
        if name is None:
            name = self.name
        docstring = self.docstring()
        if self.condition:
            Output()
            Output(self.condition)
        Output("{\"%s\", (PyCFunction)%s_%s, 1,", name, self.prefix, self.name)
        Output(" PyDoc_STR(%s)},", stringify(docstring))
        if self.condition:
            Output("#endif")

    def docstring(self):
        return None

    def __cmp__(self, other):
        if not hasattr(other, 'name'):
            return cmp(id(self), id(other))
        return cmp(self.name, other.name)

_stringify_map = {'\n': '\\n', '\t': '\\t', '\r': '\\r', '\b': '\\b',
                  '\e': '\\e', '\a': '\\a', '\f': '\\f', '"': '\\"'}
def stringify(str):
    if str is None: return "NULL"
    res = '"'
    map = _stringify_map
    for c in str:
        if map.has_key(c): res = res + map[c]
        elif ' ' <= c <= '~': res = res + c
        else: res = res + '\\%03o' % ord(c)
    res = res + '"'
    return res


class ManualGenerator(BaseFunctionGenerator):

    def __init__(self, name, body, condition=None):
        BaseFunctionGenerator.__init__(self, name, condition=condition)
        self.body = body

    def functionbody(self):
        Output("%s", self.body)

    def setselftype(self, selftype, itselftype):
        self.objecttype = selftype
        self.itselftype = itselftype


class FunctionGenerator(BaseFunctionGenerator):

    def __init__(self, returntype, name, *argumentList, **conditionlist):
        BaseFunctionGenerator.__init__(self, name, **conditionlist)
        self.returntype = returntype
        self.argumentList = []
        self.setreturnvar()
        self.parseArgumentList(argumentList)
        self.prefix     = "XXX"    # Will be changed by setprefix() call
        self.itselftype = None     # Type of _self->ob_itself, if defined

    def setreturnvar(self):
        if self.returntype:
            self.rv = self.makereturnvar()
            self.argumentList.append(self.rv)
        else:
            self.rv = None

    def makereturnvar(self):
        return Variable(self.returntype, "_rv", OutMode)

    def setselftype(self, selftype, itselftype):
        self.objecttype = selftype
        self.itselftype = itselftype

    def parseArgumentList(self, argumentList):
        iarg = 0
        for type, name, mode in argumentList:
            iarg = iarg + 1
            if name is None: name = "_arg%d" % iarg
            arg = Variable(type, name, mode)
            self.argumentList.append(arg)

    def docstring(self):
        input = []
        output = []
        for arg in self.argumentList:
            if arg.flags == ErrorMode or arg.flags == SelfMode:
                continue
            if arg.type is None:
                str = 'void'
            else:
                if hasattr(arg.type, 'typeName'):
                    typeName = arg.type.typeName
                    if typeName is None: # Suppressed type
                        continue
                else:
                    typeName = "?"
                    print "Nameless type", arg.type

                str = typeName + ' ' + arg.name
            if arg.mode in (InMode, InOutMode):
                input.append(str)
            if arg.mode in (InOutMode, OutMode):
                output.append(str)
        if not input:
            instr = "()"
        else:
            instr = "(%s)" % ", ".join(input)
        if not output or output == ["void"]:
            outstr = "None"
        else:
            outstr = "(%s)" % ", ".join(output)
        return instr + " -> " + outstr

    def functionbody(self):
        self.declarations()
        self.precheck()
        self.getargs()
        self.callit()
        self.checkit()
        self.returnvalue()

    def declarations(self):
        for arg in self.argumentList:
            arg.declare()

    def getargs(self):
        sep = ",\n" + ' '*len("if (!PyArg_ParseTuple(")
        fmt, lst = self.getargsFormatArgs(sep)
        Output("if (!PyArg_ParseTuple(_args, \"%s\"%s))", fmt, lst)
        IndentLevel()
        Output("return NULL;")
        DedentLevel()
        for arg in self.argumentList:
            if arg.flags == SelfMode:
                continue
            if arg.mode in (InMode, InOutMode):
                arg.getargsCheck()

    def getargsFormatArgs(self, sep):
        fmt = ""
        lst = ""
        for arg in self.argumentList:
            if arg.flags == SelfMode:
                continue
            if arg.mode in (InMode, InOutMode):
                arg.getargsPreCheck()
                fmt = fmt + arg.getargsFormat()
                args = arg.getargsArgs()
                if args:
                    lst = lst + sep + args
        return fmt, lst

    def precheck(self):
        pass

    def beginallowthreads(self):
        pass

    def endallowthreads(self):
        pass

    def callit(self):
        args = ""
        s = "%s%s(" % (self.getrvforcallit(), self.callname)
        sep = ",\n" + ' '*len(s)
        for arg in self.argumentList:
            if arg is self.rv:
                continue
            s = arg.passArgument()
            if args: s = sep + s
            args = args + s
        self.beginallowthreads()
        Output("%s%s(%s);",
               self.getrvforcallit(), self.callname, args)
        self.endallowthreads()

    def getrvforcallit(self):
        if self.rv:
            return "%s = " % self.rv.name
        else:
            return ""

    def checkit(self):
        for arg in self.argumentList:
            arg.errorCheck()

    def returnvalue(self):
        sep = ",\n" + ' '*len("return Py_BuildValue(")
        fmt, lst = self.mkvalueFormatArgs(sep)
        if fmt == "":
            Output("Py_INCREF(Py_None);")
            Output("_res = Py_None;");
        else:
            Output("_res = Py_BuildValue(\"%s\"%s);", fmt, lst)
        tmp = self.argumentList[:]
        tmp.reverse()
        for arg in tmp:
            if not arg: continue
            arg.cleanup()
        Output("return _res;")

    def mkvalueFormatArgs(self, sep):
        fmt = ""
        lst = ""
        for arg in self.argumentList:
            if not arg: continue
            if arg.flags == ErrorMode: continue
            if arg.mode in (OutMode, InOutMode):
                arg.mkvaluePreCheck()
                fmt = fmt + arg.mkvalueFormat()
                lst = lst + sep + arg.mkvalueArgs()
        return fmt, lst

class MethodGenerator(FunctionGenerator):

    def parseArgumentList(self, args):
        a0, args = args[0], args[1:]
        t0, n0, m0 = a0
        if m0 != InMode:
            raise ValueError, "method's 'self' must be 'InMode'"
        self.itself = Variable(t0, "_self->ob_itself", SelfMode)
        self.argumentList.append(self.itself)
        FunctionGenerator.parseArgumentList(self, args)

def _test():
    void = None
    eggs = FunctionGenerator(void, "eggs",
                 (stringptr, 'cmd', InMode),
                 (int, 'x', InMode),
                 (double, 'y', InOutMode),
                 (int, 'status', ErrorMode),
                 )
    eggs.setprefix("spam")
    print "/* START */"
    eggs.generate()


if __name__ == "__main__":
    _test()
