/*
 * Copyright (c) 1987, 1988, 1989, 1990, 1991 Stanford University
 * Copyright (c) 1991 Silicon Graphics, Inc.
 *
 * Permission to use, copy, modify, distribute, and sell this software and 
 * its documentation for any purpose is hereby granted without fee, provided
 * that (i) the above copyright notices and this permission notice appear in
 * all copies of the software and related documentation, and (ii) the names of
 * Stanford and Silicon Graphics may not be used in any advertising or
 * publicity relating to the software without the specific, prior written
 * permission of Stanford and Silicon Graphics.
 * 
 * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, 
 * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY 
 * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.  
 *
 * IN NO EVENT SHALL STANFORD OR SILICON GRAPHICS BE LIABLE FOR
 * ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND,
 * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
 * WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF 
 * LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE 
 * OF THIS SOFTWARE.
 */

#include "iostreamb.h"

// Make sure that these assumptions about the sizes of integral types
// hold for your machine.  If your machine does not have 32-bit and
// 16-bit integers, then you cannot use binary I/O because other
// machines will expect the integers to be these sizes.  If your
// machine does not have 8-bit characters, then I don't know how
// you're going to communicate with other machines!

typedef long		INT32;
typedef short		INT16;

typedef unsigned long	UINT32;
typedef unsigned short	UINT16;


// --------------------------------------------------------------------
Verbose iosb :: verbose ;

// Assume the peer machine has the same endian architecture but
// disable binary I/O by default anyway.  The two machines should
// always negotiate these two options before enabling binary I/O.

iosb::iosb() :
    _binary(false),
    _swapped(false) {}

iosb::~iosb() {}

// Get and set iosb's extra state information.  Verify that the
// assumptions about sizes of integral types hold before enabling
// binary I/O; if they do not hold, then refuse to enable binary I/O.

boolean iosb::binary() const {
    return _binary;
}

void iosb::binary(boolean binary) {
    if (binary) {
	// don't use consts; cfront has wrong idea of their values
	UINT32 max32 = ~0;
	UINT16 max16 = ~0;

	if (max32 != 0xffffffff || max16 != 0xffff) {
	    binary = false;
	}
    }
    _binary = binary;
}

boolean iosb::swapped() const {
    return _swapped;
}

void iosb::swapped(boolean swapped) {
    _swapped = swapped;
}

// Provide a constructor for derived classes to use and a constructor
// for public use.

// istreamb::istreamb() {}

istreamb::istreamb(std::streambuf* b) : std::istream(b) {
    init(b);
}

istreamb::~istreamb() {}

// Redefine these functions to extract binary data where possible for
// faster I/O throughput and to discard delimiters automatically.
// Binary integral values occupy the length implied by their type and
// already fit this machine's endian architecture.  Formatted integral
// and floating point values end in a blank character that is
// extracted.  Strings end in a null character that is extracted.

istreamb& istreamb::operator>>(char& c) {
    get(c);			// assume c is 8 bits long on all machines
    return *this;
}

istreamb& istreamb::operator>>(unsigned char& uc) {
    get((char)uc);			// assume uc is 8 bits long on all machines
    return *this;
}

istreamb& istreamb::operator>>(short& s) {
    if (_binary) {
	INT16 value;
	read((char*)&value, sizeof(value));
	if (good()) {
	    s = (short)value;
	    if (s != value) {
		setstate(std::ios::failbit); // overflow: value won't fit in short
	    }
	}
    } else {
	std::istream::operator>>(s);
	get();
    }
    return *this;
}

istreamb& istreamb::operator>>(unsigned short& us) {
    if (_binary) {
	UINT16 value;
	read((char*)&value, sizeof(value));
	if (good()) {
	    us = (unsigned short)value;
	    if (us != value) {
		setstate(std::ios::failbit); // overflow: value won't fit in ushort
	    }
	}
    } else {
	std::istream::operator>>(us);
	get();
    }
    return *this;
}

istreamb& istreamb::operator>>(int& i) {
    if (_binary) {
	INT32 value;
	read((char*)&value, sizeof(value));
	if (good()) {
	    i = (int)value;
	    if (i != value) {
		setstate(std::ios::failbit); // overflow: value won't fit in int
	    }
	}
    } else {
	std::istream::operator>>(i);
	get();
    }
    return *this;
}

istreamb& istreamb::operator>>(unsigned int& ui) {
    if (_binary) {
	UINT32 value;
	read((char*)&value, sizeof(value));
	if (good()) {
	    ui = (unsigned int)value;
	    if (ui != value) {
		setstate(std::ios::failbit); // overflow: value won't fit in uint
	    }
	}
    } else {
	std::istream::operator>>(ui);
	get();
    }
    return *this;
}

istreamb& istreamb::operator>>(long& l) {
    if (_binary) {
	INT32 value;
	read((char*)&value, sizeof(value));
	if (good()) {
	    l = (long)value;
	    if (l != value) {
		setstate(std::ios::failbit); // overflow: value won't fit in long
	    }
	}
    } else {
	std::istream::operator>>(l);
	get();
    }
    return *this;
}

istreamb& istreamb::operator>>(unsigned long& ul) {
    if (_binary) {
	UINT32 value;
	read((char*)&value, sizeof(value));
	if (good()) {
	    ul = (unsigned long)value;
	    if (ul != value) {
		setstate(std::ios::failbit); // overflow: value won't fit in ulong
	    }
	}
    } else {
	std::istream::operator>>(ul);
	get();
    }
    return *this;
}

istreamb& istreamb::operator>>(float& f) {
    std::istream::operator>>(f);
    get();
    return *this;
}

istreamb& istreamb::operator>>(double& d) {
    std::istream::operator>>(d);
    get();
    return *this;
}

istreamb& istreamb::operator>>(char* p) {
    const int MAXINT = (int)(((unsigned)-1) >> 1);
    const int w = width(0);
    getline(p, w ? w : MAXINT, '\0');
    if (w && gcount() == w - 1) {
	setstate(std::ios::failbit);
    }
    return *this;
}

istreamb& istreamb::operator>>(unsigned char* up) {
    const int MAXINT = (int)(((unsigned)-1) >> 1);
    const int w = width(0);
    getline((char*)up, w ? w : MAXINT, '\0');
    if (w && gcount() == w - 1) {
	setstate(std::ios::failbit);
    }
    return *this;
}

// Redefine the rest of the overloaded operator>> functions that we
// want to keep in the derived class.

istreamb& istreamb::operator>>(std::istream& (*f)(std::istream&)) {
    std::istream::operator>>(f);
    return *this;
}

istreamb& istreamb::operator>>(std::ios& (*f)(std::ios&)) {
    std::istream::operator>>(f);
    return *this;
}

// Provide a constructor for derived classes to use and a constructor
// for public use.

// ostreamb::ostreamb() {}

ostreamb::ostreamb(std::streambuf* b) : std::ostream(b) {
    init(b);
}

ostreamb::~ostreamb() {}

// Decrement the width by one to take into account the delimiter
// character that all the inserters insert after a formatted value.

inline void ostreamb::fixwidth() {
    register int w = width();
    if (w) {
	width(w - 1);
    }
}

// Redefine these functions to insert binary data where possible for
// faster I/O throughput and to delimit formatted data automatically.
// Binary integral values will occupy the length implied by their type
// and fit the peer's endian architecture.  Formatted integral and
// floating point values will end in a blank character.  Strings will
// end in a null character.  You need not, and should not, insert
// explicit delimiters.

ostreamb& ostreamb::operator<<(char c) {
    put(c);			// assume c is 8 bits long on all machines
    return *this;
}

ostreamb& ostreamb::operator<<(unsigned char uc) {
    put(uc);			// assume uc is 8 bits long on all machines
    return *this;
}

ostreamb& ostreamb::operator<<(short s) {
    if (_binary) {
	INT16 value = (INT16)s;
	if (value != s) {
	    setstate(std::ios::failbit); // overflow: number won't fit in 16 bits
	}
	else if (_swapped) {
	    INT16 copy = value;
	    ((char*)&value)[0] = ((char*)&copy)[1];
	    ((char*)&value)[1] = ((char*)&copy)[0];
	}
	width(0);
	write((char*)&value, sizeof(value));
    }
    else {
	fixwidth();
	std::ostream::operator<<(s);
	put(' ');
    }
    return *this;
}

ostreamb& ostreamb::operator<<(unsigned short us) {
    if (_binary) {
	UINT16 value = (UINT16)us;
	if (value != us) {
	    setstate(std::ios::failbit); // number won't fit
	}
	else if (_swapped) {
	    UINT16 copy = value;
	    ((char*)&value)[0] = ((char*)&copy)[1];
	    ((char*)&value)[1] = ((char*)&copy)[0];
	}
	width(0);
	write((char*)&value, sizeof(value));
    }
    else {
	fixwidth();
	std::ostream::operator<<(us);
	put(' ');
    }
    return *this;
}

ostreamb& ostreamb::operator<<(int i) {
    return *this << (long)i;
}

ostreamb& ostreamb::operator<<(unsigned int ui) {
    return *this << (unsigned long)ui;
}

ostreamb& ostreamb::operator<<(long l) {
    if (_binary) {
	INT32 value = (INT32)l;
	if (value != l) {
	    setstate(std::ios::failbit); // overflow: number won't fit in 32 bits
	}
	else if (_swapped) {
	    INT32 copy = value;
	    ((char*)&value)[0] = ((char*)&copy)[3];
	    ((char*)&value)[1] = ((char*)&copy)[2];
	    ((char*)&value)[2] = ((char*)&copy)[1];
	    ((char*)&value)[3] = ((char*)&copy)[0];
	}
	width(0);
	write((char*)&value, sizeof(value));
    }
    else {
	fixwidth();
	std::ostream::operator<<(l);
	put(' ');
    }
    return *this;
}

ostreamb& ostreamb::operator<<(unsigned long ul) {
    if (_binary) {
	UINT32 value = (UINT32)ul;
	if (value != ul) {
	    setstate(std::ios::failbit); // overflow: number won't fit in 32 bits
	}
	else if (_swapped) {
	    UINT32 copy = value;
	    ((char*)&value)[0] = ((char*)&copy)[3];
	    ((char*)&value)[1] = ((char*)&copy)[2];
	    ((char*)&value)[2] = ((char*)&copy)[1];
	    ((char*)&value)[3] = ((char*)&copy)[0];
	}
	width(0);
	write((char*)&value, sizeof(value));
    }
    else {
	fixwidth();
	std::ostream::operator<<(ul);
	put(' ');
    }
    return *this;
}

ostreamb& ostreamb::operator<<(float f) {
    fixwidth();
    std::ostream::operator<<(f);
    put(' ');
    return *this;
}

ostreamb& ostreamb::operator<<(double d) {
    fixwidth();
    std::ostream::operator<<(d);
    put(' ');
    return *this;
}

ostreamb& ostreamb::operator<<(const char* p) {
    fixwidth();
    std::ostream::operator<<(p);
    put('\0');
    return *this;
}

ostreamb& ostreamb::operator<<(const unsigned char* up) {
    fixwidth();
    std::ostream::operator<<((const char*)up); // 2.0 ostream omitted unsigned char*
    put('\0');
    return *this;
}

// Redefine the rest of the overloaded operator<< functions that we
// want to keep in the derived class.

ostreamb& ostreamb::operator<<(std::ostream& (*f)(std::ostream&)) {
    std::ostream::operator<<(f);
    return *this;
}

ostreamb& ostreamb::operator<<(std::ios& (*f)(std::ios&)) {
    std::ostream::operator<<(f);
    return *this;
}

// Provide a constructor for derived classes to use and a constructor
// for public use.

// iostreamb::iostreamb() {}

iostreamb::iostreamb(std::streambuf* b) : istreamb(b), ostreamb(b) {
    init(b);
}

iostreamb::~iostreamb() {}

// Negotiate the option of binary I/O with the remote iostreamb.  If
// binary I/O is set, compare endians to enable swapping if necessary.
// Ordinarily we don't need an explicit flush when we're switching
// from insertion to extraction, but we do here because the first
// underflow may read both format and remoteEndian, thus preventing
// underflow from being called again and flushing localEndian.  Tying
// the stream to itself won't work either because 1) ipfx won't call
// flush if remoteEndian is already available, and 2) opfx will make
// the stream flush itself before every insertion because it doesn't
// check for streams tied to themselves.  Sigh....

void iostreamb::negotiate(boolean b) {
    if (!good()) {
       DEBUGNL ("iostreamb::negotiate(): !good()") ;
       return;
    }

    iosb::binary(b);
    char format = iosb::binary() ? 'T' : 'F';

    *this << format; flush();
    *this >> format;

    if (format != 'T' && format != 'F') {
	setstate(std::ios::badbit);
    }
    else if (format == 'F') {
	 iosb::binary(false);
    }

    if (iosb::binary()) {
	int indian = 1;
	char localEndian = (*(char*)&indian) ? 'l' : 'B';
	char remoteEndian = localEndian;

	*this << localEndian; flush();
	*this >> remoteEndian;

	if (remoteEndian != 'l' && remoteEndian != 'B') {
	    setstate(std::ios::badbit);
	}
	else if (remoteEndian != localEndian) {
	    swapped(true);
	}
    }
}
