/* stream commands for tcl */
#include <stdio.h>
#include <tcl.h>

#define STATIC

int streamOpen(),
    streamClose(),
    streamGets(),
    streamPuts(),
    streamEof(),
    streamName(),
    streamErr(),
    streamTell(),
    streamSeek();

static struct subcmd {
	int (*func)();
	char *name;
	int min;
	int max;
	char *args;
} commands[] = {
	{ streamOpen, "open", 2, 2, "name mode" },
	{ streamClose, "close", 0, 0, "" },
	{ streamGets, "gets", 0, 0, "" },
	{ streamPuts, "puts", 1, 1, "line" },
	{ streamEof, "eof", 0, 0, "" },
	{ streamErr, "error", 0, 0, "" },
	{ streamName, "name", 0, 0, "" },
	{ streamTell, "tell", 0, 0, "" },
	{ streamSeek, "seek", 1, 2, "offset [whence]" },
};

static struct stream {
	struct stream *next;
	char *name;
	char *filename;
	char *error;
	FILE *fp;
} *streams = NULL;

save_err(s)
struct stream *s;
{
	char *strerror();
	extern int errno;
	char *name;

	name = strerror(errno);
	if(!name)
		return;

	if(s->error) ckfree(s->error);
	s->error = 0;
	s->error = ckalloc(strlen(name)+1);
	strcpy(s->error, name);
}

STATIC struct stream *get_stream(name)
char *name;
{
	struct stream *s;

	for(s = streams; s; s = s->next)
		if(strcmp(name, s->name) == 0)
			break;
	return s;
}

STATIC struct stream *add_stream(name, filename, fp)
char *name;
char *filename;
FILE *fp;
{
	struct stream *s;

	s = (struct stream *)ckalloc(sizeof(struct stream)
		+ strlen(name) + 1
		+ (filename ? (strlen(filename)+1) : 0) );

	s->name = (char *)&s[1];
	strcpy(s->name, name);
	if(filename) {
		s->filename = s->name + strlen(name) + 1;
		strcpy(s->filename, filename);
	} else
		s->filename = NULL;
	s->fp = fp;
	s->error = NULL;
	s->next = streams;
	streams = s;
}

STATIC del_stream(name)
char *name;
{
	struct stream *p, *q;

	p = streams;
	q = NULL;
	while(p) {
		if(strcmp(p->name, name) == 0) {
			if(q)
				q->next = p->next;
			else
				streams = p->next;
			if(p->error) ckfree(p->error);
			ckfree(p);
			return 1;
		}
		q = p;
		p = p->next;
	}
	return 0;
}

STATIC cmdStream(cmdname, interp, argc, argv)
char *cmdname;
Tcl_Interp *interp;
int argc;
char **argv;
{
	struct subcmd *cmdv = commands;
	int cmdc = sizeof commands / sizeof *commands;

	char *handle, *action;

	char *err;
	char *name;
	char *args;

	err = "wrong # args in";
	action = "stream";
	name = "command";
	args = "args...";

	if(argc < 3)
		goto error;

	argv++; --argc;
	handle = *argv++; --argc;
	action = *argv++; --argc;

	while(cmdc > 0) {
		if(strcmp(action, cmdv->name) == 0) {
			int result;

			name = cmdv->name;
			args = cmdv->args;
			if(argc < cmdv->min
			   || (cmdv->max != -1 && argc > cmdv->max))
				goto error;
			result = (*cmdv->func)(interp, handle, argc, argv);
			return result;
		}
		cmdv++;
		cmdc--;
	}
	err = "unknown subcommand";
error:
	sprintf(interp->result, "%s %s:  should be \"%.50s handle %s %s\"",
		err, action, cmdname, name, args);
	return TCL_ERROR;
}

stream_init(interp)
Tcl_Interp *interp;
{
	Tcl_CreateCommand(interp, "stream",
		cmdStream, (ClientData) "stream", NULL);
	add_stream("stdin", (char *)NULL, stdin);
	add_stream("stdout", (char *)NULL, stdout);
	add_stream("stderr", (char *)NULL, stderr);
}

STATIC int streamOpen(interp, handle, argc, argv)
Tcl_Interp *interp;
char *handle;
int argc;
char **argv;
{
	FILE *fp;

	if(!(fp = fopen(argv[0], argv[1]))) {
		char *strerror();
		extern int errno;
		char *s = strerror(errno);
		if(s)
			sprintf(interp->result, "%s: %s", argv[0], s);
		else
			Tcl_Return(interp, (char *)NULL, TCL_STATIC);
		return TCL_ERROR;
	}

	add_stream(handle, argv[0], fp);
	return TCL_OK;
}

STATIC not_open(interp, name)
Tcl_Interp *interp;
char *name;
{
	sprintf(interp->result,
		"%.50s is not an open stream", name);
}

STATIC int streamClose(interp, handle, argc, argv)
Tcl_Interp *interp;
char *handle;
int argc;
char **argv;
{
	struct stream *s = get_stream(handle);

	if(!s) {
		not_open(interp, handle);
		return TCL_ERROR;
	}

	fclose(s->fp);
	del_stream(handle);
	return TCL_OK;
}

STATIC int streamGets(interp, handle, argc, argv)
Tcl_Interp *interp;
char *handle;
int argc;
char **argv;
{
	struct stream *s = get_stream(handle);
	char *buffer = ckalloc(BUFSIZ);
	char *ptr, *strchr();

	if(!s) {
		not_open(interp, handle);
		ckfree(buffer);
		return TCL_ERROR;
	}

	if(fgets(buffer, BUFSIZ, s->fp)) {
		ptr = strchr(buffer, '\n');
		if(ptr) 
			*ptr = 0;
		Tcl_Return(interp, buffer, TCL_VOLATILE);
	}
	else
	{
		Tcl_Return(interp, (char *)NULL, TCL_STATIC);
	}
	ckfree(buffer);
	return TCL_OK;
}

STATIC int streamPuts(interp, handle, argc, argv)
Tcl_Interp *interp;
char *handle;
int argc;
char **argv;
{
	struct stream *s = get_stream(handle);
	char buffer[BUFSIZ];

	if(!s) {
		not_open(interp, handle);
		return TCL_ERROR;
	}

	if(fputs(argv[0], s->fp) == EOF) save_err(s);
	if(putc('\n', s->fp) == EOF) save_err(s);

	return TCL_OK;
}

STATIC int streamTell(interp, handle, argc, argv)
Tcl_Interp *interp;
char *handle;
int argc;
char **argv;
{
	struct stream *s = get_stream(handle);
	long offset, ftell();

	if(!s) {
		not_open(interp, handle);
		return TCL_ERROR;
	}

	sprintf(interp->result, "%ld", offset = ftell(s->fp));
	if(offset == -1) save_err(s);
	return TCL_OK;
}

STATIC int streamEof(interp, handle, argc, argv)
Tcl_Interp *interp;
char *handle;
int argc;
char **argv;
{
	struct stream *s = get_stream(handle);

	if(!s) {
		not_open(interp, handle);
		return TCL_ERROR;
	}

	sprintf(interp->result, "%d", !!feof(s->fp));
	return TCL_OK;
}

STATIC int streamErr(interp, handle, argc, argv)
Tcl_Interp *interp;
char *handle;
int argc;
char **argv;
{
	struct stream *s = get_stream(handle);

	if(!s) {
		not_open(interp, handle);
		return TCL_ERROR;
	}

	if(ferror(s->fp) && s->error)
		Tcl_Return(interp, s->error, TCL_VOLATILE);
	else
		Tcl_Return(interp, (char *)NULL, TCL_STATIC);

	return TCL_OK;
}

STATIC int streamName(interp, handle, argc, argv)
Tcl_Interp *interp;
char *handle;
int argc;
char **argv;
{
	struct stream *s = get_stream(handle);

	if(!s) {
		not_open(interp, handle);
		return TCL_ERROR;
	}

	if(s->filename)
		Tcl_Return(interp, s->filename, TCL_VOLATILE);
	else
		Tcl_Return(interp, (char *)NULL, TCL_STATIC);

	return TCL_OK;
}

STATIC int streamSeek(interp, handle, argc, argv)
Tcl_Interp *interp;
char *handle;
int argc;
char **argv;
{
	struct stream *s = get_stream(handle);
	int whence;
	long offset, fseek();
	long strtol();
	char *endptr;

	if(!s) {
		not_open(interp, handle);
		return TCL_ERROR;
	}

	if(argc > 1) whence = atoi(argv[1]);
	else whence = 0;

	offset = strtol(argv[0], &endptr, 0);

	offset = fseek(s->fp, offset, whence);
	sprintf(interp->result, "%ld", offset);
	if(offset == -1) save_err(s);

	return TCL_OK;
}
