//    This is part of the iostream library, providing input/output for C++.
//    Copyright (C) 1991 Per Bothner.
//
//    This library is free software; you can redistribute it and/or
//    modify it under the terms of the GNU Library General Public
//    License as published by the Free Software Foundation; either
//    version 2 of the License, or (at your option) any later version.
//
//    This library 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
//    Library General Public License for more details.
//
//    You should have received a copy of the GNU Library General Public
//    License along with this library; if not, write to the Free
//    Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

#ifdef __GNUG__
#pragma implementation
#endif
#define _STREAM_COMPAT
#include "ioprivate.h"
#include <iostream.h>
#include <stdio.h>  /* Needed for sprintf */
#include <ctype.h>
#include "floatio.h"

#define	BUF		(MAXEXP+MAXFRACT+1)	/* + decimal point */

//#define isspace(ch) ((ch)==' ' || (ch)=='\t' || (ch)=='\n')

extern streambuf not_open_filebuf;

istream::istream() : ios(&not_open_filebuf)
{
    _flags |= ios::dont_close;
    _gcount = 0;
    _tie = NULL;
}

istream::istream(streambuf *sb, ostream* tied) : ios(sb)
{
    _flags |= ios::dont_close;
    _gcount = 0;
    _tie = tied;
}

istream::~istream()
{
    if (!(_flags & (unsigned int)ios::dont_close))
	delete _strbuf;
    _flags |= ios::dont_close;
    _strbuf = &not_open_filebuf;
}

int skip_ws(streambuf* sb)
{
    int ch;
    for (;;) {
	ch = sb->sbumpc();
	if (ch == EOF || !isspace(ch))
	    return ch;
    }
}

istream& istream::get(char& c)
{
    if (ipfx1()) {
	int ch = _strbuf->sbumpc();
	if (ch == EOF) set(ios::eofbit|ios::failbit);
	else c = (char)ch;
    }
    return *this;
}

istream& istream::get(unsigned char& c)
{
    if (ipfx1()) {
	int ch = _strbuf->sbumpc();
	if (ch == EOF) set(ios::eofbit|ios::failbit);
	else c = (unsigned char)ch;
    }
    return *this;
}

istream& istream::ignore(int n=1, int delim = EOF)
{
    if (ipfx0()) {
	register streambuf* sb = _strbuf;
	for (;;) {
	    // Could skip multiple chars at a time if egptr() > gptr(),
	    // if delim==EOF. FIXME!
#if 0
	    if (n != MAXINT) // FIXME
#endif
	    if (--n < 0)
		break;
	    int ch = sb->sbumpc();
	    if (ch == EOF) {
		set(ios::eofbit|ios::failbit);
		break;
	    }
	    else if (ch == delim)
		break;
	}
    }
    return *this;
}

istream& istream::read(char *s, int n)
{
    if (ipfx1()) {
	if (_strbuf->sgetn(s, n) != n)
	    set(ios::failbit);
    }
    return *this;
}

istream& istream::seekg(streampos pos)
{
    pos = _strbuf->seekpos(pos, ios::in);
    if (pos == streampos(EOF))
	set(ios::badbit);
    return *this;
}

istream& istream::seekg(streamoff off, _seek_dir dir)
{
    streampos pos = _strbuf->seekoff(off, dir, ios::in);
    if (pos == streampos(EOF))
	set(ios::badbit);
    return *this;
}

streampos istream::tellg()
{
    streampos pos = _strbuf->seekoff(0, ios::cur, ios::in);
    if (pos == streampos(EOF))
	set(ios::badbit);
    return pos;
}

istream& operator>>(istream& is, char& c)
{
    if (is.ipfx0()) {
	int ch = is._strbuf->sbumpc();
	if (ch == EOF)
	    is.set(ios::eofbit|ios::failbit);
	else
	    c = (char)ch;
    }
    return is;
}

istream& operator>>(istream& is, unsigned char& c)
{
    if (is.ipfx0()) {
	int ch = is._strbuf->sbumpc();
	if (ch == EOF)
	    is.set(ios::eofbit|ios::failbit);
	else
	    c = (unsigned char)ch;
    }
    return is;
}

istream& operator>>(istream& is, char* ptr)
{
    if (is.ipfx0()) {
	register streambuf* sb = is._strbuf;
	int ch = sb->sbumpc();
	if (ch == EOF)
	    is.set(ios::eofbit|ios::failbit);
	else {
	    int w = is.width(0);
	    *ptr++ = ch;
	    for (;;) {
		ch = sb->sbumpc();
		if (ch == EOF) {
		    is.set(ios::eofbit|ios::failbit);
		    break;
		}
		else if (isspace(ch)) {
		    sb->sputbackc(ch);
		    break;
		}
		else if (w == 1) {
		    is.set(ios::failbit);
		    sb->sputbackc(ch);
		    break;
		}
		else *ptr++ = ch;
		w--;
	    }
	}
    }
    *ptr = '\0';
    return is;
}

static int read_int(istream& stream, unsigned long& val, int& neg)
{
    if (!stream.ipfx0())
	return 0;
    register streambuf* sb = stream.rdbuf();
    int base = 10;
    int ndigits = 0;
    register int ch = skip_ws(sb);;
    if (ch == EOF)
	goto eof_fail;
    neg = 0;
    if (ch == '+') {
	ch = skip_ws(sb);
    }
    else if (ch == '-') {
	neg = 1;
	ch = skip_ws(sb);
    }
    if (ch == EOF) goto eof_fail;
    if (!(stream.flags() & (ios::hex|ios::dec|ios::oct))) {
	if (ch == '0') {
	    ch = sb->sbumpc();
	    if (ch == EOF) {
		val = 0;
		return 1;
	    }
	    if (ch == 'x' || ch == 'X') {
		base = 16;
		ch = sb->sbumpc();
		if (ch == EOF) goto eof_fail;
	    }
	    else {
		sb->sputbackc(ch);
		base = 8;
		ch = '0';
	    }
	}
    }
    else if (stream.flags() & ios::hex)
	base = 16;
    else if (stream.flags() & ios::oct)
	base = 8;
    val = 0;
    for (;;) {
	if (ch == EOF)
	    break;
	int digit;
	if (ch >= '0' && ch <= '9')
	    digit = ch - '0';
	else if (ch >= 'A' && ch <= 'F')
	    digit = ch - 'A' + 10;
	else if (ch >= 'a' && ch <= 'f')
	    digit = ch - 'a' + 10;
	else
	    digit = 999;
	if (digit >= base)
	    if (ndigits == 0)
		goto fail;
	    else {
		sb->sputbackc(ch);
		return 1;
	    }
	ndigits++;
	val = base * val + digit;
	ch = sb->sbumpc();
    }
    return 1;
  fail:
    stream.set(ios::failbit);
    return 0;
  eof_fail:
    stream.set(ios::failbit|ios::eofbit);
    return 0;
}

#define READ_INT(TYPE) \
istream& operator>>(istream& is, TYPE& i)\
{\
    unsigned long val; int neg;\
    if (read_int(is, val, neg)) {\
	if (neg) val = -val;\
	i = (TYPE)val;\
    }\
    return is;\
}

READ_INT(short)
READ_INT(unsigned short)
READ_INT(int)
READ_INT(unsigned int)
READ_INT(long)
READ_INT(unsigned long)

istream& operator>>(istream& is, double& x)
{
    if (is.ipfx0()) {
	double* ptr = &x;
	is.rdbuf()->vscan("%lg", (_G_va_list)&ptr);
    }
    return is;
}
istream& operator>>(istream& is, float& x)
{
    if (is.ipfx0()) {
	float* ptr = &x;
	is.rdbuf()->vscan("%g", (_G_va_list)&ptr);
    }
    return is;
}

ostream& operator<<(ostream& os, char c)
{
    if (os.opfx()) {
	int w = os.width(0);
	char fill_char = os.fill();
	register int padding = w > 0 ? w - 1 : 0;
	register streambuf *sb = os._strbuf;
	if (!(os.flags() & ios::left)) // Default adjustment.
	    while (--padding >= 0) sb->sputc(fill_char);
	sb->sputc(c);
	if (os.flags() & ios::left) // Left adjustment.
	    while (--padding >= 0) sb->sputc(fill_char);
	os.osfx();
    }
    return os;
}

void write_int(ostream& stream, unsigned long val, int neg)
{
    char buf[10 + sizeof(unsigned long) * 3];
    char *show_base = "";
    int show_base_len = 0;
    if ((stream.flags() & (ios::oct|ios::hex)) == 0) // Decimal
	sprintf(buf, "%lu", val);
    else if (stream.flags() & ios::oct) { // Oct
	sprintf(buf, "%lo", val);
	if (stream.flags() && ios::showbase)
	    show_base = "0", show_base_len = 1;
    }
    else if (stream.flags() & ios::uppercase) {// Hex
	sprintf(buf, "%lX", val);
	if (stream.flags() && ios::showbase)
	    show_base = "0X", show_base_len = 2;
    }
    else { // Hex
	sprintf(buf, "%lx", val);
	if (stream.flags() && ios::showbase)
	    show_base = "0x", show_base_len = 2;
    }
    int buf_len = strlen(buf);
    int w = stream.width(0);
    int show_pos = 0;
    int i;

    // Calculate padding.
    int len = buf_len;
    if (neg) len++;
    else if (val != 0 && (stream.flags() & ios::showpos)) len++, show_pos=1;
    len += show_base_len;
    int padding = len > w ? 0 : w - len;

    // Do actual output.
    register streambuf* sbuf = stream.rdbuf();
    unsigned long pad_kind =
	stream.flags() & (ios::left|ios::right|ios::internal);
    char fill_char = stream.fill();
    if (pad_kind != (unsigned long)ios::left // Default (right) adjustment.
	&& pad_kind != (unsigned long)ios::internal)
	for (i = padding; --i >= 0; ) sbuf->sputc(fill_char);
    if (neg) sbuf->sputc('-');
    else if (show_pos) sbuf->sputc('+');
    if (show_base_len)
	sbuf->sputn(show_base, show_base_len);
    if (pad_kind == (unsigned long)ios::internal)
	for (i = padding; --i >= 0; ) sbuf->sputc(fill_char);
    sbuf->sputn(buf, buf_len);
    if (pad_kind == (unsigned long)ios::left) // Left adjustment.
	for (i = padding; --i >= 0; ) sbuf->sputc(fill_char);
    stream.osfx();
}

ostream& operator<<(ostream& os, int n)
{
    if (os.opfx()) {
	int neg = 0;
	if (n < 0 && (os.flags() & (ios::oct|ios::hex)) == 0)
	    n = -n, neg = 1;
	write_int(os, n, neg);
    }
    return os;
}

ostream& operator<<(ostream& os, long n)
{
    if (os.opfx()) {
	int neg = 0;
	if (n < 0 && (os.flags() & (ios::oct|ios::hex)) == 0)
	    n = -n, neg = 1;
	write_int(os, n, neg);
    }
    return os;
}

ostream& operator<<(ostream& os, unsigned int n)
{
    if (os.opfx())
	write_int(os, n, 0);
    return os;
}

ostream& operator<<(ostream& os, unsigned long n)
{
    if (os.opfx())
	write_int(os, n, 0);
    return os;
}

ostream& operator<<(ostream& os, float n)
{
    return os << (double)n;
}

ostream& operator<<(ostream& os, double n)
{
    if (os.opfx()) {
	// Uses __cvt_double (renamed from static cvt), in Chris Torek's
	// stdio implementation.  The setup code uses the same logic
	// as in __vsbprintf.C (also based on Torek's code).
	char negative;
	char buf[BUF];
	int format_char;
	int sign = '\0';
#if 0
	if (os.flags() ios::showpos) sign = '+';
#endif
	if (os.flags() & ios::fixed)
	    format_char = 'f';
	else if (os.flags() & ios::scientific)
	    format_char = os.flags() & ios::uppercase ? 'E' : 'e';
	else
	    format_char = os.flags() & ios::uppercase ? 'G' : 'g';

	int fpprec = 0; // 'Extra' (suppressed) floating precision.
	int prec = os.precision();
	if (prec < 0) prec = 6; // default.
	else if (prec > MAXFRACT) {
	    if (os.flags() & (ios::fixed|ios::scientific) & ios::showpos)
		fpprec = prec - MAXFRACT;
	    prec = MAXFRACT;
	}

	// Do actual conversion.
	char *cp = buf;
	*cp = 0;
	int size = __cvt_double(n, os.precision(),
				os.flags() & ios::showpoint ? 0x80 : 0,
				&negative,
				format_char, cp, buf + sizeof(buf));
	if (negative) sign = '-';
	if (*cp == 0)
	    cp++;

	// Calculate padding.
	int fieldsize = size + fpprec;
	if (sign) fieldsize++;
	int padding = 0;
	int w = os.width(0);
	if (fieldsize < w)
	    padding = w - fieldsize;

	// Do actual output.
	register streambuf* sbuf = os.rdbuf();
	register i;
	char fill_char = os.fill();
	unsigned long pad_kind =
	    os.flags() & (ios::left|ios::right|ios::internal);
	if (pad_kind != (unsigned long)ios::left // Default (right) adjust.
	    && pad_kind != (unsigned long)ios::internal)
	    for (i = padding; --i >= 0; ) sbuf->sputc(fill_char);
	if (sign)
	    sbuf->sputc(sign);
	if (pad_kind == (unsigned long)ios::internal)
	    for (i = padding; --i >= 0; ) sbuf->sputc(fill_char);
	
	// Emit the actual concented field, followed by extra zeros.
	sbuf->sputn(cp, size);
	for (i = fpprec; --i >= 0; ) sbuf->sputc('0');

	if (pad_kind == (unsigned long)ios::left) // Left adjustment
	    for (i = padding; --i >= 0; ) sbuf->sputc(fill_char);
	os.osfx();
    }
    return os;
}

ostream& operator<<(ostream& stream, const char *s)
{
    if (stream.opfx()) {
	int len = strlen(s);
	int w = stream.width(0);
	char fill_char = stream.fill();
	register streambuf *sbuf = stream.rdbuf();
	register int padding = w > len ? w - len : 0;
	if (!(stream.flags() & ios::left)) // Default adjustment.
	    while (--padding >= 0) sbuf->sputc(fill_char);
	sbuf->sputn(s, len);
	if (stream.flags() & ios::left) // Left adjustment.
	    while (--padding >= 0) sbuf->sputc(fill_char);
	stream.osfx();
    }
    return stream;
}

ostream& operator<<(ostream& os, void *p)
{
    if (os.opfx()) {
	void *px = p;
	os._strbuf->vform("0x%x", (_G_va_list)&px);
	os.osfx();
    }
    return os;
}

ostream& operator<<(ostream& os, register streambuf* sbuf)
{
    if (os.opfx()) {
	register streambuf* outbuf = os.rdbuf();
	// FIXME: Should optimize!
	for (;;) {
	    register int ch = sbuf->sbumpc();
	    if (ch == EOF) break;
	    outbuf->sputc(ch);
	}
	os.osfx();
    }
    return os;
}

ostream::ostream() : ios(&not_open_filebuf)
{
    _flags |= ios::dont_close;
    _tie = NULL;
}

ostream::ostream(streambuf* sb, ostream* tied) : ios(sb)
{
    _flags |= ios::dont_close;
    _tie = tied;
}

ostream::~ostream()
{
    if (!(_flags & (unsigned int)ios::dont_close))
	delete _strbuf;
    _flags |= ios::dont_close;
    _strbuf = &not_open_filebuf;
}

ostream& ostream::seekp(streampos pos)
{
    pos = _strbuf->seekpos(pos, ios::out);
    if (pos == streampos(EOF))
	set(ios::badbit);
    return *this;
}

ostream& ostream::seekp(streamoff off, _seek_dir dir)
{
    streampos pos = _strbuf->seekoff(off, dir, ios::out);
    if (pos == streampos(EOF))
	set(ios::badbit);
    return *this;
}

streampos ostream::tellp()
{
    streampos pos = _strbuf->seekoff(0, ios::cur, ios::out);
    if (pos == streampos(EOF))
	set(ios::badbit);
    return pos;
}

ostream& ostream::form(const char *format ...)
{
    va_list ap;
    va_start(ap, format);
    _strbuf->vform(format, (_G_va_list)ap);
    va_end(ap);
    return *this;
}

ostream& ostream::vform(const char *format, _G_va_list args)
{
    _strbuf->vform(format, args);
    return *this;
}

ostream& ostream::flush()
{
    if (_strbuf->sync())
	set(ios::badbit);
    return *this;
}

ostream& flush(ostream& outs)
{
    outs.rdbuf()->overflow(EOF);
    return outs;
}

istream& ws(istream& ins)
{
    if (ins.ipfx1())
	for (;;) {
	    int ch = ins._strbuf->sbumpc();
	    if (ch == EOF) {
		ins.set(ios::eofbit);
		break;
	    }
	    if (!isspace(ch)) {
		ins._strbuf->sputbackc(ch);
		break;
	    }
	}
    return ins;
}

ostream& ends(ostream& outs)
{
    outs.put(0);
    return outs;
}

ostream& endl(ostream& outs)
{
    return flush(outs.put('\n'));
}

ostream& ostream::write(const char *s, int n)
{
    if (opfx()) {
	if (_strbuf->sputn(s, n) != n)
	    set(ios::failbit);
    }
    return *this;
}

void ostream::do_osfx()
{
    if (flags() & ios::unitbuf)
	flush();
    if (flags() & ios::stdio) {
	fflush(stdout);
	fflush(stderr);
    }
}

const unsigned long ios::basefield = ios::hex|ios::oct|ios::dec;
const unsigned long ios::floatfield = ios::scientific|ios::fixed;
const unsigned long ios::adjustfield =
    ios::left|ios::right|ios::internal;

iostream::iostream() : ios(&not_open_filebuf)
{
    _flags |= ios::dont_close;
    _gcount = 0;
    _tie = NULL;
}

iostream::iostream(streambuf* sb, ostream* tied) : ios(sb)
{
    _flags |= ios::dont_close;
    _tie = tied;
}

iostream::~iostream()
{
    if (!(_flags & (unsigned int)ios::dont_close))
	delete _strbuf;
    _flags |= ios::dont_close;
    _strbuf = &not_open_filebuf;
}

ostream& iostream::form(const char *format ...) // Copy of ostream::form.
{
    va_list ap;
    va_start(ap, format);
    _strbuf->vform(format, (_G_va_list)ap);
    va_end(ap);
    return *(ostream*)this;
}

// NOTE: extension for compatibility with old libg++.
// Not really compatible with fistream::close().
#ifdef _STREAM_COMPAT
void ios::close()
{
    if (!(_flags & (unsigned int)ios::dont_close))
	delete _strbuf;
    else if (_strbuf->_flags & _S_IS_FILEBUF)
	((struct filebuf*)_strbuf)->close();
    else if (_strbuf != &not_open_filebuf)
	_strbuf->sync();
    _flags |= ios::dont_close;
    _strbuf = &not_open_filebuf;
}

int istream::skip(int i)
{
    int old = (_flags & ios::skipws) != 0;
    if (i)
	_flags |= ios::skipws;
    else
	_flags &= ~ios::skipws;
    return old;
}
#endif

