/*                         G 2 A S C . C
 * BRL-CAD
 *
 * Copyright (c) 1985-2024 United States Government as represented by
 * the U.S. Army Research Laboratory.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * version 2.1 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this file; see the file named COPYING for more
 * information.
 *
 */
/** @file g2asc.c
 *
 *  This program generates an ASCII data file which contains
 *  a GED database.
 *
 */

#include "common.h"

#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "bnetwork.h"
#include "bio.h"

#include "bu/app.h"
#include "bu/debug.h"
#include "bu/opt.h"
#include "bu/units.h"
#include "vmath.h"
#include "rt/db4.h"
#include "raytrace.h"
#include "wdb.h"
#include "rt/geom.h"
#include "tcl.h"

const mat_t id_mat = MAT_INIT_IDN; /* identity matrix for pipes */

char *strchop(char *str, size_t len);
#define CH(x)	strchop(x, sizeof(x))

int	combdump(void);
void	idendump(void), polyhead(void), polydata(void);
void	soldump(void), extrdump(void), sketchdump(void);
void	membdump(union record *rp), arsadump(void), arsbdump(void);
void	materdump(void), bspldump(void), bsurfdump(void);
void	pipe_dump(void), particle_dump(void), dump_pipe_segs(char *, struct bu_list *);
void	arbn_dump(void), cline_dump(void), bot_dump(void);
void	nmg_dump(void);
void	strsol_dump(void);

union record	record;		/* GED database record */

static const char usage[] = "\
Usage: g2asc file.g file.asc\n\
 Convert a binary BRL-CAD database to machine-independent ASCII form\n\
";

FILE	*ifp;
FILE	*ofp;
char	*iname = "-";

static char *tclified_name=NULL;
static size_t tclified_name_buffer_len=0;


/*	This routine escapes the '{' and '}' characters in any string and returns a static buffer containing the
 *	resulting string. Used for names and db title on output.
 *
 *	NOTE: RETURN OF STATIC BUFFER
 */
char *
tclify_name(const char *name)
{
    const char *src=name;
    char *dest;

    size_t max_len=2 * strlen(name) + 1;

    if (max_len < 2) {
	return (char *)NULL;
    }

    if (max_len > tclified_name_buffer_len) {
	tclified_name_buffer_len = max_len;
	tclified_name = (char *)bu_realloc(tclified_name, tclified_name_buffer_len, "tclified_name buffer");
    }

    dest = tclified_name;

    while (*src) {
	if (*src == '{' || *src == '}') {
	    *dest++ = '\\';
	}
	*dest++ = *src++;
    }
    *dest = '\0';

    return tclified_name;
}

int
main(int argc, char **argv)
{
    unsigned i;
    int need_help = 0;

    bu_setprogname(argv[0]);
    Tcl_FindExecutable(argv[0]);

    struct bu_opt_desc d[3];
    BU_OPT(d[0], "h", "help",        "",         NULL,        &need_help, "Print help   and exit");
    BU_OPT(d[1], "?", "",            "",         NULL,        &need_help, "");
    BU_OPT_NULL(d[2]);

    /* Skip first arg */
    argv++; argc--;
    struct bu_vls optparse_msg = BU_VLS_INIT_ZERO;
    int uac = bu_opt_parse(&optparse_msg, argc, (const char **)argv, d);

    if (uac == -1) {
	bu_exit(EXIT_FAILURE, "%s", bu_vls_addr(&optparse_msg));
    }
    bu_vls_free(&optparse_msg);

    argc = uac;

    if (need_help) {
	bu_exit(EXIT_SUCCESS, "%s", usage);
    }

    if (argc != 2) {
	bu_exit(1, "%s", usage);
    }

    setmode(fileno(stdin), O_BINARY);
    setmode(fileno(stdout), O_BINARY);
    setmode(fileno(stderr), O_BINARY);

    iname = "-";
    ifp = stdin;
    ofp = stdout;

    bu_debug = BU_DEBUG_COREDUMP;

    if (argc >= 2) {

	iname = argv[0];

	if (BU_STR_EQUAL(iname, "-")) {
	    ifp = stdin;
	} else {
	    ifp = fopen(iname, "rb");
	}

	if (!ifp)
	    perror(iname);

	if (BU_STR_EQUAL(argv[1], "-")) {
	    ofp = stdout;
	} else {
	    ofp = fopen(argv[1], "wb");
	}

	if (!ofp)
	    perror(argv[1]);

	if (ifp == NULL || ofp == NULL) {
	    bu_exit(1, "g2asc: can't open files.");
	}
    }

    if (isatty(fileno(ifp))) {
	bu_exit(1, "%s", usage);
    }

    /* First, determine what version database this is */
    if (fread((char *)&record, sizeof record, 1, ifp) != 1) {
	bu_exit(2, "g2asc(%s) ERROR, file too short to be BRL-CAD database\n",
		iname);
    }

    if (db5_header_is_valid((unsigned char *)&record)) {
	Tcl_Interp	*interp;
	struct db_i	*dbip;
	struct directory *dp;
	const char *u;

	if (ifp == stdin || ofp == stdout) {
	    bu_log("Unsupported: cannot use stdin or stdout for v5 or later geometry databases\n");
	    bu_exit(1, "Please use the \"g2asc input.g output.g\" form\n");
	}

	bu_log("Exporting v5 format geometry database\n");
	bu_log("  Note that the v5 binary format is machine independent.\n");
	bu_log("  Converting to ASCII to move database to a different\n");
	bu_log("  computer architecture is no longer necessary.\n");
	interp = Tcl_CreateInterp();
	/* This runs the init.tcl script */
	if (Tcl_Init(interp) == TCL_ERROR)
	    bu_log("Tcl_Init error %s\n", Tcl_GetStringResult(interp));

	if ((dbip = db_open(iname, DB_OPEN_READONLY)) == NULL) {
	    bu_exit(4, "Unable to open geometry database file '%s', aborting\n", iname);
	}
	RT_CK_DBI(dbip);
	if (db_dirbuild(dbip)) {
	    bu_exit(1, "db_dirbuild failed\n");
	}

	/* write out the title and units special */
	if (dbip->dbi_title[0]) {
	    fprintf(ofp, "title {%s}\n", tclify_name(dbip->dbi_title));
	} else {
	    fprintf(ofp, "title {Untitled BRL-CAD Database}\n");
	}
	u = bu_units_string(dbip->dbi_local2base);
	if (u) {
	    fprintf(ofp, "units %s\n", u);
	}
	FOR_ALL_DIRECTORY_START(dp, dbip) {
	    struct rt_db_internal	intern;
	    struct bu_attribute_value_set *avs=NULL;

	    /* Process the _GLOBAL object */
	    if (dp->d_major_type == DB5_MAJORTYPE_ATTRIBUTE_ONLY && dp->d_minor_type == 0) {
		const char *value;
		Tcl_Obj	*list, *obj;
		size_t list_len;
		struct bu_attribute_value_set g_avs;

		/* get _GLOBAL attributes */
		if (db5_get_attributes(dbip, &g_avs, dp)) {
		    bu_log("Failed to find any attributes on _GLOBAL\n");
		    bu_avs_free(&g_avs);
		    continue;
		}

		/* save the associated attributes of
		 * _GLOBAL (except for title and units
		 * which were already written out) and
		 * regionid_colortable (which is written out below)
		 */
		if (g_avs.count) {
		    int printedHeader = 0;
		    for (i = 0; i < g_avs.count; i++) {
			if (bu_strncmp(g_avs.avp[i].name, "title", 6) == 0) {
			    continue;
			} else if (bu_strncmp(g_avs.avp[i].name, "units", 6) == 0) {
			    continue;
			} else if (bu_strncmp(g_avs.avp[i].name, "regionid_colortable", 19) == 0) {
			    continue;
			} else if (strlen(g_avs.avp[i].name) <= 0) {
			    continue;
			}
			if (printedHeader == 0) {
			    fprintf(ofp, "attr set {_GLOBAL}");
			    printedHeader = 1;
			}
			fprintf(ofp, " {%s} {%s}", g_avs.avp[i].name, g_avs.avp[i].value);
		    }
		    if (printedHeader == 1)
			fprintf(ofp, "\n");
		}

		value = bu_avs_get(&g_avs, "regionid_colortable");
		if (!value) {
		    bu_avs_free(&g_avs);
		    continue;
		}
		list = Tcl_NewStringObj(value, -1);
		{
		    int llen;
		    if (Tcl_ListObjLength(interp, list, &llen) != TCL_OK) {
			bu_log("Failed to get length of region color table!!\n");
			bu_avs_free(&g_avs);
			continue;
		    }
		    list_len = (size_t)llen;
		}
		for (i = 0; i < list_len; i++) {
		    if (Tcl_ListObjIndex(interp, list, i, &obj) != TCL_OK) {
			bu_log("Cannot get entry %d from the color table!!\n", i);
			continue;
		    }
		    fprintf(ofp, "color %s\n", Tcl_GetStringFromObj(obj, NULL));
		}
		bu_avs_free(&g_avs);
		continue;
	    }

	    if (rt_db_get_internal(&intern, dp, dbip, NULL, &rt_uniresource) < 0) {
		bu_log("Unable to read '%s', skipping\n", dp->d_namep);
		continue;
	    }
	    if (!intern.idb_meth->ft_get) {
		bu_log("Unable to get '%s' (unimplemented), skipping\n", dp->d_namep);
		continue;
	    }

	    if (dp->d_flags & RT_DIR_COMB) {
		struct bu_vls logstr = BU_VLS_INIT_ZERO;

		if (intern.idb_meth->ft_get(&logstr, &intern, "tree") != TCL_OK) {
		    rt_db_free_internal(&intern);
		    bu_log("Unable to export '%s', skipping\n", dp->d_namep);
		    Tcl_AppendResult(interp, bu_vls_addr(&logstr), (char *)0);
		    bu_vls_free(&logstr);
		    continue;
		}
		Tcl_AppendResult(interp, bu_vls_addr(&logstr), (char *)0);
		bu_vls_free(&logstr);
		if (dp->d_flags & RT_DIR_REGION) {
		    fprintf(ofp, "put {%s} comb region yes tree {%s}\n",
			     tclify_name(dp->d_namep),
			     Tcl_GetStringResult(interp));
		} else {
		    fprintf(ofp, "put {%s} comb region no tree {%s}\n",
			     tclify_name(dp->d_namep),
			     Tcl_GetStringResult(interp));
		}
	    } else {
		struct bu_vls logstr = BU_VLS_INIT_ZERO;

		if ((dp->d_minor_type != ID_CONSTRAINT) && (intern.idb_meth->ft_get(&logstr, &intern, NULL) != TCL_OK)) {
		    rt_db_free_internal(&intern);
		    bu_log("Unable to export '%s', skipping\n", dp->d_namep);
		    Tcl_AppendResult(interp, bu_vls_addr(&logstr), (char *)0);
		    bu_vls_free(&logstr);
		    continue;
		}
		Tcl_AppendResult(interp, bu_vls_addr(&logstr), (char *)0);
		bu_vls_free(&logstr);
		fprintf(ofp, "put {%s} %s\n",
			 tclify_name(dp->d_namep),
			 Tcl_GetStringResult(interp));
	    }
	    avs = &intern.idb_avs;
	    if (avs->magic == BU_AVS_MAGIC && avs->count > 0) {
		fprintf(ofp, "attr set {%s}", tclify_name(dp->d_namep));
		for (i = 0; i < avs->count; i++) {
		    if (strlen(avs->avp[i].name) <= 0) {
			continue;
		    }
		    fprintf(ofp, " {%s}", avs->avp[i].name);
		    fprintf(ofp, " {%s}", avs->avp[i].value);
		}
		fprintf(ofp, "\n");
	    }
	    Tcl_ResetResult(interp);
	    rt_db_free_internal(&intern);
	} FOR_ALL_DIRECTORY_END;

	/* processing a v5, we're done */
	return 0;
    }

    /* processing a v4 */
top:
    do {
	/* A v4 record is already in the input buffer */
	/* Check record type and skip deleted records */
	switch (record.u_id) {
	    case ID_FREE:
		continue;
	    case ID_SOLID:
		soldump();
		continue;
	    case ID_COMB:
		if (combdump() > 0)
		    goto top;
		continue;
	    case ID_MEMB:
		fprintf(stderr, "g2asc: stray MEMB record, skipped\n");
		continue;
	    case ID_ARS_A:
		arsadump();
		continue;
	    case ID_P_HEAD:
		polyhead();
		continue;
	    case ID_P_DATA:
		polydata();
		continue;
	    case ID_IDENT:
		idendump();
		continue;
	    case ID_COLORTAB:
		materdump();
		continue;
	    case DBID_PIPE:
		pipe_dump();
		continue;
	    case DBID_STRSOL:
		strsol_dump();
		continue;
	    case DBID_NMG:
		nmg_dump();
		continue;
	    case DBID_PARTICLE:
		particle_dump();
		continue;
	    case DBID_ARBN:
		arbn_dump();
		continue;
	    case DBID_CLINE:
		cline_dump();
		continue;
	    case DBID_BOT:
		bot_dump();
		continue;
	    case ID_BSOLID:
		bspldump();
		continue;
	    case ID_BSURF:
		bsurfdump();
		continue;
	    case DBID_SKETCH:
		sketchdump();
		continue;
	    case DBID_EXTR:
		extrdump();
		continue;
	    default:
		fprintf(stderr,
			      "g2asc: unable to convert record type '%c' (0%o), skipping\n",
			      record.u_id, record.u_id);
		continue;
	}
    }  while (fread((char *)&record, sizeof record, 1, ifp) == 1  &&
	       !feof(ifp));

    /* done with v4 */
    return 0;
}


/*
 *  Take a database name and null-terminate it,
 *  converting unprintable characters to something printable.
 *  Here we deal with names not being null-terminated.
 */
char *encode_name(char *str)
{
    static char buf[NAMESIZE+1];
    char *ip = str;
    char *op = buf;
    int warn = 0;

    while (op < &buf[NAMESIZE]) {
	if (*ip == '\0')  break;
	if (isprint((int)*ip) && !isspace((int)*ip)) {
	    *op++ = *ip++;
	}  else  {
	    *op++ = '@';
	    ip++;
	    warn = 1;
	}
    }
    *op = '\0';
    if (warn) {
	fprintf(stderr,
		      "g2asc: Illegal char in object name, converted to '%s'\n",
		      buf);
    }
    if (op == buf) {
	/* Null input name */
	fprintf(stderr,
		      "g2asc:  NULL object name converted to -=NULL=-\n");
	return "-=NULL=-";
    }
    return buf;
}


/*
 *  Take "ngran" granules, and put them in memory.
 *  The first granule comes from the global extern "record",
 *  the remainder are read from ifp.
 */
void
get_ext(struct bu_external *ep, size_t ngran)
{
    size_t count;

    BU_EXTERNAL_INIT(ep);

    ep->ext_nbytes = ngran * sizeof(union record);
    ep->ext_buf = (uint8_t *)bu_malloc(ep->ext_nbytes, "get_ext ext_buf");

    /* Copy the freebie (first) record into the array of records.  */
    memcpy((char *)ep->ext_buf, (char *)&record, sizeof(union record));
    if (ngran <= 1)  return;

    count = fread(((char *)ep->ext_buf)+sizeof(union record),
		   sizeof(union record), ngran-1, ifp);
    if (count != (size_t)ngran-1) {
	fprintf(stderr,
		"g2asc: get_ext:  wanted to read %lu granules, got %lu\n",
		(unsigned long)ngran-1, (unsigned long)count);
	bu_exit(1, NULL);
    }
}

void
nmg_dump(void)
{
    union record rec;
    long struct_count[26];
    size_t i, granules;
    size_t j, k;

    /* just in case someone changes the record size */
    if (sizeof(union record)%32)
    {
	fprintf(stderr, "g2asc: nmg_dump cannot work with records not multiple of 32\n");
	bu_exit(-1, NULL);
    }

    /* get number of granules needed for this NMG */
    granules = ntohl(*(uint32_t *)record.nmg.N_count);

    /* get the array of structure counts */
    for (j = 0; j < 26; j++)
	struct_count[j] = ntohl(*(uint32_t *)&record.nmg.N_structs[j*4]);

    /* output some header info */
    fprintf(ofp,  "%c %d %.16s %lu\n",
		  record.nmg.N_id,	/* N */
		  record.nmg.N_version,	/* NMG version */
		  record.nmg.N_name,	/* solid name */
		  (unsigned long)granules);		/* number of additional granules */

    /* output the structure counts */
    for (j = 0; j < 26; j++)
	fprintf(ofp,  " %ld", struct_count[j]);
    (void)fputc('\n', ofp);

    /* dump the reminder in hex format */
    for (i = 0; i < granules; i++) {
	char *cp;
	/* Read the record */
	if (!fread((char *)&rec, sizeof record, 1, ifp)) {
	    fprintf(stderr, "Error reading nmg granules\n");
	    bu_exit(-1, NULL);
	}
	cp = (char *)&rec;

	/* 32 bytes per line */
	for (k = 0; k < sizeof(union record)/32; k++) {
	    for (j = 0; j < 32; j++)
		fprintf(ofp,  "%02x", (0xff & (*cp++)));	 /* two hex digits per byte */
	    fputc('\n', ofp);
	}
    }
}

void
strsol_dump(void)	/* print out strsol solid info */
{
    union record rec[DB_SS_NGRAN];
    char *cp;

    /* get all the strsol granules */
    rec[0] = record;	/* struct copy the current record */

    /* read the rest from ifp */
    if (!fread((char *)&rec[1], sizeof record, DB_SS_NGRAN-1, ifp))
    {
	fprintf(stderr, "Error reading strsol granules\n");
	bu_exit(-1, NULL);
    }

    /* make sure that at least the last byte is null */
    cp = (char *)&rec[DB_SS_NGRAN-1];
    cp += (sizeof(union record) - 1);
    *cp = '\0';

    fprintf(ofp,  "%c %.16s %.16s %s\n",
		  rec[0].ss.ss_id,	/* s */
		  rec[0].ss.ss_keyword,	/* "ebm", "vol", or ??? */
		  rec[0].ss.ss_name,	/* solid name */
		  rec[0].ss.ss_args);	/* everything else */

}

void
idendump(void)	/* Print out Ident record information */
{
    fprintf(ofp,  "%c %d %.6s\n",
		  record.i.i_id,			/* I */
		  record.i.i_units,		/* units */
		  CH(record.i.i_version)		/* version */
	);
    fprintf(ofp,  "%.72s\n",
		  CH(record.i.i_title)	/* title or description */
	);

    /* Print a warning message on stderr if versions differ */
    if (!BU_STR_EQUAL(record.i.i_version, ID_VERSION)) {
	fprintf(stderr,
		      "g2asc: File is version (%s), Program is version (%s)\n",
		      record.i.i_version, ID_VERSION);
    }
}

void
polyhead(void)	/* Print out Polyhead record information */
{
    fprintf(ofp, "%c ", record.p.p_id);		/* P */
    fprintf(ofp, "%.16s", encode_name(record.p.p_name));	/* unique name */
    fprintf(ofp, "\n");			/* Terminate w/ a newline */
}

void
polydata(void)	/* Print out Polydata record information */
{
    int i, j;

    fprintf(ofp, "%c ", record.q.q_id);		/* Q */
    fprintf(ofp, "%d", record.q.q_count);		/* # of vertices <= 5 */
    for (i = 0; i < 5; i++) {
	/* [5][3] vertices */
	for (j = 0; j < 3; j++) {
	    fprintf(ofp, " %.12e", record.q.q_verts[i][j]);
	}
    }
    for (i = 0; i < 5; i++) {
	/* [5][3] normals */
	for (j = 0; j < 3; j++) {
	    fprintf(ofp, " %.12e", record.q.q_norms[i][j]);
	}
    }
    fprintf(ofp, "\n");			/* Terminate w/ a newline */
}

void
soldump(void)	/* Print out Solid record information */
{
    int i;

    fprintf(ofp, "%c ", record.s.s_id);	/* S */
    fprintf(ofp, "%d ", record.s.s_type);	/* GED primitive type */
    fprintf(ofp, "%.16s ", encode_name(record.s.s_name));	/* unique name */
    fprintf(ofp, "%d", record.s.s_cgtype);/* COMGEOM solid type */
    for (i = 0; i < 24; i++)
	fprintf(ofp, " %.12e", record.s.s_values[i]); /* parameters */
    fprintf(ofp, "\n");			/* Terminate w/ a newline */
}

void
cline_dump(void)
{
    size_t ngranules;	/* number of granules, total */
    char *name;
    struct rt_cline_internal *cli;
    struct bu_external ext;
    struct rt_db_internal intern;

    name = record.cli.cli_name;

    ngranules = 1;
    get_ext(&ext, ngranules);

    /* Hand off to librt's import() routine */
    RT_DB_INTERNAL_INIT(&intern);
    if ((OBJ[ID_CLINE].ft_import4(&intern, &ext, id_mat, DBI_NULL, &rt_uniresource)) != 0) {
	fprintf(stderr, "g2asc: cline import failure\n");
	bu_exit(-1, NULL);
    }

    cli = (struct rt_cline_internal *)intern.idb_ptr;
    RT_CLINE_CK_MAGIC(cli);

    fprintf(ofp, "%c ", DBID_CLINE);	/* c */
    fprintf(ofp, "%.16s ", name);	/* unique name */
    fprintf(ofp, "%26.20e %26.20e %26.20e ", V3ARGS(cli->v));
    fprintf(ofp, "%26.20e %26.20e %26.20e ", V3ARGS(cli->h));
    fprintf(ofp, "%26.20e %26.20e", cli->radius, cli->thickness);
    fprintf(ofp, "\n");			/* Terminate w/ a newline */

    rt_db_free_internal(&intern);
    bu_free_external(&ext);
}

void
bot_dump(void)
{
    size_t ngranules;
    char *name;
    struct rt_bot_internal *bot;
    struct bu_external ext;
    struct rt_db_internal intern;
    size_t i;

    name = record.bot.bot_name;
    ngranules = ntohl(*(uint32_t *)record.bot.bot_nrec) + 1;
    get_ext(&ext, ngranules);

    /* Hand off to librt's import() routine */
    RT_DB_INTERNAL_INIT(&intern);
    if ((OBJ[ID_BOT].ft_import4(&intern, &ext, id_mat, DBI_NULL, &rt_uniresource)) != 0) {
	fprintf(stderr, "g2asc: bot import failure\n");
	bu_exit(-1, NULL);
    }

    bot = (struct rt_bot_internal *)intern.idb_ptr;
    RT_BOT_CK_MAGIC(bot);

    fprintf(ofp, "%c ", DBID_BOT);	/* t */
    fprintf(ofp, "%.16s ", name);	/* unique name */
    fprintf(ofp, "%d ", bot->mode);
    fprintf(ofp, "%d ", bot->orientation);
    fprintf(ofp, "%d ", 0);	/* was error_mode */
    fprintf(ofp, "%lu ", (unsigned long)bot->num_vertices);
    fprintf(ofp, "%lu", (unsigned long)bot->num_faces);
    fprintf(ofp, "\n");

    for (i = 0; i < bot->num_vertices; i++)
	fprintf(ofp,  "	%lu: %26.20e %26.20e %26.20e\n", (unsigned long)i, V3ARGS(&bot->vertices[i*3]));
    if (bot->mode == RT_BOT_PLATE) {
	struct bu_vls vls = BU_VLS_INIT_ZERO;

	for (i = 0; i < bot->num_faces; i++)
	    fprintf(ofp,  "	%lu: %d %d %d %26.20e\n", (unsigned long)i, V3ARGS(&bot->faces[i*3]), bot->thickness[i]);
	bu_bitv_to_hex(&vls, bot->face_mode);
	fprintf(ofp,  "	%s\n", bu_vls_addr(&vls));
	bu_vls_free(&vls);
    } else {
	for (i = 0; i < bot->num_faces; i++)
	    fprintf(ofp,  "	%lu: %d %d %d\n", (unsigned long)i, V3ARGS(&bot->faces[i*3]));
    }

    rt_db_free_internal(&intern);
    bu_free_external(&ext);
}

void
pipe_dump(void)	/* Print out Pipe record information */
{

    size_t ngranules;	/* number of granules, total */
    char *name;
    struct rt_pipe_internal *pipeip;		/* want a struct for the head, not a ptr. */
    struct bu_external ext;
    struct rt_db_internal intern;

    ngranules = ntohl(*(uint32_t *)record.pwr.pwr_count) + 1;
    name = record.pwr.pwr_name;

    get_ext(&ext, ngranules);

    /* Hand off to librt's import() routine */
    RT_DB_INTERNAL_INIT(&intern);
    if ((OBJ[ID_PIPE].ft_import4(&intern, &ext, id_mat, NULL, &rt_uniresource)) != 0) {
	fprintf(stderr, "g2asc: pipe import failure\n");
	bu_exit(-1, NULL);
    }

    pipeip = (struct rt_pipe_internal *)intern.idb_ptr;
    RT_PIPE_CK_MAGIC(pipeip);

    /* send the doubly linked list off to dump_pipe_segs(), which
     * will print all the information.
     */

    dump_pipe_segs(name, &pipeip->pipe_segs_head);

    rt_db_free_internal(&intern);
    bu_free_external(&ext);
}

void
dump_pipe_segs(char *name, struct bu_list *headp)
{

    struct wdb_pipe_pnt *sp;

    fprintf(ofp, "%c %.16s\n", DBID_PIPE, name);

    /* print parameters for each point: one point per line */

    for (BU_LIST_FOR(sp, wdb_pipe_pnt, headp)) {
	fprintf(ofp,  "%26.20e %26.20e %26.20e %26.20e %26.20e %26.20e\n",
		sp->pp_id, sp->pp_od, sp->pp_bendradius, V3ARGS(sp->pp_coord));
    }
    fprintf(ofp,  "END_PIPE %s\n", name);
}

/*
 * Print out Particle record information.
 * Note that particles fit into one granule only.
 */
void
particle_dump(void)
{
    struct rt_part_internal 	*part;	/* head for the structure */
    struct bu_external	ext;
    struct rt_db_internal	intern;

    get_ext(&ext, 1);

    /* Hand off to librt's import() routine */
    RT_DB_INTERNAL_INIT(&intern);
    if ((OBJ[ID_PARTICLE].ft_import4(&intern, &ext, id_mat, NULL, &rt_uniresource)) != 0) {
	fprintf(stderr, "g2asc: particle import failure\n");
	bu_exit(-1, NULL);
    }

    part = (struct rt_part_internal *)intern.idb_ptr;
    RT_PART_CK_MAGIC(part);

    /* Particle type is picked up on here merely to ensure receiving
     * valid data.  The type is not used any further.
     */

    switch (part->part_type) {
	case RT_PARTICLE_TYPE_SPHERE:
	    break;
	case RT_PARTICLE_TYPE_CYLINDER:
	    break;
	case RT_PARTICLE_TYPE_CONE:
	    break;
	default:
	    fprintf(stderr, "g2asc: no particle type %d\n", part->part_type);
	    bu_exit(-1, NULL);
    }

    fprintf(ofp, "%c %.16s %26.20e %26.20e %26.20e %26.20e %26.20e %26.20e %26.20e %26.20e\n",
	    record.part.p_id, record.part.p_name,
	    part->part_V[X],
	    part->part_V[Y],
	    part->part_V[Z],
	    part->part_H[X],
	    part->part_H[Y],
	    part->part_H[Z],
	    part->part_vrad, part->part_hrad);
}


/*
 *  Print out arbn information.
 *
 */
void
arbn_dump(void)
{
    size_t ngranules;	/* number of granules to be read */
    size_t i;		/* a counter */
    char *name;
    struct rt_arbn_internal *arbn;
    struct bu_external ext;
    struct rt_db_internal intern;

    ngranules = ntohl(*(uint32_t *)record.n.n_grans) + 1;
    name = record.n.n_name;

    get_ext(&ext, ngranules);

    /* Hand off to librt's import() routine */
    RT_DB_INTERNAL_INIT(&intern);
    if ((OBJ[ID_ARBN].ft_import4(&intern, &ext, id_mat, NULL, &rt_uniresource)) != 0) {
	fprintf(stderr, "g2asc: arbn import failure\n");
	bu_exit(-1, NULL);
    }

    arbn = (struct rt_arbn_internal *)intern.idb_ptr;
    RT_ARBN_CK_MAGIC(arbn);

    fprintf(ofp, "%c %.16s %lu\n", 'n', name, (unsigned long)arbn->neqn);
    for (i = 0; i < arbn->neqn; i++) {
	fprintf(ofp, "n %26.20e %26.20e %26.20e %26.20e\n",
		arbn->eqn[i][X], arbn->eqn[i][Y],
		arbn->eqn[i][Z], arbn->eqn[i][3]);
    }

    rt_db_free_internal(&intern);
    bu_free_external(&ext);
}


/*
 *  Note that for compatibility with programs such as FRED that
 *  (inappropriately) read .asc files, the member count has to be
 *  recalculated here.
 *
 *  Returns -
 *	0	converted OK
 *	1	converted OK, left next record in global "record" for reuse.
 */
int
combdump(void)	/* Print out Combination record information */
{
    int	m1, m2;		/* material property flags */
    struct bu_list	head;
    struct mchain {
	struct bu_list	l;
	union record	r;
    };
    struct mchain	*mp;
    struct mchain	*ret_mp = (struct mchain *)0;
    int		mcount;

    /*
     *  Gobble up all subsequent member records, so that
     *  an accurate count of them can be output.
     */
    BU_LIST_INIT(&head);
    mcount = 0;
    while (1) {
	BU_GET(mp, struct mchain);
	if (fread((char *)&mp->r, sizeof(mp->r), 1, ifp) != 1
	     || feof(ifp))
	    break;
	if (mp->r.u_id != ID_MEMB) {
	    ret_mp = mp;	/* Handle it later */
	    break;
	}
	BU_LIST_INSERT(&head, &(mp->l));
	mcount++;
    }

    /*
     *  Output the combination
     */
    fprintf(ofp, "%c ", record.c.c_id);		/* C */
    switch (record.c.c_flags) {
	case DBV4_REGION:
	    fprintf(ofp, "Y ");			/* Y if `R' */
	    break;
	case DBV4_NON_REGION_NULL:
	case DBV4_NON_REGION:
	    fprintf(ofp, "N ");			/* N if ` ' or '\0' */
	    break;
	case DBV4_REGION_FASTGEN_PLATE:
	    fprintf(ofp, "P ");
	    break;
	case DBV4_REGION_FASTGEN_VOLUME:
	    fprintf(ofp, "V ");
	    break;
    }
    fprintf(ofp, "%.16s ", encode_name(record.c.c_name));	/* unique name */
    fprintf(ofp, "%d ", record.c.c_regionid);	/* region ID code */
    fprintf(ofp, "%d ", record.c.c_aircode);	/* air space code */

    /* DEPRECATED: # of members */
    fprintf(ofp, "%d ", mcount);

    /* DEPRECATED: COMGEOM region # */
    fprintf(ofp, "%d ", 0 /* was record.c.c_num */);

    fprintf(ofp, "%d ", record.c.c_material);	/* material code */
    fprintf(ofp, "%d ", record.c.c_los);		/* equiv. LOS est. */
    fprintf(ofp, "%d %d %d %d ",
		  record.c.c_override ? 1 : 0,
		  record.c.c_rgb[0],
		  record.c.c_rgb[1],
		  record.c.c_rgb[2]);
    m1 = m2 = 0;
    if (isprint((int)record.c.c_matname[0])) {
	m1 = 1;
	if (record.c.c_matparm[0])
	    m2 = 1;
    }
    fprintf(ofp, "%d %d", m1, m2);
    switch (record.c.c_inherit) {
	case DB_INH_HIGHER:
	    fprintf(ofp, " %d", DB_INH_HIGHER);
	    break;
	default:
	case DB_INH_LOWER:
	    fprintf(ofp, " %d", DB_INH_LOWER);
	    break;
    }
    fprintf(ofp, "\n");			/* Terminate w/ a newline */

    if (m1)
	fprintf(ofp, "%.32s\n", CH(record.c.c_matname));
    if (m2)
	fprintf(ofp, "%.60s\n", CH(record.c.c_matparm));

    /*
     *  Output the member records now
     */
    struct bu_list *p;
    for (BU_LIST_FOR(p, bu_list, &head)) {
	mp = (struct mchain *)p;
	membdump(&mp->r);
    }

    /* Clean up (in the style of bu_list_free, but using BU_PUT since the
     * memory was allocated with BU_GET) */
    while (BU_LIST_WHILE(p, bu_list, &head)) {
	BU_LIST_DEQUEUE(p);
	BU_PUT(p, struct mchain);
    }

    /* ret_mp, if we have it, is not part of the list */
    if (ret_mp) {
	memcpy((char *)&record, (char *)&ret_mp->r, sizeof(record));
	BU_PUT(ret_mp, struct mchain);
	return 1;
    }

    return 0;
}

/*
 *  Print out Member record information.
 *  Intended to be called by combdump only.
 */
void
membdump(union record *rp)
{
    int i;

    fprintf(ofp, "%c ", rp->M.m_id);		/* M */
    fprintf(ofp, "%c ", rp->M.m_relation);	/* Boolean oper. */
    fprintf(ofp, "%.16s ", encode_name(rp->M.m_instname));	/* referred-to obj. */
    for (i = 0; i < 16; i++)			/* homogeneous transform matrix */
	fprintf(ofp, "%.12e ", rp->M.m_mat[i]);
    fprintf(ofp, "%d", 0);			/* was COMGEOM solid # */
    fprintf(ofp, "\n");				/* Terminate w/ nl */
}

void
arsadump(void)	/* Print out ARS record information */
{
    int i;
    int length;	/* Keep track of number of ARS B records */

    fprintf(ofp, "%c ", record.a.a_id);	/* A */
    fprintf(ofp, "%d ", record.a.a_type);	/* primitive type */
    fprintf(ofp, "%.16s ", encode_name(record.a.a_name));	/* unique name */
    fprintf(ofp, "%d ", record.a.a_m);	/* # of curves */
    fprintf(ofp, "%d ", record.a.a_n);	/* # of points per curve */
    fprintf(ofp, "%d ", record.a.a_curlen);/* # of granules per curve */
    fprintf(ofp, "%d ", record.a.a_totlen);/* # of granules for ARS */
    fprintf(ofp, "%.12e ", record.a.a_xmax);	/* max x coordinate */
    fprintf(ofp, "%.12e ", record.a.a_xmin);	/* min x coordinate */
    fprintf(ofp, "%.12e ", record.a.a_ymax);	/* max y coordinate */
    fprintf(ofp, "%.12e ", record.a.a_ymin);	/* min y coordinate */
    fprintf(ofp, "%.12e ", record.a.a_zmax);	/* max z coordinate */
    fprintf(ofp, "%.12e", record.a.a_zmin);	/* min z coordinate */
    fprintf(ofp, "\n");			/* Terminate w/ a newline */

    length = (int)record.a.a_totlen;	/* Get # of ARS B records */

    for (i = 0; i < length; i++) {
	arsbdump();
    }
}

void
arsbdump(void)	/* Print out ARS B record information */
{
    int i;
    size_t ret;

    /* Read in a member record for processing */
    ret = fread((char *)&record, sizeof record, 1, ifp);
    if (ret != 1)
	perror("fread");
    fprintf(ofp, "%c ", record.b.b_id);		/* B */
    fprintf(ofp, "%d ", record.b.b_type);		/* primitive type */
    fprintf(ofp, "%d ", record.b.b_n);		/* current curve # */
    fprintf(ofp, "%d", record.b.b_ngranule);	/* current granule */
    for (i = 0; i < 24; i++) {
	/* [8*3] vectors */
	fprintf(ofp, " %.12e", record.b.b_values[i]);
    }
    fprintf(ofp, "\n");			/* Terminate w/ a newline */
}

void
materdump(void)	/* Print out material description record information */
{
    fprintf(ofp,  "%c %d %d %d %d %d %d\n",
		  record.md.md_id,			/* m */
		  record.md.md_flags,			/* UNUSED */
		  record.md.md_low,	/* low end of region IDs affected */
		  record.md.md_hi,	/* high end of region IDs affected */
		  record.md.md_r,
		  record.md.md_g,		/* color of regions: 0..255 */
		  record.md.md_b);
}

void
bspldump(void)	/* Print out B-spline solid description record information */
{
    fprintf(ofp,  "%c %.16s %d\n",
		  record.B.B_id,		/* b */
		  encode_name(record.B.B_name),	/* unique name */
		  record.B.B_nsurf);	/* # of surfaces in this solid */
}

void
bsurfdump(void)	/* Print d-spline surface description record information */
{
    size_t i;
    float *vp;
    size_t nbytes, count;
    float *fp;

    fprintf(ofp,  "%c %d %d %d %d %d %d %d %d %d\n",
		  record.d.d_id,		/* D */
		  record.d.d_order[0],	/* order of u and v directions */
		  record.d.d_order[1],	/* order of u and v directions */
		  record.d.d_kv_size[0],	/* knot vector size (u and v) */
		  record.d.d_kv_size[1],	/* knot vector size (u and v) */
		  record.d.d_ctl_size[0],	/* control mesh size (u and v) */
		  record.d.d_ctl_size[1],	/* control mesh size (u and v) */
		  record.d.d_geom_type,	/* geom type 3 or 4 */
		  record.d.d_nknots,	/* # granules of knots */
		  record.d.d_nctls);	/* # granules of ctls */
    /*
     * The b_surf_head record is followed by
     * d_nknots granules of knot vectors (first u, then v),
     * and then by d_nctls granules of control mesh information.
     * Note that neither of these have an ID field!
     *
     * B-spline surface record, followed by
     *	d_kv_size[0] floats,
     *	d_kv_size[1] floats,
     *	padded to d_nknots granules, followed by
     *	ctl_size[0]*ctl_size[1]*geom_type floats,
     *	padded to d_nctls granules.
     *
     * IMPORTANT NOTE: granule == sizeof(union record)
     */

    /* Malloc and clear memory for the KNOT DATA and read it */
    nbytes = record.d.d_nknots * sizeof(union record);
    vp = (float *)bu_calloc((unsigned int)nbytes, 1, "KNOT DATA");
    fp = vp;
    count = fread((char *)fp, 1, nbytes, ifp);
    if (count != nbytes) {
	fprintf(stderr, "g2asc: spline knot read failure\n");
	bu_exit(1, NULL);
    }
    /* Print the knot vector information */
    count = record.d.d_kv_size[0] + record.d.d_kv_size[1];
    for (i = 0; i < count; i++) {
	fprintf(ofp, "%.12e\n", *vp++);
    }
    /* Free the knot data memory */
    (void)bu_free((char *)fp, "KNOT DATA");

    /* Malloc and clear memory for the CONTROL MESH data and read it */
    nbytes = record.d.d_nctls * sizeof(union record);
    vp = (float *)bu_calloc((unsigned int)nbytes, 1, "CONTROL MESH");
    fp = vp;
    count = fread((char *)fp, 1, nbytes, ifp);
    if (count != nbytes) {
	fprintf(stderr, "g2asc: control mesh read failure\n");
	bu_exit(1, NULL);
    }
    /* Print the control mesh information */
    count = record.d.d_ctl_size[0] * record.d.d_ctl_size[1] *
	record.d.d_geom_type;
    for (i = 0; i < count; i++) {
	fprintf(ofp, "%.12e\n", *vp++);
    }
    /* Free the control mesh memory */
    (void)bu_free((char *)fp, "CONTROL MESH");
}

/*
 *  Take a string and a length, and null terminate,
 *  converting unprintable characters to something printable.
 */
char *strchop(char *str, size_t len)
{
    static char buf[10000] = {0};
    char *ip = str;
    char *op = buf;
    int warn = 0;
    char *ep;

    CLAMP(len, 1, sizeof(buf)-2);

    ep = &buf[len-1];		/* Leave room for null */
    while (op < ep) {
	if (*ip == '\0')  break;
	if ((int)isprint((int)*ip) || isspace((int)*ip)) {
	    *op++ = *ip++;
	}  else  {
	    *op++ = '@';
	    ip++;
	    warn = 1;
	}
    }
    *op = '\0';
    if (warn) {
	fprintf(stderr,
		      "g2asc: Illegal char in string, converted to '%s'\n",
		      buf);
    }
    if (op == buf) {
	/* Null input name */
	fprintf(stderr,
		      "g2asc:  NULL string converted to -=STRING=-\n");
	return "-=STRING=-";
    }
    return buf;
}

void
extrdump(void)
{
    struct rt_extrude_internal	*extr;
    int				ngranules;
    char				*myname;
    struct bu_external		ext;
    struct rt_db_internal		intern;

    myname = record.extr.ex_name;
    ngranules = ntohl(*(uint32_t *)record.extr.ex_count) + 1;
    get_ext(&ext, ngranules);

    /* Hand off to librt's import() routine */
    RT_DB_INTERNAL_INIT(&intern);
    if ((OBJ[ID_EXTRUDE].ft_import4(&intern, &ext, id_mat, DBI_NULL, &rt_uniresource)) != 0) {
	fprintf(stderr, "g2asc: extrusion import failure\n");
	bu_exit(-1, NULL);
    }

    extr = (struct rt_extrude_internal *)intern.idb_ptr;
    RT_EXTRUDE_CK_MAGIC(extr);

    fprintf(ofp, "%c ", DBID_EXTR);	/* e */
    fprintf(ofp, "%.16s ", encode_name(myname));	/* unique name */
    fprintf(ofp, "%.16s ", encode_name(extr->sketch_name));
    fprintf(ofp, "%d ", extr->keypoint);
    fprintf(ofp, "%.12e %.12e %.12e ", V3ARGS(extr->V));
    fprintf(ofp, "%.12e %.12e %.12e ", V3ARGS(extr->h));
    fprintf(ofp, "%.12e %.12e %.12e ", V3ARGS(extr->u_vec));
    fprintf(ofp, "%.12e %.12e %.12e\n", V3ARGS(extr->v_vec));
}

void
sketchdump(void)
{
    struct rt_sketch_internal *skt;
    size_t ngranules;
    char *myname;
    struct bu_external ext;
    struct rt_db_internal intern;
    size_t i, j;
    struct rt_curve *crv;

    myname = record.skt.skt_name;
    ngranules = ntohl(*(uint32_t *)record.skt.skt_count) + 1;
    get_ext(&ext, ngranules);

    /* Hand off to librt's import() routine */
    RT_DB_INTERNAL_INIT(&intern);
    if ((OBJ[ID_SKETCH].ft_import4(&intern, &ext, id_mat, DBI_NULL, &rt_uniresource)) != 0) {
	fprintf(stderr, "g2asc: sketch import failure\n");
	bu_exit(-1, NULL);
    }

    skt = (struct rt_sketch_internal *)intern.idb_ptr;
    RT_SKETCH_CK_MAGIC(skt);
    crv = &skt->curve;
    fprintf(ofp, "%c ", DBID_SKETCH); /* d */
    fprintf(ofp, "%.16s ", encode_name(myname));  /* unique name */
    fprintf(ofp, "%.12e %.12e %.12e ", V3ARGS(skt->V));
    fprintf(ofp, "%.12e %.12e %.12e ", V3ARGS(skt->u_vec));
    fprintf(ofp, "%.12e %.12e %.12e ", V3ARGS(skt->v_vec));
    fprintf(ofp, "%lu %lu\n", (unsigned long)skt->vert_count, (unsigned long)crv->count);
    for (i = 0; i < skt->vert_count; i++)
	fprintf(ofp, " %.12e %.12e", V2ARGS(skt->verts[i]));
    fprintf(ofp, "\n");

    for (j = 0; j < crv->count; j++) {
	long *lng;
	struct line_seg *lsg;
	struct carc_seg *csg;
	struct nurb_seg *nsg;
	int k;

	lng = (long *)crv->segment[j];
	switch (*lng) {
	    case CURVE_LSEG_MAGIC:
		lsg = (struct line_seg *)lng;
		fprintf(ofp, "  L %d %d %d\n", crv->reverse[j], lsg->start, lsg->end);
		break;
	    case CURVE_CARC_MAGIC:
		csg = (struct carc_seg *)lng;
		fprintf(ofp, "  A %d %d %d %.12e %d %d\n", crv->reverse[j], csg->start, csg->end,
			      csg->radius, csg->center_is_left, csg->orientation);
		break;
	    case CURVE_NURB_MAGIC:
		nsg = (struct nurb_seg *)lng;
		fprintf(ofp, "  N %d %d %d %d %d\n   ", crv->reverse[j], nsg->order, nsg->pt_type,
			      nsg->k.k_size, nsg->c_size);
		for (k = 0; k < nsg->k.k_size; k++)
		    fprintf(ofp, " %.12e", nsg->k.knots[k]);
		fprintf(ofp, "\n   ");
		for (k = 0; k < nsg->c_size; k++)
		    fprintf(ofp, " %d", nsg->ctl_points[k]);
		fprintf(ofp, "\n");
		break;
	}
    }
}

/*
 * Local Variables:
 * mode: C
 * tab-width: 8
 * indent-tabs-mode: t
 * c-file-style: "stroustrup"
 * End:
 * ex: shiftwidth=4 tabstop=8
 */
