/*****************************************************************************
 * multpass.c                                                                *
 *                                                                           *
 * Description:                                                              *
 *   Adds multi-schema support enhancement to exp2cxx parser.  Allows the *
 *   generation of C++ representations for multiple EXPRESS schemas without  *
 *   creating conflicting header files.  Previously, exp2cxx would gene-  *
 *   rate a single set of files (.h, .cc, .init.cc) for each schema found in *
 *   the processed EXPRESS file.  A number of problem situations occurred:   *
 *                                                                           *
 *   (1) Two schemas which USE or REFERENCE entities from one another.  If   *
 *       e.g. schema A USEs entity 1 from schema B and defines ent 2 subtype *
 *       of 1, and schema B USEs ent 2 from A and defines ents 1 and 3 sub-  *
 *       type of 2, neither include file could be compiled first.            *
 *   (2) An entity which has a select or enumeration attribute which is de-  *
 *       fined in another schema.                                            *
 *   (3) A select type which has a select or enum member item which is de-   *
 *       fined in another schema.
 *   (4) An enumeration type which is a redefinition of an enum defined in   *
 *       another schema.                                                     *
 *                                                                           *
 *   The functions in multpass.c provide for the processing of each schema   *
 *   in multiple passes.  At each pass, only entities and types which are    *
 *   not dependent on undefined objects are processed.  With each pass, more *
 *   entities and types become defined and more dependent entities and types *
 *   will become processable.  At each state of processing, a separate set   *
 *   of C++ files is generated.  Lastly, multpass processes certain aggre-   *
 *   gate and redefined objects after all the fundamental types are defined. *
 *                                                                           *
 * Created by:  David Rosenfeld                                              *
 * Date:        04/09/97                                                     *
 *****************************************************************************/

#include <stdlib.h>
#include "classes.h"

#include "./trace_fprintf.h"

int isAggregateType( const Type t );

/* Local function prototypes: */
static void initializeMarks( Express );
static void cleanupMarks( Express );
static void unsetObjs( Schema );
static bool checkTypes( Schema );
static bool checkEnts( Schema );
static void markDescs( Entity );
static bool checkItem( Type, Scope, Schema, int *, int );
static int ENUMcanBeProcessed( Type, Schema );
static int inSchema( Scope, Scope );
static void addRenameTypedefs( Schema, FILE * );
static void addAggrTypedefs( Schema , FILE * );
static void addUseRefNames( Schema, FILE * );

/**
 * Generates the C++ files corresponding to a list of schemas.  Does so in
 * multiple passes through the schemas.  In each pass it checks for enti-
 * ties which are subtypes of entities in other schemas which have not yet
 * been processed.  Such entities cannot be processed in that pass until
 * their supertypes have been defined.  It also checks for entities which
 * have enum or select attributes which have not been processed, and for
 * select types which have enum or select items (or entities containing
 * enums) which have not been processed.
 */
void print_schemas_separate( Express express, void * complexCol, FILES * files ) {
    bool complete = false;
    int val1, val2, suffix;
    DictionaryEntry de;
    Schema schema;

    /* First set all marks we'll be using to UNPROCESSED/NOTKNOWN: */
    initializeMarks( express );

    /* TODO only print gr, wr, str as needed, from SCHEMAprint in classes_wrapper.cc? */
    fprintf( files->create, "    Global_rule_ptr gr;\n    Where_rule_ptr wr;\n    std::string str; //for large strings such as functions or global rules\n" );

    DICTdo_type_init( express->symbol_table, &de, OBJ_SCHEMA );
    while( ( schema = ( Scope )DICTdo( &de ) ) != 0 ) {
        numberAttributes( schema );
    }
    while( !complete ) {
        complete = true;
        DICTdo_type_init( express->symbol_table, &de, OBJ_SCHEMA );
        while( ( schema = ( Scope )DICTdo( &de ) ) != 0 ) {
            if( schema->search_id == UNPROCESSED ) {
                /* i.e., if the schema has more ents/types to process in it */
                unsetObjs( schema );
                /* Unset the ones which had search_id = CANTPROCESS.  We're
                // going to check that again since things may have changed by
                // this pass.  The ones with search_id = PROCESSED do not
                // change since we're done with them.  */
                schema->search_id = PROCESSED;
                /* We assume this is the case unless something goes wrong. */
                val1 = checkTypes( schema );
                val2 = checkEnts( schema );
                /* The check functions recheck all the ents, types, USEd, and
                // REFs which are still NOTKNOWN to see if we can process any
                // more this pass.  If any returns TRUE, we'll process again
                // this round. */
                if( val1 || val2 ) {
                    if( schema->search_id == UNPROCESSED ||
                            *( int * )schema->clientData > 0 ) {
                        /* What we're trying to determine here is if we will
                        // need to print multiple files for this schema.  If
                        // we're already beyond a first file (2nd condition)
                        // or we're at the first but there are more entities
                        // which couldn't be processed yet (and thus search_id
                        // was still set to UNPROCESSED), this schema will be
                        // printed in multiple files.  If so, SCHEMAprint()
                        // will create files with the suffixes "_1", "_2", etc.
                        // If not, no file suffix will be added. */
                        suffix = ++*( int * )schema->clientData;
                        SCHEMAprint( schema, files, complexCol, suffix );
                    } else {
                        SCHEMAprint( schema, files, complexCol, 0 );
                    }
                }
                complete = complete && ( schema->search_id == PROCESSED );
                /* Job's not complete so long as schema still has entities it
                // had to skip. */
            }
        }
    }

    /*******************
    *******************/

    DICTdo_type_init( express->symbol_table, &de, OBJ_SCHEMA );
    while( ( schema = ( Scope )DICTdo( &de ) ) != 0 ) {
        fprintf( files->create,
                 "//////////////// USE statements\n" );
        USEREFout( schema, schema->u.schema->usedict, schema->u.schema->use_schemas, "USE", files->create );

        fprintf( files->create,
                 "//////////////// REFERENCE statements\n" );
        USEREFout( schema, schema->u.schema->refdict, schema->u.schema->ref_schemas, "REFERENCE", files->create );
    }
    /*****************
    *****************/
    /* Before closing, we have three more situations to deal with (i.e., three
    // types of declarations etc. which could only be printed at the end).
    // Each is explained in the header section of its respective function. */
    DICTdo_type_init( express->symbol_table, &de, OBJ_SCHEMA );
    while( ( schema = ( Scope )DICTdo( &de ) ) != 0 ) {
        /* (These two tasks are totally unrelated but are done in the same loop
        // for efficiency.) */
        addRenameTypedefs( schema, files->classes );
        addUseRefNames( schema, files->create );
    }
    /* Third situation:  (Must be dealt with after first, see header comments
    // of addAggrTypedefs.) */
    DICTdo_type_init( express->symbol_table, &de, OBJ_SCHEMA );
    while( ( schema = ( Scope )DICTdo( &de ) ) != 0 ) {
        addAggrTypedefs( schema, files->classes );
    }

    /* On our way out, print the necessary statements to add support for
    // complex entities.  (The 1st line below is a part of SchemaInit(),
    // which hasn't been closed yet.  (That's done on 2nd line below.)) */
    fprintf( files->initall, "     reg.SetCompCollect( gencomplex() );\n" );
    fprintf( files->initall, "}\n\n" );
    fprintf( files->incall,  "\n#include \"clstepcore/complexSupport.h\"\n" );
    fprintf( files->incall,  "ComplexCollect *gencomplex();\n" );

    /* Function GetModelContents() is printed at the end of the schema.xx
    // files.  This is done in a separate loop through the schemas, in function
    // below. */
    getMCPrint( express, files->incall, files->initall );

    /* Finally clean up memory allocated by initializeMarks. */
    cleanupMarks( express );
}

static void initializeMarks( Express express )
/*
 * Set all schema->search_id's to UNPROCESSED, meaning we haven't processed
 * all the ents and types in it yet.  Also, put an int=0 in each schema's
 * clientData field.  We'll use it to record what # file we're generating
 * for each schema.  Set all entity and type search_id's to NOTKNOWN mean-
 * we don't know if we can process it yet.  (An ent & select type may have
 * an attribute/item which comes from another schema.  All other types can
 * be processed the first time, but that will be caught in checkTypes().)
 */
{
    DictionaryEntry de_sch, de_ent, de_type;
    Schema schema;

    DICTdo_type_init( express->symbol_table, &de_sch, OBJ_SCHEMA );
    while( ( schema = ( Scope )DICTdo( &de_sch ) ) != 0 ) {
        schema->search_id = UNPROCESSED;
        schema->clientData = ( int * )malloc( sizeof( int ) );
        *( int * )schema->clientData = 0;
        SCOPEdo_entities( schema, ent, de_ent )
        ent->search_id = NOTKNOWN;
        SCOPEod
        SCOPEdo_types( schema, t, de_type )
        t->search_id = NOTKNOWN;
        SCOPEod
    }
}

static void cleanupMarks( Express express ) {
    DictionaryEntry de_sch;
    Schema schema;

    DICTdo_type_init( express->symbol_table, &de_sch, OBJ_SCHEMA );
    while( ( schema = ( Scope )DICTdo( &de_sch ) ) != 0 ) {
        if( schema->clientData ) {
            free( schema->clientData );
            schema->clientData = NULL;
        }
    }
}

static void unsetObjs( Schema schema )
/*
 * Resets all the ents & types of schema which had been set to CANTPROCRSS
 * to NOTKNOWN.  This function is called every time print_schemas_separate
 * iterates through the schemas, printing to file what can be printed.  At
 * each pass we re-examine what ents/types can be processed.  Ones which
 * couldn't be processed at the last pass may be processable now.  Ents/
 * types which have already been marked PROCESSED will not have to be
 * revisited, and are not changed.
 */
{
    DictionaryEntry de;

    SCOPEdo_types( schema, t, de )
    if( t->search_id == CANTPROCESS ) {
        t->search_id = NOTKNOWN;
    }
    SCOPEod
    SCOPEdo_entities( schema, ent, de )
    if( ent->search_id == CANTPROCESS ) {
        ent->search_id = NOTKNOWN;
    }
    SCOPEod
}

/**
 * Goes through the types contained in this schema checking for ones which
 * can't be processed.  This may be the case if:  (1) We have a select type
 * which has enumeration or select items which have not yet been defined
 * (are defined in a different schema we haven't processed yet).  (2) We
 * have a select which has an entity item which contains unprocessed enums.
 * (Unproc'ed selects, however, do not pose a problem here for reasons
 * beyond the scope.)  (3) We have an enum type which is a redefinition of
 * an enum not yet defined.  If we find any such type, we set its mark to
 * CANTPROCESS.  If some types in schema *can* be processed now, we return
 * TRUE.  (See relevant header comments of checkEnts() below.)
 */
static bool checkTypes( Schema schema ) {
    DictionaryEntry de;
    bool retval = false;
    int unknowncnt;
    Type i;
    Entity ent;
    Linked_List attribs;

    do {
        unknowncnt = 0;
        SCOPEdo_types( schema, type, de ) {
            if( type->search_id != NOTKNOWN ) {
                continue;
            }
            /* We're only interested in the ones which haven't been processed
            // already or accepted (set to CANPROCESS in a previous pass thru
            // the do loop) already. */
            type->search_id = CANPROCESS;
            /* Assume this until disproven. */

            if( TYPEis_enumeration( type ) && TYPEget_head( type ) ) {
                i = TYPEget_ancestor( type );
                if( !sameSchema( i, type ) && i->search_id != PROCESSED ) {
                    /* Note - if, however, i is in same schema, we're safe: We
                    // know it'll be processed this pass because enum's are
                    // always processed on the first pass.  (We do have to take
                    // care to process the original enum before the redefined.
                    // This is done in SCOPEPrint, in classes_wrapper.cc.) */
                    type->search_id = CANTPROCESS;
                    schema->search_id = UNPROCESSED;
                }
            } else if( TYPEis_select( type ) ) {
                LISTdo( SEL_TYPEget_items( type ), ii, Type ) {
                    if( !TYPEis_entity( ii ) ) {
                        if( checkItem( ii, type, schema, &unknowncnt, 0 ) ) {
                            break;
                        }
                        /* checkItem does most of the work of determining if
                        // an item of a select will make the select type un-
                        // processable.  It checks for conditions which would
                        // make this true and sets values in type, schema, and
                        // unknowncnt accordingly.  (See checkItem's commenting
                        // below.)  It also return TRUE if ii has made type un-
                        // processable.  If so, we break - there's no point
                        // checking the other items of type any more. */
                    } else {
                        /* Check if our select has an entity item which itself
                        // has unprocessed selects or enums. */
                        ent = ENT_TYPEget_entity( ii );
                        if( ent->search_id == PROCESSED ) {
                            continue;
                        }
                        /* If entity has been processed already, things must be
                        // okay. (Note - but if it hasn't been processed yet we
                        // may still be able to process type.  This is because
                        // a sel type will only contain a pointer to an entity-
                        // item (and we can create a pointer to a not-yet-pro-
                        // cessed object), while it will contain actual objects
                        // for the enum and select attributes of ent.) */
                        attribs = ENTITYget_all_attributes( ent );
                        LISTdo_n( attribs, attr, Variable, z ) {
                            if( checkItem( attr->type, type, schema,
                                        &unknowncnt, 1 ) ) {
                                break;
                            }
                        } LISTod
                        LISTfree( attribs );
                    }
                } LISTod
                /* One more condition - if we're a select which is a rename of
                // another select - we must also make sure the original select
                // is in this schema or has been processed.  Since a rename-
                // select is defined with typedef's to the original, we can't
                // do that if the original hasn't been defined. */
                if( ( type->search_id == CANPROCESS )
                        && ( ( i = TYPEget_ancestor( type ) ) != NULL )
                        && ( !sameSchema( i, type ) )
                        && ( i->search_id != PROCESSED ) ) {
                    type->search_id = CANTPROCESS;
                    schema->search_id = UNPROCESSED;
                }
            }

            if( type->search_id == CANPROCESS ) {
                /* NOTE - This condition will be met if type isn't a select or
                // enum at all and above if was never entered (and it's our
                // first pass so type hasn't been processed).  So for non-enums
                // and selects, checkTypes() will simply check the type off and
                // go on. */
                retval = true;
            }
        } SCOPEod
    } while( unknowncnt > 0 );
    /* We loop to deal with the following situation:  Say sel A contains enum B
    // as an item, but A appears earlier in the EXPRESS file than B.  In such a
    // case, we really can process A now since it doesn't depend on anything
    // which won't be processed till later.  But when we first reach A, item B
    // will have value NOTKNOWN, and at the time we won't know if B will be set
    // to CANPROCESS when we get to it.  To deal with this, checkItem() below
    // increments unknowncnt for every item which is in this schema and still
    // has search_id = NOTKNOWN.  (Not if id = CANTPROCESS, which means we've
    // tried already this pass and B still can't be processed.)  Here, we keep
    // looping until all the unknowns are resolved.  (It may take may passes if
    // selX has item selY which has item selZ.)  (I take all the trouble with
    // this so as not to generate mult files in unnecessary cases where e.g.
    // the EXPRESS is in a single schema.) */

    return retval;
}

/**
 * Goes through the entities contained in this schema checking for ones
 * which can't be processed.  It checks for two situations:  (1) If we find
 * an entity which is a subtype of a not-yet-processed entity in another
 * schema.  (2) If an entity has an attribute which is an enumeration or
 * select type (which is implemented by exp2cxx as a C++ class), and the
 * enum or select class has not yet been defined.  For each entity which
 * satisfies one of the above conditions, we set its mark and the marks of
 * all its subtype descendents to CANTPROCESS.  Later, when C++ files are
 * generated for this schema, the ents marked CANTPROCESS will be skipped.
 * checkEnts() will return TRUE if there are some ents/types which *can* be
 * processed now.  This will tell later functions that there are some pro-
 * cessable entities at this pass and C++ files should be generated.  (Some
 * of the inline commenting of checkTypes() is applicable here and is not
 * repeated.)
 */
static bool checkEnts( Schema schema ) {
    DictionaryEntry de;
    bool retval = false;
    int ignore = 0;

    /* Loop through schema's entities: */
    SCOPEdo_entities( schema, ent, de )
    if( ent->search_id != NOTKNOWN ) {
        continue;
    }
    /* ent->search_id may = CANTPROCESS signifying we've already determined
    // that this ent is dependent on an undefined external one.  (It got
    // its mark already because it was the descendent of a parent entity we
    // already checked and rejected.)  ent->search_id may = PROCESSED.  In
    // such a case there of course is also nothing to check now. */
    ent->search_id = CANPROCESS;

    /* First traverse ent's supertypes.  If any is from a different schema
    // and is not yet defined, ent will have to wait. */
    LISTdo( ENTITYget_supertypes( ent ), super, Entity )
    if( ( !sameSchema( ent, super ) )
            && ( super->search_id != PROCESSED ) ) {
        markDescs( ent );
        schema->search_id = UNPROCESSED;
        break;
        /* Exit the LISTdo loop.  Since we found an unprocessed
        // parent, we're done with this entity. */
    }
    LISTod

    /* Next traverse ent's attributes, looking for attributes which are
    // not yet defined (more explanation in checkItem()). */
    if( ent->search_id == CANPROCESS ) {
        /* Only do next test if ent hasn't already failed the 1st. */
        LISTdo( ENTITYget_attributes( ent ), attr, Variable )
        if( checkItem( attr->type, ent, schema, &ignore, 0 ) ) {
            markDescs( ent );
            break;
        }
        LISTod
    }

    if( ent->search_id == CANPROCESS ) {
        /* If ent's mark still = CANPROCESS and not CANTPROCESS, it
        // must still be processable.  Set retval to TRUE signifying
        // that there are ent's we'll be able to process. */
        retval = true;
    }
    SCOPEod
    /* NOTE - We don't have to loop here as in checkTypes() (see long comment
    // there).  It was necessary there because we may have processed type
    // sel1 before its member enum1.  Whereas here, since all types are done
    // before selects (checkTypes() is called before checkEnts()), there is
    // no such concern. */

    return retval;
}

/**
 * Sets the mark value of ent and all its subtypes to CANTPROCESS.  This
 * function is called if we've determined that ent is a subtype of an
 * entity defined in a different schema which has not yet been processed.
 */
static void markDescs( Entity ent ) {
    ent->search_id = CANTPROCESS;
    LISTdo( ENTITYget_subtypes( ent ), sub, Entity ) {
        markDescs( sub );
    } LISTod
}

/**
 * Function with a lot of side effects: Checks if type t, a member of
 * `parent' makes parent unprocessable.  parent may be an entity and t is
 * its attribute.  parent may be a select type and t is one of its items.
 * parent may be a select type and t an attribute of one of its entity
 * items.  Lastly, parent may be an aggregate and t its base type.  t will
 * make parent unprocessable if it is an enum or sel which hasn't been
 * processed yet, see inline commenting.  If so, parent->search_id is set
 * to CANTPROCESS, and schema->search_id is set to UNPROCESSED (i.e., we're
 * not done yet).  Also, if one of parent's items is still NOTKNOWN (caused
 * if it's in our schema and we haven't gotten to it yet - see comment, end
 * of checkTypes()), parent is set to NOTKNOWN and unknowncnt is incremen-
 * ted to tell us that we'll have to try parent again after its item has
 * been processed (i.e., we have to repeat the do-loop in checkTypes()).
 * checkItem returns TRUE if parent became unprocessable; FALSE otherwise.
 *
 * NOTE:  noSel deals with the following:  Say selA has member entX.  If
 * entX has attribute enumP, it creates a problem, while if entX has attr
 * selB, it is not.  When using checkItem for an ent member of a select,
 * noSel is set to 1 to tell it to worry about t if it's an enum but not
 * if it's a select.
 */
static bool checkItem( Type t, Scope parent, Schema schema, int * unknowncnt, int noSel ) {
    Type i = t;

    if( isAggregateType( t ) ) {
        i = TYPEget_base_type( t );
        /* NOTE - If t is a 2D aggregate or higher, we do not go down to its
        // lowest base type.  An item which is a higher dimension aggregates
        // does not make its parent unprocessable.  All an e.g. entity needs
        // defined to have a 2D aggr attribute is GenericAggregate (built in)
        // and a typedef for a pointer to the type name, which is declared in
        // Sdaiclasses.h. */
    }

    if( TYPEis_enumeration( i ) && !ENUMcanBeProcessed( i, schema ) ) {
        /* Enum's are usually processed on the first try.  ENUMcanBeProcessed()
        // checks for cases of renamed enum's, which must wait for the enum i
        // is a rename of. */
        if( parent->search_id == NOTKNOWN ) {
            /* We had thought parent's val was going to be NOTKNOWN - i.e.,
            // dependent on other selects in this schema which haven't been
            // processed.  When we set it to NOTKNOWN we also incremented
            // unknowncnt.  Now we see it's not going to be unknown so we
            // decrement the count: */
            ( *unknowncnt )--;
        }
        parent->search_id = CANTPROCESS;
        schema->search_id = UNPROCESSED;
        return true;
    } else if( TYPEis_select( i ) && !noSel ) {
        if( !sameSchema( i, parent ) ) {
            if( i->search_id != PROCESSED ) {
                if( parent->search_id == NOTKNOWN ) {
                    ( *unknowncnt )--;
                }
                parent->search_id = CANTPROCESS;
                schema->search_id = UNPROCESSED;
                return true;
            }
        } else {
            /* We have another sel in the same schema.  This gets complicated -
            // it may be processable but we just haven't gotten to it yet.  So
            // we may have to wait on parent. */
            if( i->search_id == CANTPROCESS ) {
                /* We *have* checked i already and it can't be processed. */
                if( parent->search_id == NOTKNOWN ) {
                    ( *unknowncnt )--;
                }
                parent->search_id = CANTPROCESS;
                schema->search_id = UNPROCESSED;
                return true;
            } else if( i->search_id == NOTKNOWN ) {
                /* We haven't processed i this pass. */
                if( parent->search_id != NOTKNOWN ) {
                    parent->search_id = NOTKNOWN;
                    /* We lower parent's value.  But don't return TRUE.  That
                    // would tell checkTypes() that there's nothing more to
                    // check.  But checkTypes should keep looping thru the re-
                    // maining items of parent - maybe one of them will tell us
                    // that parent definitely can't be processed this pass. */
                    ( *unknowncnt )++;
                }
            }
        }
    }
    return false;
}

static int ENUMcanBeProcessed( Type e, Schema s )
/*
 * Tells us if an enumeration type has been processed already, or if not
 * will be processed this pass through schema s.  As always, I take great
 * pains to avoid breaking up a schema into >1 file unnecessarily.  In
 * cases where a user is building a library based on a single schema, we
 * shouldn't cause him to suffer side-effects of the mult schema support,
 * because of a case that "looks like" it may require multiple files.
 */
{
    Type a;

    if( !inSchema( e, s ) ) {
        /* If e is not in s - the schema we're processing now - things are
        // fairly simple.  Nothing is going to change by the time we finish
        // with this schema.  Base the return val on whether or not e *was*
        // processed already. */
        return ( e->search_id == PROCESSED );
    }

    if( e->search_id != NOTKNOWN ) {
        /* Next case: e is in our schema, but either it's been processed
        // already, or we've determined that it can or can't be processed.
        // This case is also relatively simple - we have nothing more to
        // figure out here. */
        return ( e->search_id >= CANPROCESS );
        /* PROC/CANPROC - TRUE; UNPROC'ED/CANTPROC - FALSE */
    }

    /* Remaining case: e is in our schema and still = NOTKNOWN.  I.e., we
    // haven't gotten to e this pass and don't yet know whether it'll be
    // processable.  Figure that out now: */
    if( ( a = TYPEget_ancestor( e ) ) == NULL ) {
        /* If e is not a rename of anything, it should be processed now. */
        return true;
    }
    if( inSchema( a, s ) || a->search_id == PROCESSED ) {
        /* If e's ancestor (the one it's a rename of) is in our schema it will
        // be processed now.  If not, it must have been processed already. */
        return true;
    }
    return false;
}

int sameSchema( Scope sc1, Scope sc2 )
/*
 * Checks if sc1 and sc2 are in the same superscope.  Normally called for
 * two types to see if they're in the same schema.
 */
{
    return ( !strcmp( SCOPEget_name( sc1->superscope ),
                      SCOPEget_name( sc2->superscope ) ) );
}

static int inSchema( Scope scope, Scope super )
/*
 * Checks if scope is contained in super's scope.
 */
{
    return ( !strcmp( SCOPEget_name( scope->superscope ),
                      SCOPEget_name( super ) ) );
}

/**
 * Prints typedefs at the end of Sdaiclasses.h for enumeration or select
 * types which are renamed from other enum/sel's.  Since the original e/s
 * may be in any schema, this must be done at the end of all the schemas.
 * (Actually, for the enum only the aggregate class name is written in
 * Sdaiclasses.h (needs to have forward declarations here).)
 */
static void addRenameTypedefs( Schema schema, FILE * classes ) {
    DictionaryEntry de;
    Type i;
    char nm[BUFSIZ+1], basenm[BUFSIZ+1];
    static bool firsttime = true;

    SCOPEdo_types( schema, t, de ) {
        if( ( TYPEis_enumeration( t ) || TYPEis_select( t ) )
                && ( ( i = TYPEget_ancestor( t ) ) != NULL ) ) {
            /* I.e., t is a renamed enum/sel type.  i is set to the orig enum/
            // sel t is based on (in case it's a rename of a rename etc). */
            if( firsttime ) {
                fprintf( classes, "\n// Renamed enum and select" );
                fprintf( classes, " types (from all schemas):\n" );
                firsttime = false;
            }
            if( TYPEis_enumeration( t ) ) {
                strncpy( nm, TYPEget_ctype( t ), BUFSIZ - 1 );
                nm[BUFSIZ-1] = '\0';
                strncpy( basenm, TYPEget_ctype( i ), BUFSIZ - 1 );
                basenm[BUFSIZ-1] = '\0';
                fprintf( classes, "typedef %s_agg        %s_agg;\n", basenm, nm );
            } else {
                strncpy( nm, SelectName( TYPEget_name( t ) ), BUFSIZ - 1 );
                nm[BUFSIZ-1] = '\0';
                strncpy( basenm, SelectName( TYPEget_name( i ) ), BUFSIZ - 1 );
                basenm[BUFSIZ-1] = '\0';
                fprintf( classes, "typedef       %s         %s;\n", basenm, nm );
                fprintf( classes, "typedef       %s_agg     %s_agg;\n\n", basenm, nm );
                fprintf( classes, "typedef       %s *       %s_ptr;\n", nm, nm );
                fprintf( classes, "typedef const %s *       %s_ptr_c;\n", nm, nm );
                fprintf( classes, "typedef       %s_agg *   %s_agg_ptr;\n", nm, nm );
                fprintf( classes, "typedef const %s_agg *   %s_agg_ptr_c;\n", nm, nm );
            }
        }
    } SCOPEod
}

/**
 * Print typedefs at the end of Sdiaclasses.h for aggregates of enum's and
 * selects.  Since the underlying enum/sel may appear in any schema, this
 * must be done at the end of all the schemas.  Note that this function is
 * called after addRenameTypedefs() since an aggregate may also be based on
 * one of the renamed enum/sel's defined there.
 */
static void addAggrTypedefs( Schema schema, FILE * classes ) {
    DictionaryEntry de;
    Type i;
    static bool firsttime = true;
    char nm[BUFSIZ+1];

    SCOPEdo_types( schema, t, de ) {
        if( TYPEis_aggregate( t ) ) {
            i = TYPEget_base_type( t );
            if( TYPEis_enumeration( i ) || TYPEis_select( i ) ) {
                /* This if will pass if t was a 1D aggregate only.  They are
                // the only types which had to wait for their underlying type.
                // 2D aggr's and higher only need type GenericAggr defined
                // which is built-in. */
                if( firsttime ) {
                    fprintf( classes, "\n// Aggregate types (from all schemas) which depend on other types:\n" );
                    firsttime = false;
                }
                strncpy( nm, ClassName( TYPEget_name( t ) ), BUFSIZ );
                nm[BUFSIZ-1] = '\0';
                fprintf( classes, "typedef %s        %s;\n", TYPEget_ctype( t ), nm );
                fprintf( classes, "typedef       %s *   %sH;\n", nm, nm );
                fprintf( classes, "typedef       %s *   %s_ptr;\n", nm, nm );
                fprintf( classes, "typedef const %s *   %s_ptr_c;\n", nm, nm );
            }
        }
    } SCOPEod
}

/**
 * Checks the USE and REFERENCE dicts contained in schema.  If either dict
 * contains items (types or entities) which are renamed in this schema,
 * code is written to add another member to the "altNames" list of the
 * corresponding TypeDescriptor or EntityDescriptor object in the SCL.  The
 * list will be used in the SCL to use the correct name of this type or
 * entity when reading and writing files.
 */
static void addUseRefNames( Schema schema, FILE * create ) {
    Dictionary useRefDict;
    DictionaryEntry de;
    Rename * rnm;
    char * oldnm, schNm[BUFSIZ+1];
    static bool firsttime = true;

    if( ( useRefDict = schema->u.schema->usedict ) != NULL ) {
        DICTdo_init( useRefDict, &de );
        while( ( rnm = ( Rename * )DICTdo( &de ) ) != 0 ) {
            oldnm = ( ( Scope )rnm->object )->symbol.name;
            if( ( strcmp( oldnm, rnm->nnew->name ) ) ) {
                /* strcmp != 0, so old and new names different.
                // Note: can't just check if nnew != old.  That wouldn't
                // catch following:  schema C USEs obj Y from schema B
                // (not renamed).  B USEd it from schema A and renamed it
                // from X.  nnew would = old, but name would not be same
                // as rnm->object's name. */
                if( firsttime ) {
                    fprintf( create, "        // Alternate names for types and entities when used in other schemas:\n" );
                    firsttime = false;
                }
                if( rnm->type == OBJ_TYPE ) {
                    fprintf( create, "        %s", TYPEtd_name( ( Type )rnm->object ) );
                } else {
                    /* must be an entity */
                    fprintf( create, "        %s%s%s",
                             SCOPEget_name( ( ( Entity )rnm->object )->superscope ),
                             ENT_PREFIX, ENTITYget_name( ( Entity )rnm->object ) );
                }
                strcpy( schNm, PrettyTmpName( SCHEMAget_name( schema ) ) );
                fprintf( create, "->addAltName( \"%s\", \"%s\" );\n",
                         schNm, PrettyTmpName( rnm->nnew->name ) );
            }
        }
    }
    if( ( useRefDict = schema->u.schema->refdict ) != NULL ) {
        DICTdo_init( useRefDict, &de );
        while( ( rnm = ( Rename * )DICTdo( &de ) ) != 0 ) {
            oldnm = ( ( Scope )rnm->object )->symbol.name;
            if( ( strcmp( oldnm, rnm->nnew->name ) ) ) {
                if( firsttime ) {
                    fprintf( create, "        // Alternate names for types and " );
                    fprintf( create, "entities when used in other schemas:\n" );
                    firsttime = false;
                }
                if( rnm->type == OBJ_TYPE ) {
                    fprintf( create, "        %s", TYPEtd_name( ( Type )rnm->object ) );
                } else {
                    fprintf( create, "        %s%s%s",
                             SCOPEget_name( ( ( Entity )rnm->object )->superscope ),
                             ENT_PREFIX, ENTITYget_name( ( Entity )rnm->object ) );
                }
                strcpy( schNm, PrettyTmpName( SCHEMAget_name( schema ) ) );
                fprintf( create, "->addAltName( \"%s\", \"%s\" );\n",
                         schNm, PrettyTmpName( rnm->nnew->name ) );
            }
        }
    }
}
