import cStringIO, string

from Ft.Ods.PersistentObject import TupleDefinitions
from Ft.Ods.MetaData import MetaKind
from Ft.Ods.MetaData import PrimitiveKind, CollectionKind, Direction
from Ft.Ods import Constants
import GenerateType, OdlUtil
TD = TupleDefinitions

def GetBases(name, objects, params):
    bases = []
    for object in objects:
        names = string.split(object.absolute_name(), '::')
        if names[0] == 'ODLMetaObjects':
            del names[0]
        module = string.join(names, '.')
        bases.append('%s.%s_stub' % (module, names[-1]))
        GenerateType.WriteImports(object, params)
    return bases

def GlobalFactory(write, name):
    write('class %sFactory:\n\n' % name)
    write('    def new(self,db):\n')
    write('        return %s_stub(db,None)\n\n' % name)

    write('g_%sFactory = %sFactory()\n\n' % (name, name))

    write('def new(db):\n')
    write('    return g_%sFactory.new(db)\n\n' % name)
    return

def ClassHeader(write, name, bases,isInterface):
    write('class %s_stub' % name)
    if bases:
        write('(%s' % bases[0])
        for base in bases[1:]:
            write(', %s' % base)
        write(')')
    write(':\n\n')

    write('    def __init__(self, db,data):\n')
    for base in bases:
        write('        %s.__init__(self,db,data)\n' % base)
    if isInterface:
        write('        if self.__class__ == %s_stub:\n'%name)
        write('            from Ft.Ods.Exception import FtodsInterfaceCreationError\n')
        write('            raise FtodsInterfaceCreationError(name="%s")\n' % name)
            
    return

def AttributeInit(attributes, params):
    if attributes:
        params.outFile.write('        if data is None:\n')
        params.outFile.write('            pass\n')
        for attr in attributes:
            typ = OdlUtil.NormalizeTypeDefinition(attr.type)
            if typ.meta_kind not in [MetaKind.mk_class,
                                     MetaKind.mk_interface,
                                     MetaKind.mk_collection]:
                if (typ.meta_kind == MetaKind.mk_primitive_type and
                    typ.primitive_kind in [PrimitiveKind.pk_blob,
                                           PrimitiveKind.pk_date,
                                           PrimitiveKind.pk_time,
                                           PrimitiveKind.pk_interval,
                                           PrimitiveKind.pk_timestamp,
                                                 ]):
                    continue
                if typ.meta_kind in [MetaKind.mk_structure,
                                     MetaKind.mk_enumeration,
                                     MetaKind.mk_union]:
                    continue
                params.outFile.write('            self.__dict__["%s"] = ' % attr.name)
                GenerateType.WriteInit(typ, params)
            params.outFile.write('\n')
    params.outFile.write('\n')
    return

def RelationshipInit(relationship, params):
    write = params.outFile.write
    traversal = relationship.traversal
    absoluteName = string.split(traversal.definedIn.absolute_name(), '::')
    if absoluteName[0] == 'ODLMetaObjects':
        del absoluteName[0]
    absoluteName.append(absoluteName[-1])

    name = relationship.name

    target = OdlUtil.NormalizeTypeDefinition(relationship.type)

    nSided = traversal._4ods_getCardinality() == 'N'
    params.indent = '        '
    rtype = OdlUtil.NormalizeTypeDefinition(relationship.type)
    if relationship._4ods_getCardinality() == '1':
        write('    def form_%s(self, target, inverse=1):\n' % name)
        FormRelationship(write, name, target._4ods_getId(), traversal.name, nSided)
        write('    def drop_%s(self, target, inverse=1):\n' % name)
        DropRelationship(write, name, traversal.name, nSided)
    else:
        write('    def add_%s(self, target, inverse=1):\n' % name)
        GenerateType.WriteImports(rtype, params)
        AddRelationship(write, name, target._4ods_getId(), traversal.name, nSided)
        write('    def remove_%s(self, target, inverse=1):\n' % name)
        RemoveRelationship(write, name, traversal.name, nSided)
    params.indent = ''
    return    
    
def FormRelationship(write, name, targetId,inverseOf, nSided):
    write('        try:\n')
    write('            Constants.g_typeCheck[%s](target,%d)\n' % (Constants.Types.POBJECT,targetId))
    write('        except TypeError:\n')
    write('            from Ft.Ods.Exception import IntegrityError\n')
    write('            raise IntegrityError(relName="%s")\n' % name)
    write('        if getattr(self, "%s"):\n' % name)
    write('            from Ft.Ods.Exception import IntegrityError\n')
    write('            raise IntegrityError(relName="%s")\n' % name)
    write('        self.__dict__["%s"] = target\n' % name)
    write('        if inverse:\n')
    if nSided:
        write('            target.add_%s(self, 0)\n' % inverseOf)
    else:
        write('            target.form_%s(self, 0)\n' % inverseOf)
    write('        self.__dict__["_modifiedAttrs"]["%s"] = 1\n' % name)
    write('\n')
    return

def AddRelationship(write, name, targetId, inverseOf, nSided):
    write('        collection = getattr(self, "%s")\n' % name)
    write('        if not collection.allows_duplicates() and collection.contains_element(target):\n')
    write('            from Ft.Ods.Exception import IntegrityError\n')
    write('            raise IntegrityError(relName="%s")\n' % name)
    write('        try:\n')
    write('            collection.insert_element(target)\n')
    write('        except TypeError:\n')
    write('            from Ft.Ods.Exception import IntegrityError\n')
    write('            raise IntegrityError(relName="%s")\n' % name)
    write('        if inverse:\n')
    if nSided:
        write('            target.add_%s(self, 0)\n' % inverseOf)
    else:
        write('            target.form_%s(self, 0)\n' % inverseOf)
    write('\n    form_%s = add_%s\n\n' % (name, name))
    return        

def DropRelationship(write, name, inverseOf, nSided):
    write('        if self.__dict__.has_key("%s"):\n' % name)
    write('            if self.__dict__["%s"] != target:\n' % name)
    write('                from Ft.Ods.Exception import IntegrityError\n')
    write('                raise IntegrityError(relName="%s")\n' % name)
    write('        elif not self.__dict__["_objectIds"].has_key("%s") and self.__dict__["_objectIds"]["%s"] != target._4ods_getId():\n' % (name, name))
    write('            from Ft.Ods.Exception import IntegrityError\n')
    write('            raise IntegrityError(relName="%s")\n' % name)
    write('        if self.__dict__.has_key("%s"):\n' % name)
    write('            del self.__dict__["%s"]\n' % name)
    write('        if self.__dict__["_objectIds"].has_key("%s"):\n' % name)
    write('            del self.__dict__["_objectIds"]["%s"]\n' % name)
    write('        if inverse:\n')
    if nSided:
        write('            target.remove_%s(self, 0)\n' % inverseOf)
    else:
        write('            target.drop_%s(self, 0)\n' % inverseOf)
    write('        self.__dict__["_modifiedAttrs"]["%s"] = 1\n\n' % name)


def RemoveRelationship(write, name, inverseOf, nSided):
    write('        collection = getattr(self, "%s")\n' % name)
    write('        try:\n')
    write('            collection.remove_element(target)\n')
    write('        except CollectionBase.CollectionBase.ElementNotFound, e:\n')
    write('            from Ft.Ods.Exception import IntegrityError\n')
    write('            raise IntegrityError(relName="%s")\n' % name)
    write('        if inverse:\n')
    if nSided:
        write('            target.remove_%s(self, 0)\n' % inverseOf)
    else:
        write('            target.drop_%s(self, 0)\n' % inverseOf)
    write('\n    drop_%s = remove_%s\n\n' % (name, name))


def OperationInit(operation,params):
    write = params.outFile.write
    write('    def %s(self' % operation.name)
    #Write the parameters
    hasOut = 0
    for p in operation.signature:
        if p.parameter_mode in [Direction.mode_in,Direction.mode_inout]:
            write(", %s" % p.name)
        else:
            hasOut = 1
    write('):\n')

    #Write the doc string
    write('        """Operation %s:\n' % operation.name)
    write('        Parameters:\n')
    for p in operation.signature:
        write('            %s of type %s\n' % (p.name,p.type.absolute_name()))
    write('\n')
    write('        Results:\n')
    write('            type %s\n' % (operation.result.absolute_name()))
    outs = [OdlUtil.NormalizeTypeDefinition(operation.result)]
    for p in operation.signature:
        if p.parameter_mode in [Direction.mode_inout,Direction.mode_out]:
            write('            type %s\n' % (p.type.absolute_name()))
            outs.append(OdlUtil.NormalizeTypeDefinition(p.type))

    write('\n')
    if len(operation.exceptions):
        write('        Raises:\n')
        for e in operation.exceptions:
            write('            type %s\n' % (e.absolute_name()))
            
    write('\n        """\n\n')

    #Perform input type checks
    write('        #Type check input values\n')
    for p in operation.signature:
        if p.parameter_mode in [Direction.mode_in,Direction.mode_inout]:
            typ = OdlUtil.NormalizeTypeDefinition(p.type)
            write('        try:\n')
            write('            Constants.g_typeCheck[%d](%s,%d)\n' % (typ._4ods_getOdmgType(),p.name,typ._4ods_getId()))
            write('        except TypeError:\n')
            write('            from Ft.Ods.Exception import FtodsOperationBadParam\n')
            write('            raise FtodsOperationBadParam(name="%s",type="%s",value=%s)\n' % (p.name,
                                                                                               Constants.g_odmgTypeToString[typ._4ods_getOdmgType()],
                                                                                               p.name))
            
    write('\n')
    write('\n')
    #find the registered function
    write('        func = None\n')
    write('        if self._db:\n')
    write('            func = self._db._4ods_getRegisteredOperation(%d)\n' % operation._4ods_getId())
    write('        if func is None:\n')
    write('            from Ft.Ods.Exception import FtodsOperationNotImplemented\n')
    write('            raise FtodsOperationNotImplemented(name="%s")\n' % operation.absolute_name())

    write('\n')
    write('\n')
    write('        #Execute the function\n')
    write('        results = func(self')
    for p in operation.signature:
        if p.parameter_mode in [Direction.mode_in,Direction.mode_inout]:
            write(", %s" % p.name)
    write(')\n')

    write('\n')
    write('\n')
    write('        #Type check output value(s)\n')

    if len(outs) == 1:
        #Single return value
        write('        try:\n')
        write('            Constants.g_typeCheck[%d](results,%d)\n' % (outs[0]._4ods_getOdmgType(),outs[0]._4ods_getId()))
        write('        except TypeError:\n')
        write('            from Ft.Ods.Exception import FtodsOperationBadParam\n')
        write('            raise FtodsOperationBadParam(name="Function Results",type="%s",value=results)\n' % (Constants.g_odmgTypeToString[outs[0]._4ods_getOdmgType()]))
        write('            raise FtodsOperationBadParam(name="Function Results",type="",value=repr(results))\n')
    else:
        write('        if type(results) not in [type(()),type([])]:\n')
        write('            from Ft.Ods.Exception import FtodsOperationBadParam\n')
        write('            raise FtodsOperationBadParam(name="Function Results",type="",value=repr(results))\n')
        write('        if len(results) != %d:\n' % len(outs))
        write('            from Ft.Ods.Exception import FtodsOperationBadParam\n')
        write('            raise FtodsOperationBadParam(name="Function Results Length",type="",value=repr(results))\n')
        ctr = 0
        for o in outs:
            write('        try:\n')
            write('            Constants.g_typeCheck[%d](results[%d],%d)\n' % (o._4ods_getOdmgType(),ctr,o._4ods_getId()))
            write('        except TypeError:\n')
            write('            from Ft.Ods.Exception import FtodsOperationBadParam\n')
            write('            raise FtodsOperationBadParam(name="Function Results [%d]",type="%s",value=results[%d])\n' % (ctr,Constants.g_odmgTypeToString[outs[ctr]._4ods_getOdmgType()],ctr))

            ctr = ctr + 1


    write('        return results\n')


    write('\n')
    write('\n')

def TupleDefinitions(defines, bases, params):
    write = params.outFile.write
    stream = cStringIO.StringIO()
    output = stream.write

    collections = {}
    
    output('    _tupleDefinitions = %s._tupleDefinitions.copy()\n' % bases[0])
    for base in bases[1:]:
        output('    _tupleDefinitions.update(%s._tupleDefinitions)\n' % base)

    tupleDefs = '    _tupleDefinitions.update({'
    name_padding = ' '*len(tupleDefs)
    output(tupleDefs)
    for define in defines:
        if define.meta_kind in [MetaKind.mk_attribute, MetaKind.mk_relationship]:
            name = define.name
            odmgType = define._4ods_getOdmgType()
            dType = OdlUtil.NormalizeTypeDefinition(define.type)
            
            output(repr(name) + ' : {')
            defs_padding = ' '*(len(name_padding) + len(repr(name)) + 4)
            output('%d : %d,\n' % (TD.TYPE, odmgType))
            output(defs_padding)
            if define.meta_kind == MetaKind.mk_relationship or define.is_read_only:
                output('%d : 1,\n' % TD.READONLY)
                output(defs_padding)
            if dType.meta_kind == MetaKind.mk_collection:
                if dType.collection_kind == CollectionKind.ck_string:
                    output('%d : %d,\n' % (TD.COLLECTION_MAXSIZE,
                                           dType.max_size.value()))

                else:
                    subtype = OdlUtil.NormalizeTypeDefinition(dType.subtype)

                    if subtype._4ods_getOdmgType() in [Constants.Types.DATE,
                                                       Constants.Types.INTERVAL,
                                                       Constants.Types.TIME,
                                                       Constants.Types.TIMESTAMP
                                                       ]:

                        rid = -1
                    else:
                        rid = subtype._4ods_getId()


                    output('%d : %s,\n' % (TD.COLLECTION_SUBTYPE_REPO_ID,
                                           rid))
                    output(defs_padding)
                    
                    output('%d : %s,\n' % (TD.COLLECTION_SUBTYPE,
                                           subtype._4ods_getOdmgType()))
                    output(defs_padding)

                    if dType.collection_kind == CollectionKind.ck_dictionary:
                        keytype = OdlUtil.NormalizeTypeDefinition(dType.key_type)

                        if keytype._4ods_getOdmgType() in [Constants.Types.DATE,
                                                           Constants.Types.INTERVAL,
                                                           Constants.Types.TIME,
                                                           Constants.Types.TIMESTAMP]:
                            rid = -1
                        else:

                            rid = keytype._4ods_getId()


                        output('%d : %s,\n' % (TD.COLLECTION_KEYTYPE,
                                                   keytype._4ods_getOdmgType()))
                        output(defs_padding)
                        output('%d : %s,\n' % (TD.COLLECTION_KEYTYPE_REPO_ID,
                                                   rid))
                    
                        output(defs_padding)

            if Constants.g_literalTypes[dType._4ods_getOdmgType()]:
                if dType._4ods_getOdmgType() in [Constants.Types.STRUCTURE,
                                                 Constants.Types.ENUMERATION,
                                                 Constants.Types.UNION,
                                                 Constants.Types.FIXEDSTRING]:
                    rid = dType._4ods_getId()
                else:
                    rid = -1
                output('%d : %s,\n' % (TD.LITERAL_REPO_ID, rid))
                output(defs_padding)

            if define.meta_kind == MetaKind.mk_relationship:
                output('%d : 1,\n' % (TD.RELATIONSHIP))
                output(defs_padding)
            if define.meta_kind == MetaKind.mk_attribute and dType.meta_kind in [MetaKind.mk_interface,
                                                                                 MetaKind.mk_class]:
                output('%d : %d,\n' % (TD.OBJECT_ATTRIBUTE_REPO_ID,dType._4ods_getId()))
                output(defs_padding)
                       
            output('},\n%s' % name_padding)
    output('})\n')

    params.indent = '    '
    params.indent = ''
    write(stream.getvalue())
    stream.close()

    names,types = OdlUtil.BuildNameAndTypeList(defines)
    types = FlattenTypes(types)
    
    InheritedTuples(write, '_tupleNames', names, bases)
    InheritedTuples(write, '_tupleTypes', types, bases)

    return

def InheritedTuples(write, name, value, bases):
    write('    %s = %s.%s + ' % (name, bases[0], name))
    for base in bases[1:]:
        write('    %s.%s + ' % (base, name))
    write('%s\n' % str(tuple(value)))
    return

def FlattenTypes(types):
    newTypes = []
    for t in types:
        if type(t) == type(()):
            newTypes.append(FlattenTypes(t))
        else:
            newTypes.append(t._4ods_getOdmgType())
    return tuple(newTypes)
