/*
 * Copyright (c) 1991-2004 by Wayne C. Gramlich.
 * All rights reserved.
 */

/*
 * This file implements the in_stream type operations in ANSI-C.
 */

#ifndef ERROR_EXPORTS_H
#include "error_exports.h"
#endif

#ifndef MEMORY_H
#include "memory.h"
#endif

#ifndef PORT_H
#include "port.h"
#endif

#ifndef STR_EXPORTS_H
#include "str_exports.h"
#endif

#ifndef UNIX_ASSERT_H
#include "unix_assert.h"
#endif

#ifndef UNIX_FCNTL_H
#include "fcntl.h"
#endif

#ifndef UNIX_STDIO_H
#include "unix_stdio.h"
#endif

#ifndef UNIX_STDLIB_H
#include "unix_stdlib.h"
#endif

#ifdef OLD
#ifndef UNIX_SYS_MMAN_H
#include "unix_sys_mman.h"
#endif
#endif /* OLD */

#ifndef UNIX_SYS_STAT_H
#include "unix_sys_stat.h"
#endif

#ifndef UNIX_UNISTD_H
#include "unix_unistd.h"
#endif

#define END_OF_FILE	0xffffffff		/* End-of-File */

typedef void *Out_stream;
typedef void *String;

typedef struct In_stream_struct *In_stream;
struct In_stream_struct {
	unsigned	character;	/* Previous character */
	String		file_name;	/* File name */
	Stdio		in_file;	/* Input file */
	int		is_peeked;	/* 1 => Have already read next chr. */
};

In_stream	in_stream___initial;
In_stream	in_stream__standard;

extern String		string__read_only_copy_mode(String);
extern Str		string__allocate(void);
extern Str		string__unix_string(String);
extern Str		string__map(In_stream);
extern void		string__print(String, Out_stream);
extern void		string__put(String, Out_stream);
extern String		string__construct(Str, unsigned);
extern void		out_stream__unsigned_print(Out_stream,
						     unsigned, unsigned);
#ifdef WINDOWS
extern Str program___name;
#endif /* WINDOWS */

extern In_stream	in_stream__open_read(String);

void
in_stream__external__initialize(void)
{
	in_stream___initial = memory_alloc(In_stream);
	in_stream___initial->file_name = "\011<initial>";
	in_stream___initial->in_file = stdin;
	in_stream___initial->character = END_OF_FILE;
	in_stream___initial->is_peeked = 1;

	in_stream__standard = memory_alloc(In_stream);
	in_stream__standard->file_name = "\007<stdin>";
	in_stream__standard->in_file = stdin;
	in_stream__standard->character = END_OF_FILE;
	in_stream__standard->is_peeked = 0;
}

/*
 * in_stream__address_get(in_stream)
 *	This routine will return the address of {in_stream}.
 */
unsigned
in_stream__address_get(
	In_stream	in_stream)
{
	return (unsigned)(in_stream);
}

/*
 * in_stream__character_get(in_stream)
 *	This routine will read one character from {in_stream} and return it.
 */
unsigned
in_stream__character_get(
	In_stream	in_stream)
{
	int		chr;

	if (in_stream->is_peeked) {
		in_stream->is_peeked = 0;
		chr = in_stream->character;
        } else {
		chr = getc(in_stream->in_file);
	}
	return chr & 255;
}

/*
 * in_stream__close(in_stream)
 *	This routine will close "in_stream", if it is appropriate.
 */
void
in_stream__close(
	In_stream	in_stream)
{
	if ((in_stream->in_file != stdin) &&
	    (in_stream->in_file != in_stream___initial->in_file)) {
		(void)fclose(in_stream->in_file);
		in_stream->character = END_OF_FILE;
		in_stream->file_name = in_stream___initial->file_name;
		in_stream->in_file = in_stream___initial->in_file;
		in_stream->is_peeked = 1;
	}
}

/*
 * in_stream__descriptor_open(descriptor)
 *	This routine will return an "in_stream" object that
 *	is connected to "descriptor".  If an error occurs,
 *	??@in_stream is returned.
 */
In_stream
in_stream__descriptor_open(
	unsigned	descriptor)
{
	Stdio		in_file;
	In_stream	in_stream;

	in_file = fdopen(descriptor, "r");
	if (in_file == (Stdio)0) {
		in_stream = in_stream___initial;
	} else {
		in_stream = memory_alloc(In_stream);
		in_stream->file_name = "\012descriptor";
		in_stream->in_file = in_file;
		in_stream->character = END_OF_FILE;
		in_stream->is_peeked = 0;
	}
	return in_stream;
}

/*
 * in_stream__eof_get(in_stream)
 *	This routine will return 1 if "in_stream" is at end-of-file and
 *	0 otherwise.
 */
int
in_stream__eof_get(
	In_stream	in_stream)
{
	unsigned	character;

	if (in_stream->is_peeked) {
		character = in_stream->character;
	} else {
		character = (unsigned)getc(in_stream->in_file);
		in_stream->character = character;
		in_stream->is_peeked = 1;
	}
	return (character == END_OF_FILE);
}

/*
 * in_stream__file_name_get(in_stream)
 *	This routine will return the file name associated with "in_stream".
 */
String
in_stream__file_name_get(
	In_stream	in_stream)
{
	return in_stream->file_name;
}

/*
 * in_stream__float_read(in_stream)
 *	This routine will read and return a float from "in_stream".
 */
int
in_stream__float_read(
	In_stream	in_stream)
{
	float f;

	(void)fscanf(in_stream->in_file, "%f%", &f);
	return *((int *)&f);
}

/*
 * in_stream__is_open_get(in_stream)
 *	This routine will return 1 if "in_stream" is open and 0 otherwise.
 */
int
in_stream__is_open_get(
	In_stream	in_stream)
{
	return (in_stream->in_file != in_stream___initial->in_file);
}

/*
 * in_stream__open(file_name)
 *	This routine will create and return an input stream object
 *	and connect it to "file_name".
 */
In_stream
in_stream__open(
	String		file_name)
{
	Stdio		in_file;
	In_stream	in_stream;
	Str		unix_file_name;

	unix_file_name = string__unix_string(file_name);
	/* (void)printf("in_stream__open('%s'(=0x%x))...",
	   unix_file_name, (void *)unix_file_name); */
	file_name = string__read_only_copy(file_name);
	in_stream = memory_alloc(In_stream);
	in_file = fopen(unix_file_name, "r");
	if (in_file == (Stdio)0) {
		in_stream->file_name = in_stream___initial->file_name;
		in_stream->in_file = in_stream___initial->in_file;
		in_stream->character = END_OF_FILE;
		in_stream->is_peeked = 1;
		/* (void)printf("failed\n"); */
	} else {
		in_stream->file_name = file_name;
		in_stream->in_file = in_file;
		in_stream->character = END_OF_FILE;
		in_stream->is_peeked = 0;
		/* (void)printf("succeeded\n"); */
	}
	return in_stream;
}

#ifdef BUGGY
Str
in_stream__map(
	In_stream	in_stream)
{
	int		fd;
	caddr_t		buffer;
	unsigned	page_size;
	unsigned	size;
	Stat_rec	status;

	fd = fileno(in_stream->in_file);
	assert(fstat(fd, &status) == 0);
	page_size = sysconf(_SC_PAGESIZE);
	size = (unsigned)status.st_size;
	buffer = mmap(0, (size_t)size, PROT_READ, MAP_SHARED, fd, (off_t)0);
	assert(buffer != (caddr_t)0);

	/* Map a page onto the beginning: */
	fd = open("/dev/zero", O_RDONLY, 0);
	assert(fd >= 0);
	assert(mmap(buffer - page_size, (size_t)page_size,
		    PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_FIXED,
		    fd, (off_t)0) == buffer - page_size);
	if (size % page_size == 0) {
		/* File fits evenly on pages => no terminating NULL: */
		assert(mmap(buffer + size, (size_t)page_size, PROT_READ,
			    MAP_PRIVATE | MAP_FIXED, fd, (off_t)0) ==
								buffer + size);
	}
	assert(close(fd) == 0);
	return string__construct((Str)buffer, size);
}
#endif /* BUGGY */

/*
 * in_stream__next(in_stream)
 *	This routine will get the next character from "in_stream".
 */
int
in_stream__next(
	In_stream	in_stream)
{
	unsigned	character;

	if (in_stream->is_peeked) {
		in_stream->is_peeked = 0;
		character = in_stream->character;
	} else {
		character = (unsigned)getc(in_stream->in_file);
		in_stream->character = character;
	}
	return character;
}

/*
 * in_stream__peek(in_stream)
 *	This routine will return the next character from "in_stream"
 *	without actually reading it.
 */
int
in_stream__peek(
	In_stream	in_stream)
{
	int		character;

	if (in_stream->is_peeked) {
		character = in_stream->character;
	} else {
		in_stream->is_peeked = 1;
		character = (unsigned)getc(in_stream->in_file);
		in_stream->character = character;
	}
	return character;
}

/*
 * in_stream__position_get(in_stream)
 *	This routine will return the current position of "in_stream".
 */ 
unsigned
in_stream__position_get(
	In_stream	in_stream)
{
	unsigned	position;

	position = ftell(in_stream->in_file);
	if (in_stream->is_peeked) {
		position--;
	}
	return position;
}

/*
 * in_stream__print(in_stream, out_stream)
 *	This routine will print "in_stream" to "out_stream".
 */
void
in_stream__print(
	In_stream	in_stream,
	Out_stream	out_stream)
{
	string__put("\013{file_name=", out_stream);
	string__print(in_stream->file_name, out_stream);
	string__put("\012, in_file=", out_stream);
	out_stream__unsigned_print(out_stream,
				     (unsigned)in_stream->in_file, 16);
	string__put("\001}", out_stream);
}

