/*
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * binio.cpp - Binary stream I/O classes
 * Copyright (C) 2002, 2003 Simon Peter <dn.tlp@gmx.net>
 */

#include <string.h>

#include "binio.h"

#if BINIO_WITH_MATH

#include <math.h>

#ifdef __QNXNTO__
#define pow std::powf
#endif // __QNXNTO__

// If 'math.h' doesn't define HUGE_VAL, we try to use HUGE instead.
#ifndef HUGE_VAL
# define HUGE_VAL HUGE
#endif

#endif

/***** Defines *****/

#if BINIO_ENABLE_STRING
// String buffer size for std::string readString() method
#define STRINGBUFSIZE	256
#endif

/***** binio *****/

const binio::Flags binio::system_flags = binio::detect_system_flags();

const binio::Flags binio::detect_system_flags() {
   Flags f = 0;

   // Endian test
   union {
      int word;
      Byte byte;
   } endian_test;

   endian_test.word = 1;
   if (endian_test.byte != 1) f |= BigEndian;

   // IEEE-754 floating-point test
   float fl = 6.5;
   Byte	*dat = (Byte *)&fl;

   if (sizeof(float) == 4 && sizeof(double) == 8) {
      if (f & BigEndian) {
         if (dat[0] == 0x40 && dat[1] == 0xD0 && !dat[2] && !dat[3])
            f |= FloatIEEE;
      } else {
         if (dat[3] == 0x40 && dat[2] == 0xD0 && !dat[1] && !dat[0])
            f |= FloatIEEE;
      }
   }
   return f;
}

binio::binio()
      : my_flags(system_flags), err(NoError) {
}

binio::~binio() {
}

void binio::setFlag(Flag f, bool set) {
   if (set)
      my_flags |= f;
   else
      my_flags &= !f;
}

bool binio::getFlag(Flag f) {
   return (my_flags & f ? true : false);
}

binio::Error binio::error() {
   Error e = err;

   err = NoError;
   return e;
}

bool binio::eof() {
   return (err & Eof ? true : false);
}

/***** binistream *****/

binistream::binistream() {
}

binistream::~binistream() {
}

binistream::Int binistream::readInt(unsigned int size) {
   unsigned int	i;
   Int		val = 0, in;

   // Check if 'size' doesn't exceed our system's biggest type.
   if (size > sizeof(Int)) {
      err |= Unsupported;
      return 0;
   }

   for (i = 0; i < size; i++) {
      in = getByte();
      if (getFlag(BigEndian))
         val <<= 8;
      else
         in <<= i * 8;
      val |= in;
   }

   return val;
}

binistream::Float binistream::readFloat(FType ft) {
   if (getFlag(FloatIEEE)) {	// Read IEEE-754 floating-point value
      unsigned int	i, size = 0;
      Byte		in[8];
      bool		swap;

      // Determine appropriate size for given type.
      switch (ft) {
      case Single:
         size = 4;
         break;	// 32 bits
      case Double:
         size = 8;
         break;	// 64 bits
      }

      // Determine byte ordering, depending on what we do next
      if (system_flags & FloatIEEE)
         swap = getFlag(BigEndian) ^ (system_flags & BigEndian);
      else
         swap = !getFlag(BigEndian);

      // Read the float byte by byte, converting endianess
      for (i = 0; i < size; i++)
         if (swap)
            in[size - i - 1] = getByte();
         else
            in[i] = getByte();

      if (system_flags & FloatIEEE) {
         // Compatible system, let the hardware do the conversion
         switch (ft) {
         case Single:
            return *reinterpret_cast<float *>(in);
         case Double:
            return *reinterpret_cast<double *>(in);
         }
      } else {	// Incompatible system, convert manually
         switch (ft) {
         case Single:
            return ieee_single2float(in);
         case Double:
            return ieee_double2float(in);
         }
      }
   }

   // User tried to read a (yet) unsupported floating-point type. Bail out.
   err |= Unsupported;
   return 0.0;
}

binistream::Float binistream::ieee_single2float(Byte *data) {
   signed int	sign = data[0] >> 7 ? -1 : 1;
   unsigned int	exp = ((data[0] << 1) & 0xff) | ((data[1] >> 7) & 1),
                      fracthi7 = data[1] & 0x7f;
   Float		fract = fracthi7 * 65536.0 + data[2] * 256.0 + data[3];

   // Signed and unsigned zero
   if (!exp && !fracthi7 && !data[2] && !data[3]) return sign * 0.0;

   // Signed and unsigned infinity (maybe unsupported on non-IEEE systems)
   if (exp == 255) {
      if (!fracthi7 && !data[2] && !data[3]) {
#ifdef HUGE_VAL
         if (sign == -1) return -HUGE_VAL;
         else return HUGE_VAL;
#else
         err |= Unsupported;
         if (sign == -1) return -1.0;
         else return 1.0;
#endif
      } else {	  // Not a number (maybe unsupported on non-IEEE systems)
#ifdef NAN
         return NAN;
#else
         err |= Unsupported;
         return 0.0;
#endif
      }
   }

   if (!exp)	// Unnormalized float values
      return sign * pow(2.0f, -126.0f) * fract * pow(2.0f, -23.0f);
   else		// Normalized float values
      return sign * pow(2.0f, exp - 127.0f) * (fract * pow(2.0f, -23.0f) + 1);

   err |= Fatal;
   return 0.0;
}

binistream::Float binistream::ieee_double2float(Byte *data) {
   signed int	sign = data[0] >> 7 ? -1 : 1;
   unsigned int	exp = ((unsigned int)(data[0] & 0x7f) << 4) | (data[1] >> 4),
                      fracthi4 = data[1] & 0xf;
   Float		fract = fracthi4 * pow(2.0f, 48.0f) + data[2] * pow(2.0f, 40.0f) + data[3] *
                  pow(2.0f, 32.0f) + data[4] * pow(2.0f, 24.0f) + data[5] * pow(2.0f, 16.0f) + data[6] *
                  pow(2.0f, 8.0f) + data[7];

   // Signed and unsigned zero
   if (!exp && !fracthi4 && !data[2] && !data[3] && !data[4] && !data[5] &&
         !data[6] && !data[7]) return sign * 0.0;

   // Signed and unsigned infinity  (maybe unsupported on non-IEEE systems)
   if (exp == 2047) {
      if (!fracthi4 && !data[2] && !data[3] && !data[4] && !data[5] && !data[6] && !data[7]) {
#ifdef HUGE_VAL
         if (sign == -1) return -HUGE_VAL;
         else return HUGE_VAL;
#else
         err |= Unsupported;
         if (sign == -1) return -1.0;
         else return 1.0;
#endif
      } else {	  // Not a number (maybe unsupported on non-IEEE systems)
#ifdef NAN
         return NAN;
#else
         err |= Unsupported;
         return 0.0;
#endif
      }
   }

   if (!exp)	// Unnormalized float values
      return sign * pow(2.0f, -1022.0f) * fract * pow(2.0f, -52.0f);
   else		// Normalized float values
      return sign * pow(2.0f, exp - 1023.0f) * (fract * pow(2.0f, -52.0f) + 1);

   err |= Fatal;
   return 0.0;
}

#if !BINIO_WITH_MATH
binio::Float binio::pow(Float base, signed int exp)
/* Our own, stripped-down version of pow() for not having to depend on 'math.h'.
 * This one handles float values for the base and an integer exponent, both
 * positive and negative.
 */
{
   int	i;
   Float	val = base;

   if (!exp) return 1.0;

   for (i = 1; i < (exp < 0 ? -exp : exp); i++)
      val *= base;

   if (exp < 0) val = 1.0 / val;

   return val;
}
#endif

unsigned long binistream::readString(char *str, unsigned long maxlen) {
   unsigned long	i;

   for (i = 0; i < maxlen; i++) {
      str[i] = (char)getByte();
      if (err) {
         str[i] = '\0';
         return i;
      }
   }

   return maxlen;
}

unsigned long binistream::readString(char *str, unsigned long maxlen,
                                     const char delim) {
   unsigned long i;

   for (i = 0; i < maxlen; i++) {
      str[i] = (char)getByte();
      if (str[i] == delim || err) {
         str[i] = '\0';
         return i;
      }
   }

   str[maxlen] = '\0';
   return maxlen;
}

#if BINIO_ENABLE_STRING
std::string binistream::readString(const char delim) {
   char buf[STRINGBUFSIZE + 1];
   std::string tempstr;
   unsigned long read;

   do {
      read = readString(buf, STRINGBUFSIZE, delim);
      tempstr.append(buf, read);
   } while (read == STRINGBUFSIZE);

   return tempstr;
}
#endif

binistream::Int binistream::peekInt(unsigned int size) {
   Int val = readInt(size);
   if (!err) seek(-(long)size, Add);
   return val;
}

binistream::Float binistream::peekFloat(FType ft) {
   Float val = readFloat(ft);

   if (!err)
      switch (ft) {
      case Single:
         seek(-4, Add);
         break;
      case Double:
         seek(-8, Add);
         break;
      }

   return val;
}

bool binistream::ateof() {
   Error	olderr = err;	// Save current error state
   bool	eof_then;

   peekInt(1);
   eof_then = eof();	// Get error state of next byte
   err = olderr;		// Restore original error state
   return eof_then;
}

void binistream::ignore(unsigned long amount) {
   unsigned long i;

   for (i = 0; i < amount; i++)
      getByte();
}

/***** binostream *****/

binostream::binostream() {
}

binostream::~binostream() {
}

void binostream::writeInt(Int val, unsigned int size) {
   unsigned int	i;

   // Check if 'size' doesn't exceed our system's biggest type.
   if (size > sizeof(Int)) {
      err |= Unsupported;
      return;
   }

   for (i = 0; i < size; i++) {
      if (getFlag(BigEndian))
         putByte((val >> (size - i - 1) * 8) & 0xff);
      else {
         putByte(val & 0xff);
         val >>= 8;
      }
   }
}

void binostream::writeFloat(Float f, FType ft) {
   if (getFlag(FloatIEEE)) {	// Write IEEE-754 floating-point value
      unsigned int	i, size = 0;
      Byte		*out = NULL;
      bool		swap;

      if (system_flags & FloatIEEE) {
         // compatible system, let the hardware do the conversion
         float	outf = f;
         double	outd = f;

         // Hardware could be big or little endian, convert appropriately
         swap = getFlag(BigEndian) ^ (system_flags & BigEndian);

         // Determine appropriate size for given type and convert by hardware
         switch (ft) {
         case Single:
            size = 4;
            out = (Byte *)&outf;
            break;	// 32 bits
         case Double:
            size = 8;
            out = (Byte *)&outd;
            break;	// 64 bits
         }
      } else {
#if BINIO_WITH_MATH
         // incompatible system, do the conversion manually
         Byte	buf[8];

         // Our own value is always big endian, just check whether we have to
         // convert for a different stream format.
         swap = !getFlag(BigEndian);

         // Convert system's float to requested IEEE-754 float
         switch (ft) {
         case Single:
            size = 4;
            float2ieee_single(f, buf);
            break;
         case Double:
            size = 8;
            float2ieee_double(f, buf);
            break;
         }

         out = buf;	// Make the value ready for writing
#else
         // No necessary support routines to do the conversion, bail out!
         err |= Unsupported;
         return;
#endif
      }

      // Write the float byte by byte, converting endianess
      if (swap) out += size - 1;
      for (i = 0; i < size; i++) {
         putByte(*out);
         if (swap) out--;
         else out++;
      }

      return;	// We're done.
   }

   // User tried to write an unsupported floating-point type. Bail out.
   err |= Unsupported;
}

#ifdef BINIO_WITH_MATH

/*
 * Single and double floating-point to IEEE-754 equivalent conversion functions
 * courtesy of Ken Turkowski.
 *
 * Copyright (C) 1989-1991 Ken Turkowski. <turk@computer.org>
 *
 * All rights reserved.
 *
 * Warranty Information
 *  Even though I have reviewed this software, I make no warranty
 *  or representation, either express or implied, with respect to this
 *  software, its quality, accuracy, merchantability, or fitness for a
 *  particular purpose.  As a result, this software is provided "as is,"
 *  and you, its user, are assuming the entire risk as to its quality
 *  and accuracy.
 *
 * This code may be used and freely distributed as long as it includes
 * this copyright notice and the above warranty information.
 */

/****************************************************************
 * The following two routines make up for deficiencies in many
 * compilers to convert properly between unsigned integers and
 * floating-point.  Some compilers which have this bug are the
 * THINK_C compiler for the Macintosh and the C compiler for the
 * Silicon Graphics MIPS-based Iris.
 ****************************************************************/

#ifdef applec	/* The Apple C compiler works */
# define FloatToUnsigned(f)	((unsigned long)(f))
#else
# define FloatToUnsigned(f)	((unsigned long)(((long)((f) - 2147483648.0)) + 2147483647L + 1))
#endif

#define SEXP_MAX	255
#define SEXP_OFFSET	127
#define SEXP_SIZE	8
#define SEXP_POSITION	(32-SEXP_SIZE-1)

void binostream::float2ieee_single(Float num, Byte *bytes) {
   long		sign;
   register long	bits;

   if (num < 0) {	/* Can't distinguish a negative zero */
      sign = 0x80000000;
      num *= -1;
   } else {
      sign = 0;
   }

   if (num == 0) {
      bits = 0;
   } else {
      Float	fMant;
      int		expon;

      fMant = frexp(num, &expon);

      if ((expon > (SEXP_MAX-SEXP_OFFSET+1)) || !(fMant < 1)) {
         /* NaN's and infinities fail second test */
         bits = sign | 0x7F800000;		/* +/- infinity */
      }

      else {
         long mantissa;

         if (expon < -(SEXP_OFFSET-2)) {	/* Smaller than normalized */
            int shift = (SEXP_POSITION+1) + (SEXP_OFFSET-2) + expon;
            if (shift < 0) {	/* Way too small: flush to zero */
               bits = sign;
            } else {			/* Nonzero denormalized number */
               mantissa = (long)(fMant * (1L << shift));
               bits = sign | mantissa;
            }
         }

         else {				/* Normalized number */
            mantissa = (long)floor(fMant * (1L << (SEXP_POSITION+1)));
            mantissa -= (1L << SEXP_POSITION);			/* Hide MSB */
            bits = sign | ((long)((expon + SEXP_OFFSET - 1)) << SEXP_POSITION) | mantissa;
         }
      }
   }

   bytes[0] = bits >> 24;	/* Copy to byte string */
   bytes[1] = bits >> 16;
   bytes[2] = bits >> 8;
   bytes[3] = bits;
}

#define DEXP_MAX	2047
#define DEXP_OFFSET	1023
#define DEXP_SIZE	11
#define DEXP_POSITION	(32-DEXP_SIZE-1)

void binostream::float2ieee_double(Float num, Byte *bytes) {
   long	sign;
   long	first, second;

   if (num < 0) {	/* Can't distinguish a negative zero */
      sign = 0x80000000;
      num *= -1;
   } else {
      sign = 0;
   }

   if (num == 0) {
      first = 0;
      second = 0;
   } else {
      Float	fMant, fsMant;
      int		expon;

      fMant = frexp(num, &expon);

      if ((expon > (DEXP_MAX-DEXP_OFFSET+1)) || !(fMant < 1)) {
         /* NaN's and infinities fail second test */
         first = sign | 0x7FF00000;		/* +/- infinity */
         second = 0;
      }

      else {
         long mantissa;

         if (expon < -(DEXP_OFFSET-2)) {	/* Smaller than normalized */
            int shift = (DEXP_POSITION+1) + (DEXP_OFFSET-2) + expon;
            if (shift < 0) {	/* Too small for something in the MS word */
               first = sign;
               shift += 32;
               if (shift < 0) {	/* Way too small: flush to zero */
                  second = 0;
               } else {			/* Pretty small demorn */
                  second = FloatToUnsigned(floor(ldexp(fMant, shift)));
               }
            } else {			/* Nonzero denormalized number */
               fsMant = ldexp(fMant, shift);
               mantissa = (long)floor(fsMant);
               first = sign | mantissa;
               second = FloatToUnsigned(floor(ldexp(fsMant - mantissa, 32)));
            }
         }

         else {				/* Normalized number */
            fsMant = ldexp(fMant, DEXP_POSITION+1);
            mantissa = (long)floor(fsMant);
            mantissa -= (1L << DEXP_POSITION);			/* Hide MSB */
            fsMant -= (1L << DEXP_POSITION);
            first = sign | ((long)((expon + DEXP_OFFSET - 1)) << DEXP_POSITION) | mantissa;
            second = FloatToUnsigned(floor(ldexp(fsMant - mantissa, 32)));
         }
      }
   }

   bytes[0] = first >> 24;
   bytes[1] = first >> 16;
   bytes[2] = first >> 8;
   bytes[3] = first;
   bytes[4] = second >> 24;
   bytes[5] = second >> 16;
   bytes[6] = second >> 8;
   bytes[7] = second;
}

#endif // BINIO_WITH_MATH

unsigned long binostream::writeString(const char *str, unsigned long amount) {
   unsigned int i;

   if (!amount) amount = strlen(str);

   for (i = 0; i < amount; i++) {
      putByte(str[i]);
      if (err) return i;
   }

   return amount;
}

#if BINIO_ENABLE_STRING
unsigned long binostream::writeString(const std::string &str) {
   return writeString(str.c_str());
}
#endif