#include <../../nrnconf.h>
/* /local/src/master/nrn/src/oc/fileio.c,v 1.34 1999/09/14 13:11:46 hines Exp */

#include	<stdio.h>
#include	<stdlib.h>
#include	<stdarg.h>
#include <unistd.h>
#include	"hoc.h"
#include "ocmisc.h"
#include "hocstr.h"
#include	"hoclist.h"
#include	"parse.h"
#include "hocparse.h"
#include	<setjmp.h>
#include	<errno.h>
#include	"nrnfilewrap.h"
#include "nrnjava.h"

extern jmp_buf		begin;
extern char* neuron_home;

NrnFILEWrap	*frin;
FILE	*fout;

#if 0 && MAC
#include <stdarg.h>

void debugfile(const char* format, ...) {
	va_list args;
	static FILE* df;
	if (!df) {
		df = fopen("debugfile", "w");
	}
	va_start(args, format);
	vfprintf(df, format, args);
	fflush(df);
}
#endif

void hoc_stdout(void) {
#if defined(WIN32) && !defined(CYGWIN)
	extern FILE* hoc_redir_stdout;
	if (ifarg(1)) {
		if (hoc_redir_stdout) {
			hoc_execerror("stdout already switched", (char*)0);
		}
		hoc_redir_stdout = fopen(gargstr(1), "wb");
	}else if (hoc_redir_stdout) {
		fclose(hoc_redir_stdout);
		hoc_redir_stdout = (FILE*)0;
	}
	ret();
	pushx ( (hoc_redir_stdout) ? (double)fileno(hoc_redir_stdout) : 1.);
#else
#if defined(MAC) && !defined(DARWIN)
	hoc_execerror("hoc_stdout", "not implemented for MAC");
#else /*UNIX */
	static int prev = -1;
	if (ifarg(1)) {
		FILE* f1;
		if (prev != -1) {
			hoc_execerror("stdout already switched", (char*)0);
		}
		prev = dup(1);
		if (prev < 0) {
			hoc_execerror("Unable to backup stdout", (char*)0);
		}
		f1 = fopen(gargstr(1), "wb");
		
		if (!f1) {
			hoc_execerror("Unable to open ", gargstr(1));
		}
		if (dup2(fileno(f1), 1) < 0) {
			hoc_execerror("Unable to attach stdout to ", gargstr(1));
		}
		fclose(f1);
	}else if (prev > -1) {
		int i;
		if (dup2(prev, 1) < 0) {
			hoc_execerror("Unable to restore stdout", (char*)0);
		}
		close(prev);
		prev = -1;
	}
	ret();
	pushx((double)fileno(stdout));
#endif
#endif
}

void ropen(void)		/* open file for reading */
{
	double d;
	char *fname;

	if (ifarg(1))
		fname = gargstr(1);
	else
		fname = "";
	d = 1.;
	if (!nrn_fw_eq(frin, stdin))
		IGNORE(nrn_fw_fclose(frin));
	frin = nrn_fw_set_stdin();
	if (fname[0] != 0) {
		if ((frin = nrn_fw_fopen(fname, "r")) == (NrnFILEWrap *)0)
		{
			const char* retry;
			retry = expand_env_var(fname);
			if ((frin = nrn_fw_fopen(retry, "r")) == (NrnFILEWrap *)0) {
				d = 0.;
				frin = nrn_fw_set_stdin();
			}
		}
	}
	errno = 0;
	ret();
	pushx(d);
}

void wopen(void)		/* open file for writing */
{
	char *fname;
	double d;

	if (ifarg(1))
		fname = gargstr(1);
	else
		fname = "";
	d = 1.;
	if (fout != stdout)
 	        ERRCHK(IGNORE(fclose(fout));) 
	fout = stdout;
	if (fname[0] != 0)
 		ERRCHK(if ((fout = fopen(expand_env_var(fname), "w")) == (FILE *)0)
		{
			d = 0.;
			fout = stdout;
		})
	errno = 0;
	ret();
	pushx(d);
}

const char* expand_env_var(const char* s) {
	static HocStr* hs;
	const char* cp1;
	char* cp2;
	int n;
	int begin = 1; /* only needed for mac when necessary to prepend a : */
	if (!hs) { hs = hocstr_create(256); }
	hocstr_resize(hs, strlen(s) + 2);
	for (cp1=s, cp2 = hs->buf + begin; *cp1; ++cp1) {
		if (*cp1 == '$' && cp1[1] == '(') {
			char* cp3;
			char buf[200];
			cp1 += 2;
			for (cp3 = buf; *cp1 && *cp1 != ')'; ++cp1) {
				*cp3++ = *cp1;
				assert(cp3 - buf < 200);
			}
			if (*cp1) {
				*cp3 = '\0';
				if (strcmp(buf, "NEURONHOME") == 0) {
					cp3 = neuron_home;
				}else{
					cp3 = getenv(buf);
				}
				if (cp3) {
					n = cp2 - hs->buf;
					hocstr_resize(hs, n + strlen(cp3) + strlen(s) + 2);
					cp2 = hs->buf + n;
					while(*cp3) {
						*cp2++ = *cp3++;
					}
				}
			}else{
				--cp1;
			}
		}else{
			*cp2++ = *cp1;
		}
	}
	*cp2 = '\0';
#if MAC && !defined(DARWIN)
/* convert / to : */
	for (cp1 = hs->buf+begin; *cp1; ++cp1) {
		if (*cp1 == '/') {
			*cp1 = ':';
		}
	}
/* if a : in the original name then assume already mac and done */
/* if $ in original name then done since NEURONHOME already has correct prefix */
/* if first is : then remove it, otherwise prepend it */
	if (!strchr(s, ':') && !strchr(s, '$')) {
		if (hs->buf[begin] == ':') {
			begin = 2;
		}else{
			begin = 0;
			hs->buf[0] = ':';
		}
	}
	for (cp1 = hs->buf+begin , cp2 = cp1; *cp1 ;) {
		if (cp1[0] == ':' && cp1[1] == '.') {
			if (cp1[2] == ':') {
				cp1 += 2;
				continue;
			}else if (cp1[2] == '.' && cp1[3] == ':') {
				cp1 += 3;
				*cp2++ = ':';
				continue;
			}
		}
		*cp2++ = *cp1++;
	}
	*cp2 = '\0';
#endif
	return hs->buf + begin;
}

int hoc_xopen_file_size_;
char* hoc_xopen_file_;

char* hoc_current_xopen(void) {
	return hoc_xopen_file_;
}

int hoc_xopen1(const char* name, const char* rcs) /* read and execute a hoc program */
{
/*printf("hoc_xopen1 %d %s\n", strlen(name), name);*/
	NrnFILEWrap *savfin;
	int savpipflag, save_lineno;
	char* savname;
	char* fname = strdup(name);
	assert(fname);
#if 1
	if (rcs) {
	   if (rcs[0] != '\0') {
		int sz = 2*(strlen(rcs) + strlen(name)) + 20;
		free(fname);
		fname = emalloc(sz);
		Sprintf(fname, "co -p%s %s > %s-%s", rcs, name,
			name, rcs);
 		ERRCHK(if (system(fname) != 0) { 
		    free(fname);
		    hoc_execerror(name, "\nreturned error in hoc_co system call");
		})
		Sprintf(fname, "%s-%s", name, rcs);
	   }
	}else if (hoc_retrieving_audit()) {
		hoc_xopen_from_audit(fname);
		free(fname);
		return 0;
	}
#endif
	savpipflag = pipeflag;
	savfin = fin;
	pipeflag = 0;

 	errno = EINTR; 
 	while (errno == EINTR) {
	   errno = 0; 
#if MAC
	   if ((fin = nrn_fw_fopen(fname, "rb")) == NULL) {
#else
	   if ((fin = nrn_fw_fopen(fname, "r")) == NULL) {
#endif
		const char* retry;
		retry = expand_env_var(fname);
		free(fname);
		assert((fname = strdup(retry)));
#if MAC
		if ((fin = nrn_fw_fopen(retry, "rb")) == NULL) {
#else
		if ((fin = nrn_fw_fopen(retry, "r")) == NULL) {
#endif
			fin = savfin;
			pipeflag = savpipflag;
			free(fname);
			execerror("Can't open ", retry);
		}
	   }
	}

	save_lineno = hoc_lineno;
	hoc_lineno = 0;
	assert((savname = strdup(hoc_xopen_file_)));
	if (strlen(fname) >= hoc_xopen_file_size_) {
		hoc_xopen_file_size_ = strlen(fname) + 100;
		hoc_xopen_file_ = erealloc(hoc_xopen_file_, hoc_xopen_file_size_);
	}
	strcpy(hoc_xopen_file_, fname);
	if (fin) {
		hoc_audit_from_xopen1(fname, rcs);
		IGNORE(hoc_xopen_run((Symbol *)0, (char *)0));
	}
	if (fin && !nrn_fw_eq(fin,stdin)) {
 		ERRCHK(IGNORE(nrn_fw_fclose(fin));)
	}
	fin = savfin;
	pipeflag = savpipflag;
	if (rcs && rcs[0]) {
		unlink(fname);
	}
	free(fname);
	hoc_xopen_file_[0] = '\0';
	hoc_lineno = save_lineno;
	strcpy(hoc_xopen_file_, savname);
	return 0;
}

void xopen(void)		/* read and execute a hoc program */
{

	if (ifarg(2)) {
		hoc_xopen1(gargstr(1), gargstr(2));
	}else{
		hoc_xopen1(gargstr(1), 0);
	}
	ret();
	pushx(1.);
}

void Fprint(void)	/* fprintf function */
{
	char* buf;
	double d;

	hoc_sprint1(&buf, 1);
	d = (double)fprintf(fout, "%s", buf);
	ret();
	pushx(d);
}

void PRintf(void)	/* printf function */
{
	char* buf;
	double d;

	hoc_sprint1(&buf, 1);
	d = (int)strlen(buf);
	NOT_PARALLEL_SUB(plprint(buf);)
	fflush(stdout);
	ret();
	pushx(d);
}


void hoc_Sprint(void)    /* sprintf function */
{ 
	char **cpp;
	char *buf;
	/* This is not guaranteed safe since we may be pointing to double */
	cpp = hoc_pgargstr(1);
	hoc_sprint1(&buf, 2);
	hoc_assign_str(cpp, buf);
        ret();
	pushx(1.);
}

double hoc_scan(FILE* fi)
{
	double d;
	char fs[256];

	for(;;) {
		if (fscanf(fi, "%255s", fs) == EOF) {
			execerror("EOF in fscan", (char *)0);
		}
		if (fs[0] == 'i' || fs[0] == 'n' || fs[0] == 'I' || fs[0] == 'N') {
			 continue;
		}
		if (sscanf(fs, "%lf", &d) == 1) {
			/* but if at end of line, leave at beginning of next*/
			if(fscanf(fi, "\n")) {;} /* ignore return value */
			break;
		}
	}
	return d;
}

double hoc_fw_scan(NrnFILEWrap* fi) {
	double d;
	char fs[256];

	for(;;) {
		if (nrn_fw_fscanf(fi, "%255s", fs) == EOF) {
			execerror("EOF in fscan", (char *)0);
		}
		if (fs[0] == 'i' || fs[0] == 'n' || fs[0] == 'I' || fs[0] == 'N') {
			 continue;
		}
		if (sscanf(fs, "%lf", &d) == 1) {
			/* but if at end of line, leave at beginning of next*/
			nrnignore = nrn_fw_fscanf(fi, "\n");
			break;
		}
	}
	return d;
}

void Fscan(void)		/* read a number from input file */
{
	double d;
	NrnFILEWrap *fi;

	if (nrn_fw_eq(frin, stdin)) {
		fi = fin;
	}else{
		fi = frin;
	}
	d = hoc_fw_scan(fi);
	ret();
	pushx(d);
}

void hoc_Getstr(void)	/* read a line (or word) from input file */
{
	char* buf;
	char **cpp;
	NrnFILEWrap* fi;
	int word = 0;
	if (nrn_fw_eq(frin, stdin)) {
		fi = fin;
	}else{
		fi = frin;
	}
	cpp = hoc_pgargstr(1);
	if (ifarg(2)) {
		word = (int)chkarg(2, 0., 1.);
	}
	if (word) {
		buf = hoc_tmpbuf->buf;
		if(nrn_fw_fscanf(fi, "%s", buf) != 1) {
			execerror("EOF in getstr", (char*)0);
		}
	}else{
		if ((buf = fgets_unlimited(hoc_tmpbuf, fi)) == (char *)0) {
			execerror("EOF in getstr", (char *)0);
		}
	}
	hoc_assign_str(cpp, buf);
	ret();
	pushx((double)strlen(buf));
}

void hoc_sprint1(char** ppbuf, int argn) {	/* convert args to right type for conversion */
	/* argn is argument number where format is */
	static HocStr* hs;
	char *pbuf, *pfmt, *pfrag, frag[120];
	int n, convflag, lflag, didit, hoc_argtype();
	char *fmt, *cp;

	if (!hs) { hs = hocstr_create(512); }
	fmt = gargstr(argn++);
 	convflag = lflag = didit = 0;
	pbuf = hs->buf;
	pfrag = frag;
	*pfrag = 0;
	*pbuf = 0;

	for (pfmt = fmt; *pfmt; pfmt++)
	{
		*pfrag++ = *pfmt;
		*pfrag = 0;
		if (convflag) { switch (*pfmt)
		 {

		case 'l':
			lflag += 1;
			break;

		case 'd':
		case 'o':
		case 'x':
			if (lflag) {
				if (lflag == 1) {
					pfrag[1] = pfrag[0];
					pfrag[0] = pfrag[-1];
					pfrag[-1] = 'l';
				}
				Sprintf(pbuf, frag, (long long)*getarg(argn));
			}else{
				Sprintf(pbuf, frag, (int)*getarg(argn));
			}
			didit = 1;
			break;

		case 'c':
			Sprintf(pbuf, frag, (char)*getarg(argn));
			didit = 1;
			break;

		case 'f':
		case 'e':
		case 'g':
			Sprintf(pbuf, frag, *getarg(argn));
			didit = 1;
			break;

		case 's':
			if (hoc_is_object_arg(argn)) {
				char* hoc_object_name();
				Object** hoc_objgetarg();
				cp = hoc_object_name(*hoc_objgetarg(argn));
			}else{
				cp = gargstr(argn);
			}
			n = pbuf - hs->buf;
			hocstr_resize(hs, n + strlen(cp) + 100);
			pbuf = hs->buf + n;
			Sprintf(pbuf, frag, cp);
			didit = 1;
			break;

		case '%':
			pfrag[-1] = 0;
			strcpy(pbuf, frag);
			didit = 1;
			argn--; /* an arg was not consumed */
			break;

		default:
			break;
		 }
		}else if (*pfmt == '%') {
			convflag = 1;
		}else if (pfrag - frag > 100) {
			n = pbuf - hs->buf;
			hocstr_resize(hs, n + strlen(frag) + 100);
			pbuf = hs->buf + n;
			Sprintf(pbuf, "%s", frag);
			pfrag = frag;
			*pfrag = 0;
			while (*pbuf) {	pbuf++; }
		}

		if (didit)
		{
			int n;
			argn++;
			lflag = 0;
			convflag = 0;
			didit = 0;
			pfrag = frag;
			*pfrag = 0;
			while (*pbuf) {	pbuf++; }
			n = pbuf - hs->buf;
			hocstr_resize(hs, n + 100);
			pbuf = hs->buf + n;
		}
	}
	if (pfrag != frag)
		Sprintf(pbuf, "%s", frag);
	*ppbuf = hs->buf;
}

#if defined(__TURBOC__) || defined(__GO32__) || defined(WIN32) || defined(MAC)
static FILE* oc_popen(char* cmd, char* type) {
	FILE* fp;
	char buf[1024];
	assert(strlen(cmd) + 20 < 1024);
	sprintf(buf, "sh %s > hocload.tmp", cmd);
	if (system(buf) != 0) {
		return (FILE*)0;
	}else if ( (fp = fopen("hocload.tmp", "r")) == (FILE*)0) {
		return (FILE*)0;
	}else{
		return fp;
	}
}
static void oc_pclose(FILE* fp) {
	fclose(fp);
	unlink("hocload.tmp");
}
#else
#define oc_popen popen
#define oc_pclose pclose
#endif

static int hoc_Load_file(int, const char*);

static void hoc_load(const char* stype)
{
	int i=1;
	char* s;
	Symbol* sym;
	char cmd[1024];
	FILE* p;
	char file[1024], *f;

	while(ifarg(i)) {
		s = gargstr(i);
		++i;
		sym = hoc_lookup(s);
		if (!sym || sym->type == UNDEF) {
			assert(strlen(stype) + strlen(s) + 50 < 1024);
			sprintf(cmd, "$NEURONHOME/lib/hocload.sh %s %s %d", stype, s, hoc_pid());
			p = oc_popen(cmd, "r");
			if (p) {
				f = fgets(file, 1024, p);
				if (f) {
					f[strlen(f)-1] = '\0';
				}
				oc_pclose(p);
				if (f) {
		fprintf(stderr, "Getting %s from %s\n", s, f);
					hoc_Load_file(0,f);
				}else{
fprintf(stderr, "Couldn't find a file that declares %s\n", s);
				}
			}else{
				hoc_execerror("can't run:", cmd);
			}
		}
	}
}

Pfri p_hoc_load_java;
void hoc_load_java(void) {
	int r = 0;
	if (p_hoc_load_java) {	
		r =  (*p_hoc_load_java)();
	}
	ret();
	pushx((double)r);
}

void hoc_load_proc(void) {
	hoc_load("proc");
	ret();
	pushx(1.);
}
void hoc_load_func(void) {
	hoc_load("func");
	ret();
	pushx(1.);
}
void hoc_load_template(void) {
	hoc_load("begintemplate");
	ret();
	pushx(1.);
}

void hoc_load_file(void) {
	int iarg = 1;
	int i = 0;
	if (hoc_is_double_arg(iarg)) {
		i = (int)chkarg(iarg, 0., 1.);
		iarg = 2;
	}
	if (!ifarg(iarg+1) || !hoc_lookup(gargstr(iarg+1))) {
		i = hoc_Load_file(i, gargstr(iarg));
	}
	ret();
	pushx((double)i);
}

static int hoc_Load_file(int always, const char* name) {
	/*
	  if always is 0 then
	  xopen only if file of that name not already loaded with one of
		the load_xxx functions
		and search in the current, $HOC_LIBRARY_PATH,
		$NEURONHOME/lib/hoc directories (in that order) for
		the file if there is no directory prefix.
		Temporarily change to the directory containing the file so
		that it can xopen files relative to its location.
	*/
#define hoc_load_file_size_ 1024
	static hoc_List* loaded;
	hoc_Item* q;
	int b, is_loaded;
	int goback;
	char expname[hoc_load_file_size_];
	const char *base;
	char path[hoc_load_file_size_], old[hoc_load_file_size_];
	char fname[hoc_load_file_size_], cmd[hoc_load_file_size_];
#if USE_NRNFILEWRAP
	int f;	
#else
	FILE* f;
#endif

	old[0] = '\0';
	goback = 0;
	/* has the file already been loaded */
	is_loaded = 0;
	if (!loaded) {
		loaded = hoc_l_newlist();
	}
	ITERATE(q, loaded) {
		if (strcmp(STR(q), name) == 0) {
			if (!always) {
				return 1;
			}else{
				is_loaded = 1;
			}
		}
	}
	
	/* maybe the name already has an explicit path */
	expname[hoc_load_file_size_ - 1] = '\0';
	strncpy(expname, expand_env_var(name), hoc_load_file_size_);
	assert(expname[hoc_load_file_size_ - 1] == '\0');
	name = expname;
	if ((base = strrchr(name, '/')) != NULL) {
		strncpy(path, name, base-name);
		path[base-name] = '\0';
		++base;
#if USE_NRNFILEWRAP
		f = nrn_fw_readaccess(name);
#else
		f = fopen(name, "r");
#endif
	}else{
		base = name;
		path[0] = '\0';
		/* otherwise find the file in the default directories */
#if USE_NRNFILEWRAP
		f = nrn_fw_readaccess(base);
#else
		f = fopen(base, "r"); /* cwd */
#endif
#if !MAC
		if (!f) { /* try HOC_LIBRARY_PATH */
			int i;
			char* hlp;
			hlp = getenv("HOC_LIBRARY_PATH");
			while(hlp && *hlp) {
				char* cp =  strchr(hlp, ':');
				if (!cp) {
					cp = strchr(hlp, ' ');
				}
				if (!cp) {
					cp = hlp + strlen(hlp);
				}
				assert(cp-hlp < hoc_load_file_size_);
				strncpy(path, hlp, cp-hlp);
				path[cp-hlp] = '\0';
				if (*cp) {
					hlp = cp+1;
				}else{
					hlp = 0;
				}
				if (path[0]) {
					sprintf(fname, "%s/%s", path, base);
#if USE_NRNFILEWRAP
					f = nrn_fw_readaccess(expand_env_var(fname));
#else
					f = fopen(expand_env_var(fname), "r");
#endif
					if (f) {
						break;
					}
				}else{
					break;
				}
			}
		}
#endif
		if (!f) { /* try NEURONHOME/lib/hoc */
			sprintf(path, "$(NEURONHOME)/lib/hoc");
			assert(strlen(path) + strlen(base) + 1 < hoc_load_file_size_);
			sprintf(fname, "%s/%s", path, base);
#if USE_NRNFILEWRAP
			f = nrn_fw_readaccess(expand_env_var(fname));
#else
			f = fopen(expand_env_var(fname), "r");
#endif
		}
	}
	/* add the name to the list of loaded packages */
	if (f) {
		if (!is_loaded) {
			hoc_l_lappendstr(loaded, name);
		}
#if USE_NRNFILEWRAP == 0
		fclose(f);
#endif
		b = 1;
	}else{
		b = 0;
		hoc_warning("Couldn't find:", name);
		path[0] = '\0';
	}
	/* change to the right directory*/
	if (b && path[0]) {
		goback = (getcwd(old, 1000) != 0);
		errno = 0;
		if (chdir(expand_env_var(path)) == -1) {
			hoc_warning("Couldn't change directory to:", path);
			path[0] = '\0';
			b = 0;
		}
/*printf("load_file cd to %s\n", path);*/
	}
	/* xopen the file */
	if (b) {
/*printf("load_file xopen %s\n", base);*/
		assert(strlen(base) + 50 < hoc_load_file_size_);
		sprintf(cmd, "hoc_ac_ = execute1(\"{xopen(\\\"%s\\\")}\")\n", base);
		b = hoc_oc(cmd);
		b = (int)hoc_ac_;
	}
	/* change back */
	if (path[0] && goback) {
		if (hoc_chdir(old) == -1) {
			hoc_warning("Couldn't change directory back to:", old);
			b = 0;
		}
/*printf("load_file cd back to %s\n", old);*/
	}
	
	return b;
}

void hoc_getcwd(void) {
	int len;
	static char* buf;
	if (!buf) {
		buf = emalloc(hoc_load_file_size_);
	}
	if (!getcwd(buf, hoc_load_file_size_)) {
		hoc_execerror("getcwd failed. Perhaps the path length is > hoc_load_file_size_", (char*)0);
	}
#if defined(WIN32)
{extern char* hoc_back2forward();
	strcpy(buf, hoc_back2forward(buf));
}
#endif
	len = strlen(buf);
#if defined(MAC)
	if (buf[len-1] != ':') {
		buf[len] = ':';
		buf[len+1] = '\0';
	}
#else
	if (buf[len-1] != '/') {
		buf[len] = '/';
		buf[len+1] = '\0';
	}
#endif
	hoc_ret();
	hoc_pushstr(&buf);
}

void hoc_machine_name(void)
{
#if !defined(__GO32__) && !defined(WIN32) && !defined(MAC)
  				       /*----- functions called -----*/
				       /*----- local  variables -----*/
    char	buf[20];

    gethostname(buf, 20);
    hoc_assign_str(hoc_pgargstr(1), buf); 
#endif
    ret();
    pushx(0.);
}

int hoc_chdir(const char* path) {
	return chdir(expand_env_var(path));
}

void hoc_Chdir(void) {
	int i = hoc_chdir(gargstr(1));
	ret();
	pushx((double)i);
}

int nrn_is_python_extension;
int (*nrnpy_pr_callback)(char*);

void nrnpy_set_pr(int (*cb)(char*)) {
    if (nrn_is_python_extension) {
        nrnpy_pr_callback = cb;
    }
}

int nrnpy_pr(const char *fmt, ...) {
    int size = 0;
    char *p = NULL;
    va_list ap;

    if (!nrnpy_pr_callback) {
        va_start(ap, fmt);
        size = vprintf(fmt, ap);
        return size;
    }

    /* Determine required size */

    va_start(ap, fmt);
    size = vsnprintf(p, size, fmt, ap);
    va_end(ap);

    if (size < 0)
        return 0;

    size++;      /* For '\0' */
    p = malloc(size);
    if (p == NULL)
        return 0;

    va_start(ap, fmt);
    size = vsnprintf(p, size, fmt, ap);
    if (size < 0) {
        free(p);
        return 0;
    }
    va_end(ap);

    (*nrnpy_pr_callback)(p);

    free(p);
    return size;
}

