/*
 * dump-smi.c --
 *
 *      Operations to dump SMIv1/v2 module information.
 *
 * Copyright (c) 1999 Frank Strauss, Technical University of Braunschweig.
 *
 * See the file "COPYING" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 * @(#) $Id: dump-smi.c 1796 2013-06-06 09:11:43Z schoenw $
 */

#include <config.h>

#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <time.h>

#include "smi.h"
#include "smidump.h"



extern int smiAsprintf(char **strp, const char *format, ...);



#define  INDENT		4    /* indent factor */
#define  INDENTVALUE	16   /* column to start values, except multiline */
#define  INDENTTEXTS	 9   /* column to start multiline texts */
#define  INDENTTEXTS2	15   /* column to start indented multiline texts */
#define  INDENTMAX	72   /* max column to fill, break lines otherwise */



static char *convertTypev1[] = {
    NULL,		 "Counter32",	       NULL,	   "Counter",
    NULL,		 "Gauge32",	       NULL,	   "Gauge",
    NULL,		 "Integer32",	       NULL,	   "INTEGER",
    NULL,		 "Unsigned32",	       NULL,	   "Gauge",
    NULL,		 "Bits",	       NULL,	   "OCTET STRING",
    NULL,		 "OctetString",	       NULL,	   "OCTET STRING",
    NULL,		 "ObjectIdentifier",   NULL,	   "OBJECT IDENTIFIER",
    NULL, NULL, NULL, NULL };


static char *convertTypev2[] = {
    NULL,		 "INTEGER",	       NULL,	   "Integer32",
    NULL,		 "Enumeration",	       NULL,	   "INTEGER",
    NULL,		 "Bits",	       NULL,	   "BITS",
    NULL,		 "OctetString",	       NULL,	   "OCTET STRING",
    NULL,		 "ObjectIdentifier",   NULL,	   "OBJECT IDENTIFIER",
    NULL,		 "Counter",	       NULL,	   "Counter32",
    NULL,		 "Gauge",	       NULL,	   "Gauge32",
    NULL, NULL, NULL, NULL };

static char *convertTypePIBtoMIB[] = {
    NULL,		 "Unsigned64",	       NULL,	   "IBRUnsigned64",
    NULL,		 "Integer64",	       NULL,	   "IBRInteger64",
/* XXX This breaks SMIv2 -> SMIv? compilation such that the IMPORT statement
   for SnmpAdminString is removed. I've remove this as a quick fix, but I'm
   not sure what to do for PIB compilation here.
    NULL,		 "SnmpAdminString",    NULL,	   "OCTET STRING",
*/
    NULL, NULL, NULL, NULL };

static char *convertImportv2[] = {
    "IRTF-NMRG-SMING-TYPES", "IpAddress",       "SNMPv2-SMI", "IpAddress",
    "IRTF-NMRG-SMING-TYPES", "Counter32",       "SNMPv2-SMI", "Counter32",
    "IRTF-NMRG-SMING-TYPES", "Gauge32",	        "SNMPv2-SMI", "Gauge32",
    "IRTF-NMRG-SMING-TYPES", "TimeTicks",       "SNMPv2-SMI", "TimeTicks",
    "IRTF-NMRG-SMING-TYPES", "Opaque",	        "SNMPv2-SMI", "Opaque",
    "IRTF-NMRG-SMING-TYPES", "Counter64",       "SNMPv2-SMI", "Counter64",
    "IRTF-NMRG-SMING-TYPES", "DisplayString",   "SNMPv2-TC", "DisplayString",
    "IRTF-NMRG-SMING-TYPES", "PhysAddress",     "SNMPv2-TC", "PhysAddress",
    "IRTF-NMRG-SMING-TYPES", "MacAddress",      "SNMPv2-TC", "MacAddress",
    "IRTF-NMRG-SMING-TYPES", "TruthValue",      "SNMPv2-TC", "TruthValue",
    "IRTF-NMRG-SMING-TYPES", "TestAndIncr",     "SNMPv2-TC", "TestAndIncr",
    "IRTF-NMRG-SMING-TYPES", "AutonomousType",  "SNMPv2-TC", "AutonomousType",
    "IRTF-NMRG-SMING-TYPES", "InstancePointer", "SNMPv2-TC", "InstancePointer",
    "IRTF-NMRG-SMING-TYPES", "VariablePointer", "SNMPv2-TC", "VariablePointer",
    "IRTF-NMRG-SMING-TYPES", "RowPointer",      "SNMPv2-TC", "RowPointer",
    "IRTF-NMRG-SMING-TYPES", "RowStatus",       "SNMPv2-TC", "RowStatus",
    "IRTF-NMRG-SMING-TYPES", "TimeStamp",       "SNMPv2-TC", "TimeStamp",
    "IRTF-NMRG-SMING-TYPES", "TimeInterval",    "SNMPv2-TC", "TimeInterval",
    "IRTF-NMRG-SMING-TYPES", "DateAndTime",     "SNMPv2-TC", "DateAndTime",
    "IRTF-NMRG-SMING-TYPES", "StorageType",     "SNMPv2-TC", "StorageType",
    "IRTF-NMRG-SMING-TYPES", "TDomain",         "SNMPv2-TC", "TDomain",
    "IRTF-NMRG-SMING-TYPES", "TAddress",        "SNMPv2-TC", "TAddress",
    "IRTF-NMRG-SMING", NULL,                    "SNMPv2-SMI", NULL,

    "RFC1155-SMI", "internet",	    "SNMPv2-SMI", "internet",
    "RFC1155-SMI", "directory",	    "SNMPv2-SMI", "directory",
    "RFC1155-SMI", "mgmt",	    "SNMPv2-SMI", "mgmt",
    "RFC1155-SMI", "experimental",  "SNMPv2-SMI", "experimental",
    "RFC1155-SMI", "private",	    "SNMPv2-SMI", "private",
    "RFC1155-SMI", "enterprises",   "SNMPv2-SMI", "enterprises",
    "RFC1155-SMI", "IpAddress",     "SNMPv2-SMI", "IpAddress",
    "RFC1155-SMI", "Counter",       "SNMPv2-SMI", "Counter32",
    "RFC1155-SMI", "Gauge",         "SNMPv2-SMI", "Gauge32",
    "RFC1155-SMI", "TimeTicks",     "SNMPv2-SMI", "TimeTicks",
    "RFC1155-SMI", "Opaque",        "SNMPv2-SMI", "Opaque",
    "RFC1213-MIB", "mib-2",         "SNMPv2-SMI", "mib-2",    
    "RFC1213-MIB", "DisplayString", "SNMPv2-TC",  "DisplayString",    

/* import types for PIB to MIB conversion */

    NULL, NULL, NULL, NULL };

static char *convertImportv1[] = {
    "IRTF-NMRG-SMING-TYPES", "IpAddress",   "RFC1155-SMI", "IpAddress",
    "IRTF-NMRG-SMING-TYPES", "Counter32",   "RFC1155-SMI", "Counter",
    "IRTF-NMRG-SMING-TYPES", "Gauge32",	    "RFC1155-SMI", "Gauge",
    "IRTF-NMRG-SMING-TYPES", "TimeTicks",   "RFC1155-SMI", "TimeTicks",
    "IRTF-NMRG-SMING-TYPES", "Opaque",	    "RFC1155-SMI", "Opaque",
    "IRTF-NMRG-SMING-TYPES", "Counter64",   NULL,          NULL,
    "IRTF-NMRG-SMING",       "mib-2",	    "RFC1213-MIB", "mib-2",
    
    "SNMPv2-SMI", "IpAddress",              "RFC1155-SMI", "IpAddress",
    "SNMPv2-SMI", "Counter32",              "RFC1155-SMI", "Counter",
    "SNMPv2-SMI", "Gauge32",	            "RFC1155-SMI", "Gauge",
    "SNMPv2-SMI", "TimeTicks",              "RFC1155-SMI", "TimeTicks",
    "SNMPv2-SMI", "Opaque",	            "RFC1155-SMI", "Opaque",
    "SNMPv2-SMI", "Integer32",	            NULL,	   NULL,
    "SNMPv2-SMI", "Unsigned32",	            NULL,	   NULL,
    "SNMPv2-SMI", "Counter64",	            NULL,	   NULL,
    "SNMPv2-SMI", "mib-2",                  "RFC1213-MIB", "mib-2",
    "SNMPv2-SMI", "MODULE-IDENTITY",        NULL,	   NULL,
    "SNMPv2-SMI", "OBJECT-IDENTITY",        NULL,	   NULL,
    "SNMPv2-SMI", "ObjectName",		    NULL,	   NULL,
    "SNMPv2-SMI", "NotificationName",       NULL,	   NULL,
    "SNMPv2-SMI", "ObjectSyntax",	    NULL,	   NULL,
    "SNMPv2-SMI", "SimpleSyntax",	    NULL,	   NULL,
    "SNMPv2-SMI", "ApplicationSyntax",      NULL,	   NULL,
    "SNMPv2-SMI", "OBJECT-TYPE",	    NULL,	   NULL,
    "SNMPv2-SMI", "NOTIFICATION-TYPE",      NULL,	   NULL,
    NULL, NULL, NULL, NULL };

static int current_column = 0;
static int smiv1 = 0;
static int silent = 0;
static int pibtomib = 0;


typedef struct Import {
    char          *module;
    char          *name;
    struct Import *nextPtr;
} Import;

static Import *importList = NULL;



static int invalidType(SmiBasetype basetype)
{
    if (smiv1) {
	return (basetype == SMI_BASETYPE_INTEGER64)
	    || (basetype == SMI_BASETYPE_UNSIGNED64)
	    || (basetype == SMI_BASETYPE_FLOAT32)
	    || (basetype == SMI_BASETYPE_FLOAT64)
	    || (basetype == SMI_BASETYPE_FLOAT128);
    } else if (!pibtomib) {
	return (basetype == SMI_BASETYPE_INTEGER64)
	    || (basetype == SMI_BASETYPE_FLOAT32)
	    || (basetype == SMI_BASETYPE_FLOAT64)
	    || (basetype == SMI_BASETYPE_FLOAT128);
    } else {
	return (basetype == SMI_BASETYPE_FLOAT32)
	    || (basetype == SMI_BASETYPE_FLOAT64)
	    || (basetype == SMI_BASETYPE_FLOAT128);
    }
}



static char *getStatusString(SmiStatus status)
{
    if (smiv1) {
	return
	    (status == SMI_STATUS_CURRENT)     ? "mandatory" :
	    (status == SMI_STATUS_DEPRECATED)  ? "deprecated" :
	    (status == SMI_STATUS_OBSOLETE)    ? "obsolete" :
	    (status == SMI_STATUS_MANDATORY)   ? "mandatory" :
	    (status == SMI_STATUS_OPTIONAL)    ? "optional" :
					         "<unknown>";
    } else {
	return
	    (status == SMI_STATUS_CURRENT)     ? "current" :
	    (status == SMI_STATUS_DEPRECATED)  ? "deprecated" :
	    (status == SMI_STATUS_OBSOLETE)    ? "obsolete" :
	    (status == SMI_STATUS_MANDATORY)   ? "current" :
	    (status == SMI_STATUS_OPTIONAL)    ? "current" :
					         "<unknown>";
    }
}



static char *getAccessString(SmiAccess access, int create)
{
    if (smiv1) {
	return
	    (access == SMI_ACCESS_NOT_ACCESSIBLE) ? "not-accessible" :
	    (access == SMI_ACCESS_NOTIFY)	  ? "read-only" :
	    (access == SMI_ACCESS_READ_ONLY)      ? "read-only" :
	    (access == SMI_ACCESS_READ_WRITE)     ? "read-write" :
						    "<unknown>";

    } else {
	if (create && (access == SMI_ACCESS_READ_WRITE)) {
	    return "read-create";
	} else {
            if (pibtomib)
                return
	        (access == SMI_ACCESS_NOT_ACCESSIBLE) ? "not-accessible" :
	        (access == SMI_ACCESS_NOTIFY)	      ? "read-only" :
	        (access == SMI_ACCESS_READ_ONLY)      ? "read-only" :
	        (access == SMI_ACCESS_READ_WRITE)     ? "read-write" :
	        (access == SMI_ACCESS_INSTALL)        ? "read-create" :
	        (access == SMI_ACCESS_INSTALL_NOTIFY) ? "read-create" :
						        "<unknown>";
            else
	        return
	        (access == SMI_ACCESS_NOT_ACCESSIBLE) ? "not-accessible" :
	        (access == SMI_ACCESS_NOTIFY)	  ? "accessible-for-notify" :
	        (access == SMI_ACCESS_READ_ONLY)      ? "read-only" :
	        (access == SMI_ACCESS_READ_WRITE)     ? "read-write" :
						        "<unknown>";
	}
    }
}



static char *getTimeString(time_t t)
{
    static char   *s = NULL;
    struct tm	  *tm;

    if (s) xfree(s);
    
    tm = gmtime(&t);
    smiAsprintf(&s, "%04d%02d%02d%02d%02dZ",
		tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
		tm->tm_hour, tm->tm_min);
    return s;
}



static char *getTypeString(SmiBasetype basetype, SmiType *smiType)
{
    int         i;
    char	**convertType;
    char        *typeModule, *typeName;

    typeName = smiType ? smiType->name : NULL;
    typeModule = smiType ? smiGetTypeModule(smiType)->name : NULL;

    convertType = smiv1 ? convertTypev1 : convertTypev2;

    if (typeName &&
	(basetype != SMI_BASETYPE_ENUM) &&
	(basetype != SMI_BASETYPE_BITS)) {
        if (pibtomib)
	    for(i=0; convertTypePIBtoMIB[i+1]; i += 4) {
	        if ((!strcmp(typeName, convertTypePIBtoMIB[i+1])) &&
		    ((!typeModule) || (!convertTypePIBtoMIB[i]) ||
		     (!strcmp(typeModule, convertTypePIBtoMIB[i])))) {
		    return convertTypePIBtoMIB[i+3];
	        }
	    }
	for(i=0; convertType[i+1]; i += 4) {
	    if ((!strcmp(typeName, convertType[i+1])) &&
		((!typeModule) || (!convertType[i]) ||
		 (!strcmp(typeModule, convertType[i])))) {
		return convertType[i+3];
	    }
	}
    }

    if ((!typeModule) || (!strlen(typeModule)) || (!typeName)) {
	if (basetype == SMI_BASETYPE_ENUM) {
	    return "INTEGER";
	}
	if (basetype == SMI_BASETYPE_BITS) {
	    if (smiv1) {
		return "OCTET STRING";
	    } else {
		return "BITS";
	    }
	}
    }
	
    /* TODO: fully qualified if unambigous */

    return typeName;
}



static char *getOidString(SmiNode *smiNode, int importedParent)
{
    SmiNode	 *parentNode;
    SmiModule	 *smiModule;
    static char	 s[200];
    char	 append[200];
    unsigned int i;

    append[0] = 0;

    parentNode = smiNode;
    smiModule = smiGetNodeModule(smiNode);
    
    do {
	
	if (parentNode->oidlen <= 1) {
	    break;
	}
	
	/* prepend the cut-off subidentifier to `append'. */
	strcpy(s, append);
	if (parentNode->decl != SMI_DECL_IMPL_OBJECT) {
	    sprintf(append, " %u%s", parentNode->oid[parentNode->oidlen-1], s);
	} else {
	    sprintf(append, " %s(%u)%s",
		    parentNode->name,
		    parentNode->oid[parentNode->oidlen-1], s);
	}
	
	/* retrieve the parent SmiNode */
	parentNode = smiGetParentNode(parentNode);

	if (!parentNode) {
	    sprintf(s, "%s", append);
	    return s;
	}
	
	/* found an imported or a local parent node? */
	if ((parentNode->decl != SMI_DECL_IMPL_OBJECT) &&
	    ((parentNode->name && strlen(parentNode->name)) &&
	    (smiIsImported(smiModule, NULL, parentNode->name) ||
	     (!importedParent &&
	      (smiGetNodeModule(parentNode) == smiModule)) ||
	     (parentNode->oidlen == 1)))) {
	    sprintf(s, "%s%s", parentNode->name, append);
	    return s;
	}

    } while (parentNode);

    s[0] = 0;
    for (i=0; i < smiNode->oidlen; i++) {
	if (i) strcat(s, " ");
	sprintf(&s[strlen(s)], "%u", smiNode->oid[i]);
    }
    return s;
}



static char *getUppercaseString(char *s)
{
    static char *ss;

    ss = xstrdup(s);
    ss[0] = (char)toupper((int)ss[0]);
    return ss;
}



static int isObjectGroup(SmiNode *groupNode)
{
    SmiNode     *smiNode;
    SmiElement  *smiElement;
    
    for (smiElement = smiGetFirstElement(groupNode); smiElement;
	 smiElement = smiGetNextElement(smiElement)) {

	smiNode = smiGetElementNode(smiElement);
	
	if (smiNode->nodekind != SMI_NODEKIND_SCALAR
	    && smiNode->nodekind != SMI_NODEKIND_COLUMN) {
	    return 0;
	}
    }

    return 1;
}



static int isNotificationGroup(SmiNode *groupNode)
{
    SmiNode     *smiNode;
    SmiElement  *smiElement;
    
    for (smiElement = smiGetFirstElement(groupNode); smiElement;
	 smiElement = smiGetNextElement(smiElement)) {

	smiNode = smiGetElementNode(smiElement);
	
	if (smiNode->nodekind != SMI_NODEKIND_NOTIFICATION) {
	    return 0;
	}
    }

    return 1;
}



static Import* addImport(char *module, char *name)
{
    Import **import, *newImport;
    char   **convertImport;
    int    i;

    convertImport = smiv1 ? convertImportv1 : convertImportv2;

    for (i = 0; convertImport[i]; i += 4) {
	if (convertImport[i] && convertImport[i+1]
	    && !strcmp(module, convertImport[i])
	    && !strcmp(name, convertImport[i+1])) {
	    module = convertImport[i+2];
	    name = convertImport[i+3];
	    break;
	} else if (convertImport[i] && !convertImport[i+1]
		   && !strcmp(module, convertImport[i])) {
	    module = convertImport[i+2];
	    break;
	}
    }

    if (!module || !name) {
	return NULL;
    }
	    
    for (import = &importList; *import; import = &(*import)->nextPtr) {
	int c = strcmp((*import)->module, module);
	if (c < 0) continue;
	if (c == 0) {
	    int d = strcmp((*import)->name, name);
	    if (d < 0) continue;
	    if (d == 0) return *import;
	    if (d > 0) break;
	}
	if (c > 0) break;
    }

    newImport = xmalloc(sizeof(Import));
    if (! newImport) {
	return NULL;
    }
    newImport->module = module;
    newImport->name = name;
    newImport->nextPtr = *import;
    *import = newImport;
	
    return *import;
}



static void createImportList(SmiModule *smiModule)
{
    SmiNode     *smiNode;
    SmiType     *smiType;
    SmiNodekind kind = SMI_NODEKIND_SCALAR | SMI_NODEKIND_COLUMN;
    SmiImport   *smiImport;
    SmiModule	*smiModule2;
    
    if (pibtomib) {
        addImport("TUBS-SMI", "ibrpibtomib");
	for(smiType = smiGetFirstType(smiModule);
	    smiType; smiType = smiGetNextType(smiType)) {
	    if (smiType->basetype == SMI_BASETYPE_UNSIGNED32)
	        addImport("SNMPv2-SMI", "Unsigned32"); 
            else if (smiType->basetype == SMI_BASETYPE_UNSIGNED64)
	        addImport("TUBS-SMI", "IBRUnsigned64"); 
            else if (smiType->basetype == SMI_BASETYPE_INTEGER64)
	        addImport("TUBS-SMI", "IBRInteger64"); 
        }
        if (smiGetFirstNode(smiModule, SMI_NODEKIND_TABLE) != NULL)
            addImport("SNMPv2-TC", "RowStatus");
        for(smiNode = smiGetFirstNode(smiModule, SMI_NODEKIND_ROW);
            smiNode; smiNode = smiGetNextNode(smiNode, SMI_NODEKIND_ROW)) {
            if (smiNode->indexkind == SMI_INDEX_SPARSE) {
                SmiNode *smiNode2 = smiGetRelatedNode(smiNode);
                while (smiNode2 && (smiNode2->indexkind == SMI_INDEX_SPARSE))
                    smiNode2 = smiGetRelatedNode(smiNode2);
                if (smiNode2) {
                    smiModule2 = smiGetNodeModule(smiNode2);
                    if (smiModule != smiModule2)
                        addImport(smiModule2->name, smiNode2->name);
                }
            }
        }
    }
        
    for(smiNode = smiGetFirstNode(smiModule, kind); smiNode;
	smiNode = smiGetNextNode(smiNode, kind)) {
	smiType = smiGetNodeType(smiNode);
	if (smiType && (smiType->decl == SMI_DECL_IMPLICIT_TYPE)) {
	    smiType = smiGetParentType(smiType);
	}
	if (smiType) {
	    smiModule2 = smiGetTypeModule(smiType);
	    if (smiModule2 && (smiModule2 != smiModule)) {
                size_t len = strlen(smiModule2->name);
		if (len && smiType->name) {
                    if (pibtomib && (len > 4) &&
                        !strcmp(&smiModule2->name[len - 4], "-PIB")) {
                        char *newName = xmalloc(len + 5);
                        strcpy(newName, smiModule2->name);
                        strcat(newName, "-MIB");
                        addImport(newName, smiType->name);
                    } else
		        addImport(smiModule2->name, smiType->name);
		}
	    }
	}

	if (pibtomib) {
            if (smiType && (smiType->basetype == SMI_BASETYPE_UNSIGNED32))
	        addImport("SNMPv2-SMI", "Unsigned32");
            if (smiType && (smiType->basetype == SMI_BASETYPE_UNSIGNED64))
	        addImport("TUBS-SMI", "IBRUnsigned64");
            if (smiType && (smiType->basetype == SMI_BASETYPE_INTEGER64))
	        addImport("TUBS-SMI", "IBRInteger64");
	}

	if (! smiv1 &&
	    smiType && smiType->basetype == SMI_BASETYPE_INTEGER32) {
	    addImport("SNMPv2-SMI", "Integer32");
	}

	if ((!smiv1) &&
	    (smiNode->value.basetype == SMI_BASETYPE_OBJECTIDENTIFIER) &&
	    (!strcmp((char *) smiNode->value.value.ptr, "zeroDotZero"))) {
	    addImport("SNMPv2-SMI", "zeroDotZero");
	}
    }

    smiNode = smiGetFirstNode(smiModule,
			      SMI_NODEKIND_SCALAR | SMI_NODEKIND_COLUMN
			      | SMI_NODEKIND_TABLE | SMI_NODEKIND_ROW);
    if (smiNode) {
	addImport(smiv1 ? "RFC-1212" : "SNMPv2-SMI", "OBJECT-TYPE");
    }

    smiNode = smiGetFirstNode(smiModule, SMI_NODEKIND_NOTIFICATION);
    if (smiNode) {
	addImport(smiv1 ? "RFC-1215" : "SNMPv2-SMI",
		  smiv1 ? "TRAP-TYPE" : "NOTIFICATION-TYPE");
    }

    if (! smiv1) {
	smiNode = smiGetModuleIdentityNode(smiModule);
	if (smiNode) {
	    if (strcmp("SNMPv2-SMI", smiModule->name)) {
		addImport("SNMPv2-SMI", "MODULE-IDENTITY");
	    }
	}
    }
    
    if (! smiv1) {
	for(smiNode = smiGetFirstNode(smiModule, SMI_NODEKIND_NODE);
	    smiNode; smiNode = smiGetNextNode(smiNode, SMI_NODEKIND_NODE)) {
	    if (smiNode->status != SMI_STATUS_UNKNOWN &&
		smiNode != smiGetModuleIdentityNode(smiModule)) {
		if (strcmp("SNMPv2-SMI", smiModule->name)) {
		    addImport("SNMPv2-SMI", "OBJECT-IDENTITY");
		}
		break;
	    }
	}
    }

    if (! smiv1) {
	smiNode = smiGetFirstNode(smiModule, SMI_NODEKIND_COMPLIANCE);
	if (smiNode) {
	    if (strcmp("SNMPv2-CONF", smiModule->name)) {
		addImport("SNMPv2-CONF", "MODULE-COMPLIANCE");
	    }
	}
    }

    if (! smiv1) {
	for(smiNode = smiGetFirstNode(smiModule, SMI_NODEKIND_GROUP);
	    smiNode;
	    smiNode = smiGetNextNode(smiNode, SMI_NODEKIND_GROUP)) {
	    if (isObjectGroup(smiNode)) {
		if (strcmp("SNMPv2-CONF", smiModule->name)) {
		    addImport("SNMPv2-CONF", "OBJECT-GROUP");
		}
	    } else if (isNotificationGroup(smiNode)) {
		if (strcmp("SNMPv2-CONF", smiModule->name)) {
		    addImport("SNMPv2-CONF", "NOTIFICATION-GROUP");
		}
	    }
	}
    }

    if (! smiv1) {
	for(smiType = smiGetFirstType(smiModule);
	    smiType; smiType = smiGetNextType(smiType)) {
	    if (smiType->description) {
		if (strcmp("SNMPv2-TC", smiModule->name)) {
		    addImport("SNMPv2-TC", "TEXTUAL-CONVENTION");
		}
		break;
	    }
	}
    }

    for (smiImport = smiGetFirstImport(smiModule); smiImport;
	 smiImport = smiGetNextImport(smiImport)) {
	if (islower((int) smiImport->name[0]) ||
	    (smiImport->module && !strcmp(smiImport->module, "SNMPv2-SMI")) ||
	    (!smiv1 && smiImport->module && !strcmp(smiImport->module, "SNMPv2-TC"))) {
	    addImport(smiImport->module, smiImport->name);
	}
    }
}



static void freeImportList(void)
{
    Import *import, *freeme;

    for (import = importList; import; ) {
	freeme = import;
	import = import->nextPtr;
	xfree(freeme);
    }
    importList = NULL;
}



static void fprint(FILE *f, char *fmt, ...)
{
    va_list ap;
    char    *s;
    char    *p;
    
    va_start(ap, fmt);
    current_column += smiVasprintf(&s, fmt, ap);
    va_end(ap);
    fputs(s, f);
    if ((p = strrchr(s, '\n'))) {
	current_column = strlen(p) - 1;
    }
    free(s);
}



static void fprintSegment(FILE *f, int column, char *string,
			  int length, int comment)
{
    char *format;
    
    if (comment) {
	format = "-- %*c%s";
	/* if (column >= 3) column -= 3; */
    } else {
	format = "%*c%s";
    }
    
    fprint(f, format, column, ' ', string);
    if (length) {
	fprint(f, "%*c", length - strlen(string) - column, ' ');
    }
}



static void fprintWrapped(FILE *f, int column, char *string, int comment)
{
    if ((current_column + strlen(string)) > INDENTMAX) {
	putc('\n', f);
	current_column = 0;
	fprintSegment(f, column, "", 0, comment);
    }
    fprint(f, "%s", string);
}



static void fprintMultilineString(FILE *f, const char *s, const int comment)
{
    int i, len;
    
    fprintSegment(f, INDENTTEXTS - 1, "\"", 0, comment);
    if (s) {
	len = strlen(s);
	for (i=0; i < len; i++) {
	    putc(s[i], f);
	    current_column++;
	    if (s[i] == '\n') {
		current_column = 0;
		fprintSegment(f, INDENTTEXTS, "", 0, comment);
	    }
	}
    }
    putc('\"', f);
    current_column++;
}



static void fprintMultilineString2(FILE *f, const char *s, const int comment)
{
    int i, len;
    
    fprintSegment(f, INDENTTEXTS2 - 1, "\"", 0, comment);
    if (s) {
	len = strlen(s);
	for (i=0; i < len; i++) {
	    putc(s[i], f);
	    current_column++;
	    if (s[i] == '\n') {
		current_column = 0;
		fprintSegment(f, INDENTTEXTS2, "", 0, comment);
	    }
	}
    }
    putc('\"', f);
    current_column++;
}



static void fprintSubtype(FILE *f, SmiType *smiType, const int comment)
{
    SmiRange       *range;
    SmiNamedNumber *nn;
    char	   s[1024];
    int		   i, c = comment;

    if (pibtomib && ((smiType->basetype == SMI_BASETYPE_UNSIGNED64) ||
        (smiType->basetype == SMI_BASETYPE_INTEGER64))) {
        c = 1;
        fprint(f, " -- commented out by smidump\n-- ");
    }
    c = c || (smiv1 && smiType->basetype == SMI_BASETYPE_BITS);
    
    if ((smiType->basetype == SMI_BASETYPE_ENUM) ||
	(smiType->basetype == SMI_BASETYPE_BITS)) {
	for(i = 0, nn = smiGetFirstNamedNumber(smiType);
	    nn; i++, nn = smiGetNextNamedNumber(nn)) {
	    if (i) {
		fprint(f, ", ");
	    } else {
		if (c) {
		    fprint(f, "\n");
		    fprintSegment(f, INDENT, "", INDENTVALUE, c);
		    fprint(f, "  { ");
		} else {
		    fprint(f, " { ");
		}
	    }
	    sprintf(s, "%s(%s)", nn->name,
		    smiValueAsString(&nn->value, smiType,
			     smiv1 ? SMI_LANGUAGE_SMIV1 : SMI_LANGUAGE_SMIV2));
	    fprintWrapped(f, INDENTVALUE + INDENT, s, c);
	}
	if (i) {
	    fprint(f, " }");
	}
    } else {
	for(i = 0, range = smiGetFirstRange(smiType);
	    range; i++, range = smiGetNextRange(range)) {
	    if (i) {
		fprint(f, " | ");
	    } else {
		if (smiType->basetype == SMI_BASETYPE_OCTETSTRING) {
		    fprint(f, " (SIZE(");
		} else {
		    fprint(f, " (");
		}
	    }	    
	    if (memcmp(&range->minValue, &range->maxValue,
		       sizeof(SmiValue))) {
		sprintf(s, "%s",
			smiValueAsString(&range->minValue, smiType,
			    smiv1 ? SMI_LANGUAGE_SMIV1 : SMI_LANGUAGE_SMIV2));
		sprintf(&s[strlen(s)], "..%s", 
			smiValueAsString(&range->maxValue, smiType,
                            smiv1 ? SMI_LANGUAGE_SMIV1 : SMI_LANGUAGE_SMIV2));
	    } else {
		sprintf(s, "%s",
			smiValueAsString(&range->minValue, smiType,
			    smiv1 ? SMI_LANGUAGE_SMIV1 : SMI_LANGUAGE_SMIV2));
	    }
	    fprintWrapped(f, INDENTVALUE + INDENT, s, 0);
	}
	if (i) {
	    if (smiType->basetype == SMI_BASETYPE_OCTETSTRING) {
		fprint(f, ")");
	    }
	    fprint(f, ")");
	}
    }
}



static void fprintIndex(FILE *f, SmiNode *indexNode, const int comment)
{
    SmiElement *smiElement, *smiFirstElement;
    int        n, j;

    for (n = 0, smiElement = smiGetFirstElement(indexNode); smiElement;
	 n++, smiElement = smiGetNextElement(smiElement));
    if (pibtomib && (n > 1))
        smiFirstElement = smiGetNextElement(smiGetFirstElement(indexNode));
    else
        smiFirstElement = smiGetFirstElement(indexNode);
    fprintSegment(f, INDENT, "INDEX", INDENTVALUE, comment);
    fprint(f, "{ ");
    for (j = 0, smiElement = smiFirstElement; smiElement;
	 j++, smiElement = smiGetNextElement(smiElement)) {
	if (j) {
	    fprint(f, ", ");
	}
	if (indexNode->implied && ((j+1) == n)) {
	    fprintWrapped(f, INDENTVALUE + 2, "IMPLIED ", 0);
	}
	fprintWrapped(f, INDENTVALUE + 2,
		      smiGetElementNode(smiElement)->name, 0);
	/* TODO: non-local name if non-local */
    } /* TODO: empty? -> print error */
    fprint(f, " }\n");
}



static void fprintImports(FILE *f)
{
    Import        *import;
    char	  *lastModulename = NULL;
    char	  *importedModulename, *importedDescriptor;
    int		  i;
    char	  **convertImport;

    convertImport = smiv1 ? convertImportv1 : convertImportv2;

    for(import = importList; import; import = import->nextPtr) {
	importedModulename = import->module;
	importedDescriptor = import->name;

	if (!strlen(importedModulename))
	    continue;
	
	for(i = 0; convertImport[i]; i += 4) {
	    if (convertImport[i] && convertImport[i+1]
		&& !strcmp(importedModulename, convertImport[i])
		&& !strcmp(importedDescriptor, convertImport[i+1])) {
		importedModulename = convertImport[i+2];
		importedDescriptor = convertImport[i+3];
		break;
	    } else if (convertImport[i] && !convertImport[i+1]
		       && !strcmp(importedModulename, convertImport[i])) {
		importedModulename = convertImport[i+2];
		break;
	    }
	}

	if (importedModulename && importedDescriptor &&
	    strlen(importedDescriptor)) {
	    if ((!lastModulename) ||
		strcmp(importedModulename, lastModulename)) {
		if (!lastModulename) {
		    fprint(f, "IMPORTS");
		} else {
		    fprint(f, "\n");
		    fprintSegment(f, 2 * INDENT, "", 0, 0);
		    fprint(f, "FROM %s", lastModulename);
		}
		fprint(f, "\n");
		fprintSegment(f, INDENT, "", 0, 0);
	    } else {
		fprint(f, ", ");
	    }
	    fprintWrapped(f, INDENT, importedDescriptor, 0);
	    lastModulename = importedModulename;
	}
    }
    if (lastModulename) {
	fprint(f, "\n");
	fprintSegment(f, 2 * INDENT, "", 0, 0);
	fprint(f, "FROM %s;\n\n", lastModulename);
    }
}



static void fprintModuleIdentity(FILE *f, SmiModule *smiModule)
{
    SmiRevision  *smiRevision;
    SmiNode      *smiNode;

    smiNode = smiGetModuleIdentityNode(smiModule);
    
    if (smiNode) {

	if (smiv1 && smiNode) {
	    fprint(f, "%s OBJECT IDENTIFIER\n", smiNode->name);
	    fprintSegment(f, INDENT, "::= ", 0, 0);
	    fprint(f, "{ %s }\n\n", getOidString(smiNode, 0));
	}

	if (! smiv1 || ! silent) {

	    if (smiv1) {
		fprint(f, "-- %s MODULE-IDENTITY\n", smiNode->name);
	    } else {
		fprint(f, "%s MODULE-IDENTITY\n", smiNode->name);
	    }
	    fprintSegment(f, INDENT, "LAST-UPDATED", INDENTVALUE, smiv1);
	    smiRevision = smiGetFirstRevision(smiModule);
	    if (smiRevision)
		fprint(f, "\"%s\"\n", getTimeString(smiRevision->date));
	    else
		fprint(f, "\"197001010000Z\"\n");
	    fprintSegment(f, INDENT, "ORGANIZATION", INDENTVALUE, smiv1);
	    fprint(f, "\n");
	    fprintMultilineString(f, smiModule->organization, smiv1);
	    fprint(f, "\n");
	    fprintSegment(f, INDENT, "CONTACT-INFO", INDENTVALUE, smiv1);
	    fprint(f, "\n");
	    fprintMultilineString(f, smiModule->contactinfo, smiv1);
	    fprint(f, "\n");
	    fprintSegment(f, INDENT, "DESCRIPTION", INDENTVALUE, smiv1);
	    fprint(f, "\n");
	    if (smiModule->description) {
		fprintMultilineString(f, smiModule->description, smiv1);
	    } else {
		fprintMultilineString(f, "...", smiv1);
	    }
	    fprint(f, "\n");
	    
	    for(; smiRevision;
		smiRevision = smiGetNextRevision(smiRevision)) {
		if (!smiRevision->description
		    || strcmp(smiRevision->description,
	   "[Revision added by libsmi due to a LAST-UPDATED clause.]")) {
		    fprintSegment(f, INDENT, "REVISION", INDENTVALUE, smiv1);
		    fprint(f, "\"%s\"\n", getTimeString(smiRevision->date));
		    fprintSegment(f, INDENT, "DESCRIPTION", INDENTVALUE,
				  smiv1);
		    fprint(f, "\n");
		    if (smiRevision->description) {
			fprintMultilineString(f, smiRevision->description,
					      smiv1);
		    } else {
			fprintMultilineString(f, "...", smiv1);
		    }
		    fprint(f, "\n");
		}
	    }
	    
	    if (smiNode) {
		fprintSegment(f, INDENT, "::= ", 0, smiv1);
                if (!pibtomib)
		    fprint(f, "{ %s }\n\n", getOidString(smiNode, 0));
                else
                    fprint(f, "{ ibrpibtomib %d }\n\n",
                           smiNode->oid[smiNode->oidlen - 1]);
	    }
	    /* TODO: else error */
	    
	    fprint(f, "\n");
	}
    }
}



static void fprintTypeDefinition(FILE *f, SmiType *smiType)
{
    int		 invalid;

    if (smiType->status == SMI_STATUS_UNKNOWN) {
	invalid = invalidType(smiType->basetype);
	if (invalid) {
	    fprint(f, "-- %s ::=\n", smiType->name);
	} else {
	    fprint(f, "%s ::=\n", smiType->name);
	}
	fprintSegment(f, INDENT, "", 0, invalid);
	fprint(f, "%s", getTypeString(smiType->basetype,
				      smiGetParentType(smiType)));
	fprintSubtype(f, smiType, invalid);
	fprint(f, "\n\n");
    }
}



static void fprintTypeDefinitions(FILE *f, SmiModule *smiModule)
{
    SmiType	 *smiType;

    for(smiType = smiGetFirstType(smiModule);
	smiType; smiType = smiGetNextType(smiType)) {
	fprintTypeDefinition(f, smiType);
    }
}



static void fprintTextualConvention(FILE *f, SmiType *smiType)
{
    SmiType      *baseType;
    SmiModule    *baseModule;
    int		 i, invalid;

    char *smiv2basetypes[] = {
	"SNMPv2-SMI", "IpAddress",
	"SNMPv2-SMI", "Counter32",
	"SNMPv2-SMI", "Gauge32",
	"SNMPv2-SMI", "TimeTicks",    
	"SNMPv2-SMI", "Counter64",
	NULL, NULL
    };
    
    if (smiType->status != SMI_STATUS_UNKNOWN) {
	invalid = invalidType(smiType->basetype);
	if (smiv1 && !invalid) {
	    fprint(f, "%s ::=\n", smiType->name);
	    fprintSegment(f, INDENT, "", 0, invalid);
	    fprint(f, "%s", getTypeString(smiType->basetype,
					  smiGetParentType(smiType)));
	    fprintSubtype(f, smiType, invalid);
	    fprint(f, "\n\n");
	}
	
	if (! smiv1 || ! silent) {
	    
	    if (smiv1 || invalid) {
		fprint(f, "-- %s ::= TEXTUAL-CONVENTION\n", smiType->name);
	    } else {
		fprint(f, "%s ::= TEXTUAL-CONVENTION\n", smiType->name);
	    }
	    
	    if (smiType->format) {
		fprintSegment(f, INDENT, "DISPLAY-HINT", INDENTVALUE,
			      smiv1 || invalid);
		fprint(f, "\"%s\"\n", smiType->format);
	    }
	    
	    fprintSegment(f, INDENT, "STATUS", INDENTVALUE,
			  smiv1 || invalid);
	    fprint(f, "%s\n", getStatusString(smiType->status));
	    
	    fprintSegment(f, INDENT, "DESCRIPTION", INDENTVALUE,
			  smiv1 || invalid);
	    fprint(f, "\n");
	    if (smiType->description) {
		fprintMultilineString(f, smiType->description,
				      smiv1 || invalid);
	    } else {
		fprintMultilineString(f, "...", smiv1 || invalid);
	    }
	    fprint(f, "\n");
	    
	    if (smiType->reference) {
		fprintSegment(f, INDENT, "REFERENCE", INDENTVALUE,
			      smiv1 || invalid);
		fprint(f, "\n");
		fprintMultilineString(f, smiType->reference,
				      smiv1 || invalid);
		fprint(f, "\n");
	    }
	    
	    for (baseType = smiGetParentType(smiType);
		 baseType;
		 baseType = smiGetParentType(baseType)) {
		SmiType *parent = smiGetParentType(baseType);

		baseModule = smiGetTypeModule(baseType);
		if (baseModule && baseModule->name) {
		    for (i = 0; smiv2basetypes[i]; i += 2) {
			if (strcmp(smiv2basetypes[i+1], baseType->name) == 0
			    && strcmp(smiv2basetypes[i], baseModule->name) == 0) {
			    parent = NULL;
			}
		    }
		}
		
		fprintSegment(f, INDENT, "SYNTAX", INDENTVALUE,
			      smiv1 || invalid || parent);
		fprint(f, "%s",
		       getTypeString(baseType->basetype, baseType));
		fprintSubtype(f, smiType, smiv1 || invalid || parent);
		if (parent) {
		    fprintf(f, "\n");
		}
		if (!parent) break;
	    }
	    fprint(f, "\n\n");
	}
    }
}



static void fprintTextualConventions(FILE *f, SmiModule *smiModule)
{
    SmiType	 *smiType;
    
    for(smiType = smiGetFirstType(smiModule);
	smiType; smiType = smiGetNextType(smiType)) {
	fprintTextualConvention(f, smiType);
    }
}


static int isInIndex(SmiNode *node, SmiNode *parentNode)
{
    SmiElement *element;
    
    if ((parentNode->indexkind != SMI_INDEX_INDEX) &&
        (parentNode->indexkind != SMI_INDEX_EXPAND))
        return 0;
    
    for (element = smiGetNextElement(smiGetFirstElement(parentNode)); element;
         element = smiGetNextElement(element))
        if (smiGetElementNode(element)->name &&
            node->name &&
            !strcmp(smiGetElementNode(element)->name, node->name))
            return 1;
    return 0;
}


static void fprintObjects(FILE *f, SmiModule *smiModule)
{
    SmiNode	 *smiNode, *rowNode, *colNode, *smiParentNode, *relatedNode;
    SmiType	 *smiType;
    SmiNodekind  nodekinds;
    int		 i, invalid, create, assignement, indentsequence;
    
    nodekinds =  SMI_NODEKIND_NODE | SMI_NODEKIND_TABLE |
	SMI_NODEKIND_ROW | SMI_NODEKIND_COLUMN | SMI_NODEKIND_SCALAR |
	SMI_NODEKIND_CAPABILITIES;
    
    for(smiNode = smiGetFirstNode(smiModule, nodekinds);
	smiNode; smiNode = smiGetNextNode(smiNode, nodekinds)) {

	smiType = smiGetNodeType(smiNode);
	smiParentNode = smiGetParentNode(smiNode);
	
	create = smiParentNode ? smiParentNode->create : 0;
	
	invalid = !smiType ? 0 : invalidType(smiType->basetype);
	assignement = 0;

	if (invalid && silent
	    && (smiNode->nodekind == SMI_NODEKIND_SCALAR
		|| smiNode->nodekind == SMI_NODEKIND_COLUMN)) {
	    continue;
	}

	if (smiNode == smiGetModuleIdentityNode(smiModule)) {
	    continue;
	}
	
	if ((smiNode->nodekind == SMI_NODEKIND_NODE) &&
	    (smiNode->status == SMI_STATUS_UNKNOWN)) {
	    assignement = 1;
	    fprint(f, "%s OBJECT IDENTIFIER\n", smiNode->name);
	} else if (smiNode->nodekind == SMI_NODEKIND_NODE) {
	    if (smiv1) {
		assignement = 1;
		fprint(f, "%s OBJECT IDENTIFIER\n", smiNode->name);
	    } else {
		fprint(f, "%s OBJECT-IDENTITY\n", smiNode->name);
	    }
	} else if (smiNode->nodekind == SMI_NODEKIND_CAPABILITIES) {
	    /* TODO: real agent capabilities */
	    fprint(f, "-- This has been an SMIv2 AGENT-CAPABILITIES node:\n");
	    if (smiv1) {
		assignement = 1;
		fprint(f, "%s OBJECT IDENTIFIER\n", smiNode->name);
	    } else {
		fprint(f, "%s OBJECT-IDENTITY\n", smiNode->name);
	    }
	} else {
	    if (invalid) {
		fprint(f, "-- %s OBJECT-TYPE\n", smiNode->name);
	    } else {
		fprint(f, "%s OBJECT-TYPE\n", smiNode->name);
	    }
	}

	if ((smiNode->nodekind == SMI_NODEKIND_TABLE) ||
	    (smiNode->nodekind == SMI_NODEKIND_ROW) ||
	    (smiType)) {
	    fprintSegment(f, INDENT, "SYNTAX", INDENTVALUE, invalid);
	    if (smiNode->nodekind == SMI_NODEKIND_TABLE) {
		fprint(f, "SEQUENCE OF ");
		rowNode = smiGetFirstChildNode(smiNode);
		if (rowNode) {
		    smiType = smiGetNodeType(rowNode);
		    if (smiType) {
			fprint(f, "%s\n", smiType->name);
		    } else {
			/* guess type name is uppercase row name */
			char *s = getUppercaseString(rowNode->name);
			fprint(f, "%s\n", s);
			xfree(s);
		    }
		    /* TODO: print non-local name qualified */
		} else {
		    fprint(f, "<unknown>\n");
		}
	    } else if (smiNode->nodekind == SMI_NODEKIND_ROW) {
		if (smiType) {
		    fprint(f, "%s\n", smiType->name);
		} else {
		    char *s = getUppercaseString(smiNode->name);
		    /* guess type name is uppercase row name */
		    fprint(f, "%s\n", s);
		    xfree(s);
		}
		/* TODO: print non-local name qualified */
	    } else if (smiType) {
		if (!smiType->name) {
		    /*
		     * an implicitly restricted type.
		     */
		    fprint(f, "%s", getTypeString(smiType->basetype,
						  smiGetParentType(smiType)));
		    fprintSubtype(f, smiType, invalid);
		    fprint(f, "\n");
		} else {
		    fprint(f, "%s\n",
			   getTypeString(smiType->basetype, smiType));
		}
	    }
	}

	if (! assignement && smiNode->units) {
	    fprintSegment(f, INDENT, "UNITS", INDENTVALUE, smiv1 || invalid);
	    fprint(f, "\"%s\"\n", smiNode->units);
	}
	
	if (! assignement &&
	    smiNode->nodekind != SMI_NODEKIND_CAPABILITIES &&
	    smiNode->nodekind != SMI_NODEKIND_NODE) {
	    if (smiv1) {
		fprintSegment(f, INDENT, "ACCESS", INDENTVALUE, invalid);
	    } else {
 		fprintSegment(f, INDENT, "MAX-ACCESS", INDENTVALUE, 0);
	    }
            if (pibtomib) {
                if ((smiNode->nodekind == SMI_NODEKIND_TABLE) ||
                    (smiNode->nodekind == SMI_NODEKIND_ROW))
                    fprint(f, "not-accessible\n");
                else if ((smiNode->nodekind == SMI_NODEKIND_COLUMN) &&
                         isInIndex(smiNode, smiParentNode))
	            fprint(f, "not-accessible\n");
                else if (smiNode->nodekind == SMI_NODEKIND_COLUMN)
                    fprint(f, "read-create\n");
                else
	            fprint(f, "%s\n", getAccessString(smiNode->access, create));
            } else
	        fprint(f, "%s\n", getAccessString(smiNode->access, create));
	}

	if (! assignement) {
	    fprintSegment(f, INDENT, "STATUS", INDENTVALUE, invalid);
	    fprint(f, "%s\n", getStatusString(smiNode->status));
	}
	
	if (! assignement) {
	    fprintSegment(f, INDENT, "DESCRIPTION", INDENTVALUE, invalid);
	    fprint(f, "\n");
	    if (smiNode->description) {
		fprintMultilineString(f, smiNode->description, invalid);
	    } else {
		fprintMultilineString(f, "...", invalid);
	    }
	    fprint(f, "\n");
	}
	    
	if (! assignement && smiNode->reference) {
	    fprintSegment(f, INDENT, "REFERENCE", INDENTVALUE,
			  smiv1 || invalid);
	    fprint(f, "\n");
	    fprintMultilineString(f, smiNode->reference, smiv1 || invalid);
	    fprint(f, "\n");
	}

	relatedNode = smiGetRelatedNode(smiNode);
	switch (smiNode->indexkind) {
	case SMI_INDEX_INDEX:
	case SMI_INDEX_REORDER:
	    fprintIndex(f, smiNode, invalid);
	    break;
	case SMI_INDEX_EXPAND:  /* TODO: we have to do more work here! */
	    fprintIndex(f, smiNode, invalid);
	    break;
	case SMI_INDEX_AUGMENT:
	    if (smiv1 && ! invalid) {
		if (relatedNode) {
		    fprintIndex(f, relatedNode, invalid);
		}
	    }
	    if ((! smiv1 || ! silent) && relatedNode) {
		fprintSegment(f, INDENT, "AUGMENTS", INDENTVALUE,
			      smiv1 || invalid);
		fprint(f, "{ %s }\n", relatedNode->name);
	    }
	    break;
	case SMI_INDEX_SPARSE:
	    if (! invalid) {
                while (relatedNode &&
                       (relatedNode->indexkind == SMI_INDEX_SPARSE))
                    relatedNode = smiGetRelatedNode(relatedNode);
		if (relatedNode) {
		    fprintIndex(f, relatedNode, invalid);
		}
	    }
	    /* TODO: non-local name if non-local */
	    break;
	case SMI_INDEX_UNKNOWN:
	    break;
	}
	
	if (smiNode->value.basetype != SMI_BASETYPE_UNKNOWN) {
	    fprintSegment(f, INDENT, "DEFVAL", INDENTVALUE, invalid);
	    fprint(f, "{ %s }",
		   smiValueAsString(&smiNode->value, smiType,
			    smiv1 ? SMI_LANGUAGE_SMIV1 : SMI_LANGUAGE_SMIV2));
	    fprint(f, "\n");
	}

	fprintSegment(f, INDENT, "::= ", 0, invalid);
	fprint(f, "{ %s }\n\n", getOidString(smiNode, 0));

	smiType = smiGetNodeType(smiNode);
	if (smiNode->nodekind == SMI_NODEKIND_ROW) {
 	    if (smiType) {
		fprint(f, "%s ::= SEQUENCE {", smiType->name);
	    } else {
		/* guess type name is uppercase row name */
		char *s = getUppercaseString(smiNode->name);
		fprint(f, "%s ::= SEQUENCE {", s);
		xfree(s);
	    }
	    /* Find the last valid node in this sequence. We need it
	     * to suppress its trailing comma. Compute the longest
	     * column name so that we can adjust the indentation of
	     * the type names in the SEQUENCE definition. */
	    for(indentsequence = 0, colNode = smiGetFirstChildNode(smiNode);
		colNode;
		colNode = smiGetNextChildNode(colNode)) {
		int len = strlen(colNode->name);
		if (len > indentsequence) indentsequence = len;
		smiType = smiGetNodeType(colNode);
		if (smiType && !invalidType(smiType->basetype)) {
		    relatedNode = colNode;
		}
	    }
            if (pibtomib) {
                int len = strlen(smiParentNode->name) + 9;
                if (len > 64)
                  len = 64;
                indentsequence = len;
            }
	    if (relatedNode) relatedNode = smiGetNextChildNode(relatedNode);
	    indentsequence = (2*INDENT + indentsequence + 1) / INDENT * INDENT;
	    /* TODO: non-local name? */
	    for(i = 0, invalid = 0, colNode = smiGetFirstChildNode(smiNode);
		colNode;
		colNode = smiGetNextChildNode(colNode)) {
		if (! invalid || ! silent) {
		    if (i && (relatedNode != colNode)) {
			fprint(f, ",");
		    } 
		    fprint(f, "\n");
		}
		
		smiType = smiGetNodeType(colNode);
		invalid = (smiType == NULL) || invalidType(smiType->basetype);

		if (! invalid || ! silent) {
		    fprintSegment(f, INDENT, colNode->name, indentsequence,
				  invalid);
		    if (smiType && smiType->decl == SMI_DECL_IMPLICIT_TYPE) {
			fprint(f, "%s", getTypeString(smiType->basetype,
					      smiGetParentType(smiType)));
		    } else if (smiType) {
			fprint(f, "%s", getTypeString(smiType->basetype,
					      smiGetNodeType(colNode)));
		    } else {
			fprint(f, "<unknown>");
		    }
		}
		i++;
	    }
            if (pibtomib) {
                size_t len = strlen(smiParentNode->name);
                int maxid;
                char *rowStatus = xmalloc(len + 10);
                strcpy(rowStatus, smiParentNode->name);
                if (len > 55)
                  len = 55;
                strcpy(&rowStatus[len], "RowStatus");
                fprint(f, ",\n");
                fprintSegment(f, INDENT, rowStatus, indentsequence, 0);
                fprint(f, "RowStatus\n}\n\n");
                
                fprint(f, "%s OBJECT-TYPE\n", rowStatus);
	        fprintSegment(f, INDENT, "SYNTAX", INDENTVALUE, 0);
                fprint(f, "RowStatus\n");
                fprintSegment(f, INDENT, "MAX-ACCESS", INDENTVALUE, 0);
                fprint(f, "read-create\n");
                fprintSegment(f, INDENT, "STATUS", INDENTVALUE, 0);
                fprint(f, "current\n");
                fprintSegment(f, INDENT, "DESCRIPTION", INDENTVALUE, 0);
                fprint(f, "\n");
		fprintMultilineString(f, "Added by smidump for automatic " \
                                      "PIB to MIB conversion.", 0);
                fprint(f, "\n");
	        fprintSegment(f, INDENT, "::= ", 0, 0);
                for (maxid = 0, colNode = smiGetFirstChildNode(smiNode);
                     colNode; colNode = smiGetNextChildNode(colNode))
                     if (colNode->oidlen &&
                         (colNode->oid[colNode->oidlen - 1] > maxid))
                         maxid = colNode->oid[colNode->oidlen - 1];
	        fprint(f, "{ %s %d }\n\n",
                       smiGetFirstChildNode(smiParentNode)->name, 
                       (maxid + 1) > 128 ? (maxid + 1) : 128);
                xfree(rowStatus);
            } else
 	        fprint(f, "\n}\n\n");
	}
    }
}



static void fprintNotifications(FILE *f, SmiModule *smiModule)
{
    SmiNode	 *smiNode, *parentNode;
    SmiElement   *smiElement;
    int          j;
    
    for(smiNode = smiGetFirstNode(smiModule, SMI_NODEKIND_NOTIFICATION);
	smiNode;
	smiNode = smiGetNextNode(smiNode, SMI_NODEKIND_NOTIFICATION)) {

	if (smiv1) {
	    fprint(f, "%s TRAP-TYPE\n", smiNode->name);
	    parentNode = smiGetParentNode(smiNode);
	    while ((parentNode->oidlen > 0) &&
		   ((!parentNode->name) ||
		    (!parentNode->oid[parentNode->oidlen-1]))) {
		parentNode = smiGetParentNode(parentNode);
	    }
	    fprintSegment(f, INDENT, "ENTERPRISE", INDENTVALUE, 0);
	    fprint(f, "%s\n", parentNode->name);
	} else {
	    fprint(f, "%s NOTIFICATION-TYPE\n", smiNode->name);
	}

	if ((smiElement = smiGetFirstElement(smiNode))) {
	    if (smiv1) {
		fprintSegment(f, INDENT, "VARIABLES", INDENTVALUE, 0);
	    } else {
		fprintSegment(f, INDENT, "OBJECTS", INDENTVALUE, 0);
	    }
	    fprint(f, "{ ");
	    for (j = 0; smiElement;
		 j++, smiElement = smiGetNextElement(smiElement)) {
		if (j) {
		    fprint(f, ", ");
		}
		fprintWrapped(f, INDENTVALUE + 2,
			      smiGetElementNode(smiElement)->name, 0);
		/* TODO: non-local name if non-local */
	    } /* TODO: empty? -> print error */
	    fprint(f, " }\n");
	}

	if (! smiv1 || ! silent) {
	    fprintSegment(f, INDENT, "STATUS", INDENTVALUE, smiv1);
	    fprint(f, "%s\n", getStatusString(smiNode->status));
	}

	fprintSegment(f, INDENT, "DESCRIPTION", INDENTVALUE, 0);
	fprint(f, "\n");
	if (smiNode->description) {
	    fprintMultilineString(f, smiNode->description, 0);
	} else {
	    fprintMultilineString(f, "...", 0);
	}
	fprint(f, "\n");
	    
	if (smiNode->reference) {
	    fprintSegment(f, INDENT, "REFERENCE", INDENTVALUE, smiv1);
	    fprint(f, "\n");
	    fprintMultilineString(f, smiNode->reference, smiv1);
	    fprint(f, "\n");
	}

	fprintSegment(f, INDENT, "::= ", 0, 0);
	if (smiv1) {
	    fprint(f, "%d\n\n", smiNode->oid[smiNode->oidlen-1]);
	} else {
	    fprint(f, "{ %s }\n\n", getOidString(smiNode, 0));
	}
    }
}



static void fprintGroups(FILE *f, SmiModule *smiModule)
{
    SmiNode	 *smiNode;
    SmiElement   *smiElement;
    int          j, objectGroup = 0, notificationGroup = 0;
    
    for (smiNode = smiGetFirstNode(smiModule, SMI_NODEKIND_GROUP);
	 smiNode; smiNode = smiGetNextNode(smiNode, SMI_NODEKIND_GROUP)) {

	objectGroup = isObjectGroup(smiNode);
	if (! objectGroup) {
	    notificationGroup = isNotificationGroup(smiNode);
	}

	if (smiv1 || (!objectGroup && !notificationGroup)) {
	    fprint(f, "%s OBJECT IDENTIFIER\n", smiNode->name);
	    fprintSegment(f, INDENT, "::= ", 0, 0);
	    fprint(f, "{ %s }\n\n", getOidString(smiNode, 0));
	}

	if (!objectGroup && !notificationGroup) {
	    continue;
	}

	if (! smiv1 || ! silent) {

	    if (smiv1) {
		fprint(f, "-- %s %s\n", smiNode->name,
		       objectGroup ? "OBJECT-GROUP" : "NOTIFICATION-GROUP");
	    } else {
		fprint(f, "%s %s\n", smiNode->name,
		       objectGroup ? "OBJECT-GROUP" : "NOTIFICATION-GROUP");
	    }

	    if (objectGroup) {
		fprintSegment(f, INDENT, "OBJECTS", INDENTVALUE, smiv1);
	    } else {
		fprintSegment(f, INDENT, "NOTIFICATIONS", INDENTVALUE, smiv1);
	    }
	    fprint(f, "{ ");
	    for (j = 0, smiElement = smiGetFirstElement(smiNode);
		 smiElement;
		 j++, smiElement = smiGetNextElement(smiElement)) {
		if (j) {
		    fprint(f, ", ");
		}
		fprintWrapped(f, INDENTVALUE + 2,
			      smiGetElementNode(smiElement)->name, smiv1);
		/* TODO: non-local name if non-local */
	    } /* TODO: empty? -> print error */
	    fprint(f, " }\n");
	    
	    fprintSegment(f, INDENT, "STATUS", INDENTVALUE, smiv1);
	    fprint(f, "%s\n", getStatusString(smiNode->status));
	    
	    fprintSegment(f, INDENT, "DESCRIPTION", INDENTVALUE, smiv1);
	    fprint(f, "\n");
	    if (smiNode->description) {
		fprintMultilineString(f, smiNode->description, smiv1);
	    } else {
		fprintMultilineString(f, "...", smiv1);
	    }
	    fprint(f, "\n");
	    
	    if (smiNode->reference) {
		fprintSegment(f, INDENT, "REFERENCE", INDENTVALUE, smiv1);
		fprint(f, "\n");
		fprintMultilineString(f, smiNode->reference, smiv1);
		fprint(f, "\n");
	    }
	    
	    fprintSegment(f, INDENT, "::= ", 0, smiv1);
	    fprint(f, "{ %s }\n\n", getOidString(smiNode, 0));
	}
    }
}



static void fprintModuleCompliances(FILE *f, SmiModule *smiModule)
{
    SmiNode	  *smiNode, *smiNode2;
    SmiModule     *smiModule2;
    SmiType	  *smiType;
    SmiOption	  *smiOption;
    SmiRefinement *smiRefinement;
    SmiElement    *smiElement;
    char	  *module;
    char	  *done = NULL; /* "+" separated list of module names */
    char	  s[1024];
    int		  j;

    for (smiNode = smiGetFirstNode(smiModule, SMI_NODEKIND_COMPLIANCE);
	 smiNode; smiNode = smiGetNextNode(smiNode, SMI_NODEKIND_COMPLIANCE)) {
	
	if (smiv1) {
	    fprint(f, "%s OBJECT IDENTIFIER\n", smiNode->name);
	    fprintSegment(f, INDENT, "::= ", 0, 0);
	    fprint(f, "{ %s }\n\n", getOidString(smiNode, 0));
	}

	if (! smiv1 || ! silent) {

	    if (smiv1) {
		fprint(f, "-- %s MODULE-COMPLIANCE\n", smiNode->name);
	    } else {
		fprint(f, "%s MODULE-COMPLIANCE\n", smiNode->name);
	    }
	    
	    fprintSegment(f, INDENT, "STATUS", INDENTVALUE, smiv1);
	    fprint(f, "%s\n", getStatusString(smiNode->status));
	    
	    fprintSegment(f, INDENT, "DESCRIPTION", INDENTVALUE, smiv1);
	    fprint(f, "\n");
	    if (smiNode->description) {
		fprintMultilineString(f, smiNode->description, smiv1);
	    } else {
		fprintMultilineString(f, "...", smiv1);
	    }
	    fprint(f, "\n");
	    
	    if (smiNode->reference) {
		fprintSegment(f, INDENT, "REFERENCE", INDENTVALUE, smiv1);
		fprint(f, "\n");
		fprintMultilineString(f, smiNode->reference, smiv1);
		fprint(f, "\n");
	    }
	    
	    /* `this module' always first */
	    done = xstrdup("+");
	    for (module = smiModule->name; module; ) {
		
		fprint(f, "\n");
		fprintSegment(f, INDENT, "MODULE", INDENTVALUE, smiv1);
		if (strlen(module) && strcmp(smiModule->name, module)) {
		    fprint(f, "%s\n", module);
		} else {
		    if (smiv1) {
			fprint(f, "-- -- this module\n");
		    } else {
			fprint(f, "-- this module\n");
		    }
		}
		
		for (j = 0, smiElement = smiGetFirstElement(smiNode);
		     smiElement;
		     smiElement = smiGetNextElement(smiElement)) {
		    if (!strcmp(smiGetNodeModule(smiGetElementNode(smiElement))->name, module)) {
			if (j) {
			    fprint(f, ", ");
			} else {
			    fprint(f, "\n");
			    fprintSegment(f, 2 * INDENT, "MANDATORY-GROUPS",
					  INDENTVALUE, smiv1);
			    fprint(f, "{ ");
			}
			j++;
			fprintWrapped(f, INDENTVALUE + 2,
				      smiGetElementNode(smiElement)->name,
				      smiv1);
		    }
		}
		if (j) {
		    fprint(f, " }\n");
		}
		
		for(smiOption = smiGetFirstOption(smiNode); smiOption;
		    smiOption = smiGetNextOption(smiOption)) {
		    smiNode2 = smiGetOptionNode(smiOption);
		    smiModule2 = smiGetNodeModule(smiNode2);
		    if (!strcmp(smiModule2->name, module)) {
			fprint(f, "\n");
			fprintSegment(f, 2 * INDENT, "GROUP",
				      INDENTVALUE, smiv1);
			fprint(f, "%s\n", smiNode2->name);
			fprintSegment(f, 2 * INDENT, "DESCRIPTION",
				      INDENTVALUE, smiv1);
			fprint(f, "\n");
			if (smiOption->description) {
			    fprintMultilineString2(f, smiOption->description,
						  smiv1);
			} else {
			    fprintMultilineString2(f, "...", smiv1);
			}
			fprint(f, "\n");
		    }
		}
		
		for(smiRefinement = smiGetFirstRefinement(smiNode);
		    smiRefinement;
		    smiRefinement = smiGetNextRefinement(smiRefinement)) {
		    smiNode2 = smiGetRefinementNode(smiRefinement);
		    smiModule2 = smiGetNodeModule(smiNode2);
		    if (!strcmp(smiModule2->name, module)) {
			fprint(f, "\n");
			fprintSegment(f, 2 * INDENT, "OBJECT",
				      INDENTVALUE, smiv1);
			fprint(f, "%s\n", smiNode2->name);

			smiType = smiGetRefinementType(smiRefinement);
			if (smiType) {
  			    SmiType *parentType = smiGetParentType(smiType);
			    if (! parentType->name) {
			        parentType = smiGetParentType(parentType);
			    }
			    fprintSegment(f, 2 * INDENT, "SYNTAX", INDENTVALUE,
					  smiv1);
			    fprint(f, "%s",
				   getTypeString(smiType->basetype, 
						 parentType));
			    fprintSubtype(f, smiType, smiv1);
			    fprint(f, "\n");
			}
			
			smiType = smiGetRefinementWriteType(smiRefinement);
			if (smiType) {
			    SmiType *parentType = smiGetParentType(smiType);
			    if (! parentType->name) {
			        parentType = smiGetParentType(parentType);
			    }
			    fprintSegment(f, 2 * INDENT, "WRITE-SYNTAX",
					  INDENTVALUE, smiv1);
			    fprint(f, "%s",
				   getTypeString(smiType->basetype,
						 parentType));
			    fprintSubtype(f, smiType, smiv1);
			    fprint(f, "\n");
			}
			
			if (smiRefinement->access != SMI_ACCESS_UNKNOWN) {
			    fprintSegment(f, 2 * INDENT, "MIN-ACCESS",
					  INDENTVALUE, smiv1);
			    fprint(f, "%s\n",
				   getAccessString(smiRefinement->access, 0));
			    /* we assume, that read-create does not appear in
			     * an OT refinement.
			     */
			}
			fprintSegment(f, 2 * INDENT, "DESCRIPTION",
				      INDENTVALUE, smiv1);
			fprint(f, "\n");
			if (smiRefinement->description) {
			    fprintMultilineString2(f,
						  smiRefinement->description,
						  smiv1);
			} else {
			    fprintMultilineString2(f, "...", smiv1);
			}
			fprint(f, "\n");
		    }
		}
		
		/*
		 * search the next module name in the list of mandatory
		 * groups, optional groups and refinements.
		 */
		done = xrealloc(done,
				strlen(done)+strlen(module)+2*sizeof(char));
		strcat(done, module);
		strcat(done, "+");
		module = NULL;
		for (smiElement = smiGetFirstElement(smiNode);
		     smiElement;
		     smiElement = smiGetNextElement(smiElement)) {
		    sprintf(s, "+%s+",
			smiGetNodeModule(smiGetElementNode(smiElement))->name);
		    if ((!strstr(done, s))) {
			module =
			 smiGetNodeModule(smiGetElementNode(smiElement))->name;
			break;
		    }
		}
		if (!module) {
		    ; /* TODO: search in options list */
		}
		if (!module) {
		    ; /* TODO: search in refinements list */
		}
	    }

	    fprint(f, "\n");
	    fprintSegment(f, INDENT, "::= ", 0, smiv1);
	    fprint(f, "{ %s }\n\n", getOidString(smiNode, 0));
	}
    }
    xfree(done);
}



static void dumpSmi(FILE *f, SmiModule *smiModule, int flags)
{
    if (smiModule->language == SMI_LANGUAGE_SPPI) /* PIB to MIB conversion */
        pibtomib = 1;
    else
        pibtomib = 0;

    createImportList(smiModule);
    
    fprint(f, "--\n");
    fprint(f, "-- This %s module has been generated by smidump "
	   SMI_VERSION_STRING ". Do not edit.\n", smiv1 ? "SMIv1" : "SMIv2");
    fprint(f, "--\n\n");
    if (pibtomib && smiv1) {
        fprint(f, "--\n");
        fprint(f, "-- WARNING: SPPI to SMIv1 conversion is not explicitly " \
          "supported.\n");
        fprint(f, "-- Expect flawed output.\n");
        fprint(f, "--\n\n");
    }
    if (! (flags & SMIDUMP_FLAG_SILENT) && (flags & SMIDUMP_FLAG_ERROR)) {
	fprintf(f, "--\n-- WARNING: this output may be incorrect due to "
		"significant parse errors\n--\n\n");
    }
    fprint(f, "%s%s DEFINITIONS ::= BEGIN\n\n", smiModule->name,
           (pibtomib ? "-MIB" : ""));
	
    fprintImports(f);
    fprintModuleIdentity(f, smiModule);
    fprintTypeDefinitions(f, smiModule);
    fprintTextualConventions(f, smiModule);
    fprintObjects(f, smiModule);
    fprintNotifications(f, smiModule);
    fprintGroups(f, smiModule);
    fprintModuleCompliances(f, smiModule);
    
    fprint(f, "END -- end of module %s%s.\n", smiModule->name,
           (pibtomib ? "-MIB" : ""));

    freeImportList();
}


static void dumpSmiV1(int modc, SmiModule **modv, int flags, char *output)
{
    int  i;
    FILE *f = stdout;
    
    smiv1 = 1;
    silent = (flags & SMIDUMP_FLAG_SILENT);

    if (output) {
	f = fopen(output, "w");
	if (!f) {
	    fprintf(stderr, "smidump: cannot open %s for writing: ", output);
	    perror(NULL);
	    exit(1);
	}
    }

    for (i = 0; i < modc; i++) {
	dumpSmi(f, modv[i], flags);
	if (fflush(f) || ferror(f)) {
	    perror("smidump: write error");
	    exit(1);
	}
    }

    if (output) {
	fclose(f);
    }
}


static void dumpSmiV2(int modc, SmiModule **modv, int flags, char *output)
{
    int  i;
    FILE *f = stdout;

    smiv1 = 0;
    silent = (flags & SMIDUMP_FLAG_SILENT);

    if (output) {
	f = fopen(output, "w");
	if (!f) {
	    fprintf(stderr, "smidump: cannot open %s for writing: ", output);
	    perror(NULL);
	    exit(1);
	}
    }

    for (i = 0; i < modc; i++) {
	dumpSmi(f, modv[i], flags);
    }
    
    if (fflush(f) || ferror(f)) {
	perror("smidump: write error");
	exit(1);
    }

    if (output) {
	fclose(f);
    }
}



void initSmi()
{
    static SmidumpDriver driver1 = {
	"smiv1",
	dumpSmiV1,
	0,
	SMIDUMP_DRIVER_CANT_UNITE,
	"SMIv1 (RFC 1155, RFC 1212, RFC 1215)",
	NULL,
	NULL
    };
    
    static SmidumpDriver driver2 = {
	"smiv2",
	dumpSmiV2,
	0,
	SMIDUMP_DRIVER_CANT_UNITE | SMIDUMP_DRIVER_CANT_YANG,
	"SMIv2 (RFC 2578, RFC 2579, RFC 2580)",
	NULL,
	NULL
    };
    
    smidumpRegisterDriver(&driver1);
    smidumpRegisterDriver(&driver2);
}
