
/*
 * Copyright (c) 1998 University of Southern California.
 * All rights reserved.                                            
 *                                                                
 * Redistribution and use in source and binary forms are permitted
 * provided that the above copyright notice and this paragraph are
 * duplicated in all such forms and that any documentation, advertising
 * materials, and other materials related to such distribution and use
 * acknowledge that the software was developed by the University of
 * Southern California, Information Sciences Institute.  The name of the
 * University may not be used to endorse or promote products derived from
 * this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 *
 */

#include <stdlib.h>
#include <sys/types.h>
#include <values.h>

#include <assert.h>

#ifndef WIN32
extern "C" off_t tell();
extern "C" double atof();
#endif

#include <tclcl.h>
#include "trace.h"
#include "nam_stream.h"

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

class NamStreamClass : public TclClass {
public:
	NamStreamClass() : TclClass("NamStream") {}
	TclObject* create(int argc, const char*const* argv) {
		if (argc < 5) 
			return 0;
		return NamStream::open(argv[4]);
	}
} NamStream_class;

int
NamStream::command(int argc, const char *const *argv)
{
	Tcl& tcl = Tcl::instance();

	if (0) {
	} else if (argc == 2 && strcmp(argv[1], "gets") == 0) {
		if (eof())
			return TCL_ERROR;
		// return a line
		char *buf = tcl.buffer();
		assert(4096 > TRACE_LINE_MAXLEN+1);  // can the buffer handle us?
		gets(buf, TRACE_LINE_MAXLEN);
		buf[TRACE_LINE_MAXLEN] = 0;  // paranoia
		tcl.result(buf);
		return TCL_OK;
	} else if (argc == 2 && strcmp(argv[1], "close") == 0) {
		close();
		return TCL_OK;
	} else if (argc == 2 && strcmp(argv[1], "eof") == 0) {
		tcl.resultf("%d", eof());
		return TCL_OK;
	};
	return (TclObject::command(argc, argv));
}

NamStream *
NamStream::open(const char *fn)
{
	if (strcmp(fn, "-") == 0 || strcmp(fn, "-.nam") == 0) {  // XXX
#if 1
		return new NamStreamPipe(fn);
#else
		fprintf(stderr, "nam does not currently support reading from stdin.\n");
		return NULL;
#endif
	};
	if (strcmp(fn + strlen(fn) - 3, ".gz") == 0 ||
	    strcmp(fn + strlen(fn) - 2, ".Z") == 0) {
#ifdef HAVE_ZLIB_H
		return new NamStreamCompressedFile(fn);
#else /* ! HAVE_ZLIB */
		fprintf(stderr, "nam not built with zlib; cannot read compressed files.\n");
		return NULL;
#endif /* HAVE_ZLIB */
	};
	/* hope we've got it now */
	return new NamStreamFile(fn);
}

/*
 * rgets (gets in reverse order)
 * isn't used by nam, but probably should be.
 */
char *
NamStream::rgets(char *buf, int len)
{
	int ch;

	/*
	 * prior-line \n current-line \n next-line
	 *
	 * Initially the cursor is on the n of next-line.
	 * read in current-line (which is behind us)
	 * return it
	 * leave the cursor on c of current line.
	 */

	/* first make sure we back over the prior newline, if any */
	if (seek(-1, SEEK_CUR) < 0)
		return NULL;
	ch = get_char();
	if (seek(ch == '\n' ? -2 : -1, SEEK_CUR) < 0)
		return NULL;
	/* now loop backwards until we get to the newline separating
	 * prior and current.
	 */
	off_t pos = tell();
	for(;;) {
		ch = get_char();
		if (ch == '\n')
			break;
		if (pos == 0) {
			// beginning of file
			if (seek(-1, SEEK_CUR) < 0)
				return NULL;
			break;
		}
		// back up a char & try again
		pos--;
		if (seek(-2, SEEK_CUR) < 0)
			return NULL;
	};
	/* we're just passed the newline for prior-line, or we're at 0 */
	/* read current-line, then reset the pointer there */
	gets(buf, len);
	if (pos != seek(pos, SEEK_SET))
		return NULL;
	return buf;
}

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

#if 0
NamStreamFile::NamStreamFile(int fd) : NamStream(fd)
{
	file_ = fdopen(fd, "r");
	is_open_ = (NULL != file_);
}
#endif /* 0 */

NamStreamFile::NamStreamFile(const char *fn) : NamStream(fn)
{
	file_ = fopen(fn, "r");
	is_open_ = (NULL != file_);
	if (!is_open_) 
		perror(fn);
}

char *
NamStreamFile::gets(char *buf, int len)
{
	return fgets(buf, len, file_);
}

char
NamStreamFile::get_char()
{
	return getc(file_);
}

off_t
NamStreamFile::seek(off_t offset, int whence)
{
	if (0 == fseek(file_, offset, whence))
		return ftell(file_);
	else return -1;
}

off_t
NamStreamFile::tell()
{
	return ftell(file_);
}

int
NamStreamFile::close()
{
	int e = fclose(file_);
	file_ = NULL;
	return e;
}

int
NamStreamFile::eof()
{
	return feof(file_);
}

int
NamStreamFile::read(char *buf, int size)
{
      return fread(buf, 1, size, file_);
}


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

#ifdef HAVE_ZLIB_H

/*
 * Beware:
 * nam *requires* zlib-1.1.3, as we trip over bugs in 1.1.2's gz* functions.
 */

NamStreamCompressedFile::NamStreamCompressedFile(const char *fn) : NamStream(fn)
{
#ifndef ZLIB_VERSION
	die("zlib version not specified.\n");
	int a, b, c;
	if (3 != sscanf(ZLIB_VERSION, "%d.%d.%d", &a, &b, &c)) {
		die("zlib version: unknown format.\n");
	};
	if (!(a > 1 ||
	    (a == 1 && b > 1) ||
	      (a == 1 && b == 1 && c >= 3)))
		die("zlib version is too old, nam requires 1.1.3.\n");
#endif

	file_ = gzopen(fn, "r");
	is_open_ = (NULL != file_);
}

char *
NamStreamCompressedFile::gets(char *buf, int len)
{
	char *b = gzgets(file_, buf, len);
	return b;
}

char 
NamStreamCompressedFile::get_char()
{
	return gzgetc(file_);
}

off_t
NamStreamCompressedFile::seek(off_t offset, int whence)
{
	if (whence == SEEK_END) {
		/*
		 * zlib doesn't support SEEK_END :-<
		 * Walk our way to the end-of-file.
		 */
		off_t p = gzseek(file_, 0, SEEK_SET), q;
#define STEP (16*1024)
		char buf[STEP];
		int count;
		for (;;) {
			/*
			 * Sigh.  We actually move all the bytes.  XXX
			 * (we can't lseek because we'd lseek
			 * past eof without knowing).
			 */
			count = gzread(file_, buf, STEP);
			if (count <= 0)
				break;
			p += count;
		};
		q = gztell(file_);
		assert (p == q);
		return q;
	} else {
		return gzseek(file_, offset, whence);
	};
}

off_t
NamStreamCompressedFile::tell()
{
	return gztell(file_);
}

int
NamStreamCompressedFile::close()
{
	int e = gzclose(file_);
	file_ = NULL;
	return e;
}

int
NamStreamCompressedFile::eof()
{
	return gzeof(file_);
}

int
NamStreamCompressedFile::read(char *buf, int size)
{
	int e = gzread(file_, buf, size);
	return e;
}


#endif /* HAVE_ZLIB */


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

FILE *NamStreamPipe::persistent_front_ = 0;
FILE *NamStreamPipe::persistent_back_ = 0;

NamStreamPipe::NamStreamPipe(const char *fn) :
	NamStream(fn), front_(NULL), back_(NULL), back_len_(0)
{
	/*
	 * As a special hack for nam we never close the files
	 * but instead keep them in persistent_*.
	 * This allows nam to open/close/reopen stdin
	 * while doing the right thing.
	 * Better suggestions are welcome; restructuring nam's
	 * front end (the Right Thing) was deemed harder.
	 *
	 * The really bad news about this hack is it makes this
	 * code not at all re-entrant (although one only has one stdin).
	 */
	if (persistent_front_) {
		front_ = persistent_front_;
		back_ = persistent_back_;
		if (-1 == fseek(back_, 0, SEEK_END))
			die("NamStreamPipe::NamStreamPipe fseek");
		back_len_ = ftell(back_);
		if (-1 == fseek(back_, 0, SEEK_SET))
			die("NamStreamPipe::NamStreamPipe fseek");
		is_open_ = 1;
	} else {
		/* dup stdin */
		persistent_front_ = front_ = fdopen(0, "r");
		persistent_back_ = back_ = tmpfile();
		is_open_ = front_ && back_;
	};
}

void
NamStreamPipe::insure_backing(off_t lim)
{
	if (lim <= back_len_)
		return;
	if (feof(front_))
		return;
	// read data up to lim
	off_t where = ftell(back_);  // save where we were
	fseek(back_, 0, SEEK_END);
#ifndef STEP
#define STEP (16*1024)
#endif
	char buf[STEP];
	while (back_len_ < lim) {
		/*
		 * We may need to set up the input
		 * in some kind of non-blocking mode if
		 * reading in STEP chunks is too bursty.
		 */
		int in_count = fread(buf, 1, STEP, front_);
		if (in_count <= 0)  // error or eof
			break;
		int out_count = fwrite(buf, 1, in_count, back_);
		if (out_count == -1)
			perror("error writing NamStreamPipe backing file.\n");
		else {
			if (out_count < in_count) 
				fprintf(stderr, "NamStreamPipe::insure_backing: out of space for nam backing file (in /tmp)\n");
			back_len_ += out_count;
		};
	};
	if (-1 == fseek(back_, where, SEEK_SET)) {
		die("fseek problem in NamStreamPipe::insure_backing.\n");
	};
}

char *
NamStreamPipe::gets(char *buf, int len)
{
	insure_backing(tell() + len);
	char *b = fgets(buf, len, back_);
	return b;
}

char
NamStreamPipe::get_char()
{
	insure_backing(tell() + 1);
	return getc(back_);
}

off_t
NamStreamPipe::seek(off_t offset, int whence)
{
	off_t lim;
	switch(whence) {
	case SEEK_SET: lim = offset; break;
	case SEEK_CUR: lim = tell() + offset; break;
	case SEEK_END: lim = MAXLONG; break; // XXX: MAXINT may not be max(off_t)
	}
	insure_backing(lim);
	if (0 == fseek(back_, offset, whence))
		return ftell(back_);
	else return -1;
}

off_t
NamStreamPipe::tell()
{
	return ftell(back_);
}

int
NamStreamPipe::close()
{
	/*
	 * Don't actually close them because we might need them later.
	 */
	int e = 0;
	// e = fclose(back_);
	// e = fclose(front_);
	back_ = front_ = NULL;
	return e;
}

int
NamStreamPipe::eof()
{
	if (feof(front_))
		return feof(back_);
	else return 0;
}

int
NamStreamPipe::read(char *buf, int len)
{
	insure_backing(tell() + len);
	int e = fread(buf, 1, len, back_);
	return e;
}

