/*

Copyright (C) 1996-2012 John W. Eaton

This file is part of Octave.

Octave is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the
Free Software Foundation; either version 3 of the License, or (at your
option) any later version.

Octave 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 General Public License
for more details.

You should have received a copy of the GNU General Public License
along with Octave; see the file COPYING.  If not, see
<http://www.gnu.org/licenses/>.

*/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <cctype>
#include <cstdlib>

#include <iostream>
#include <vector>

#include "byte-swap.h"
#include "data-conv.h"
#include "lo-error.h"
#include "lo-ieee.h"
#include "oct-locbuf.h"

template void swap_bytes<2> (volatile void *, int);
template void swap_bytes<4> (volatile void *, int);
template void swap_bytes<8> (volatile void *, int);

#if defined HAVE_LONG_LONG_INT
#define FIND_SIZED_INT_TYPE(VAL, BITS, TQ, Q) \
  do \
    { \
      int sz = BITS / CHAR_BIT; \
      if (sizeof (TQ char) == sz) \
        VAL = oct_data_conv::dt_ ## Q ## char; \
      else if (sizeof (TQ short) == sz) \
        VAL = oct_data_conv::dt_ ## Q ## short; \
      else if (sizeof (TQ int) == sz) \
        VAL = oct_data_conv::dt_ ## Q ## int; \
      else if (sizeof (TQ long) == sz) \
        VAL = oct_data_conv::dt_ ## Q ## long; \
      else if (sizeof (TQ long long) == sz) \
        VAL = oct_data_conv::dt_ ## Q ## longlong; \
      else \
        VAL = oct_data_conv::dt_unknown; \
    } \
  while (0)
#else
#define FIND_SIZED_INT_TYPE(VAL, BITS, TQ, Q) \
  do \
    { \
      int sz = BITS / CHAR_BIT; \
      if (sizeof (TQ char) == sz) \
        VAL = oct_data_conv::dt_ ## Q ## char; \
      else if (sizeof (TQ short) == sz) \
        VAL = oct_data_conv::dt_ ## Q ## short; \
      else if (sizeof (TQ int) == sz) \
        VAL = oct_data_conv::dt_ ## Q ## int; \
      else if (sizeof (TQ long) == sz) \
        VAL = oct_data_conv::dt_ ## Q ## long; \
      else \
        VAL = oct_data_conv::dt_unknown; \
    } \
  while (0)
#endif

#define FIND_SIZED_FLOAT_TYPE(VAL, BITS) \
  do \
    { \
      int sz = BITS / CHAR_BIT; \
      if (sizeof (float) == sz) \
        VAL = oct_data_conv::dt_float; \
      else if (sizeof (double) == sz) \
        VAL = oct_data_conv::dt_double; \
      else \
        VAL = oct_data_conv::dt_unknown; \
    } \
  while (0)

// I'm not sure it is worth the trouble, but let's use a lookup table
// for the types that are supposed to be a specific number of bits
// wide.  Given the macros above, this should work as long as CHAR_BIT
// is a multiple of 8 and there are types with the right sizes.
//
// The sized data type lookup table has the following format:
//
//                            bits
//                    +----+----+----+----+
//                    |  8 | 16 | 32 | 64 |
//                    +----+----+----+----+
//     signed integer |    |    |    |    |
//                    +----+----+----+----+
//   unsigned integer |    |    |    |    |
//                    +----+----+----+----+
//     floating point |    |    |    |    |
//                    +----+----+----+----+
//
// So, the 0,3 element is supposed to contain the oct_data_conv enum
// value corresponding to the correct native data type for a signed
// 32-bit integer.

static void
init_sized_type_lookup_table (oct_data_conv::data_type table[3][4])
{
  int bits = 8;

  for (int i = 0; i < 4; i++)
    {
      FIND_SIZED_INT_TYPE (table[0][i], bits, , );

      FIND_SIZED_INT_TYPE (table[1][i], bits, unsigned, u);

      FIND_SIZED_FLOAT_TYPE (table[2][i], bits);

      bits *= 2;
    }
}

static std::string
strip_spaces (const std::string& str)
{
  size_t n = str.length ();

  size_t k = 0;

  std::string s (n, ' ');

  for (size_t i = 0; i < n; i++)
    if (! isspace (str[i]))
      s[k++] = tolower (str[i]);

  s.resize (k);

  return s;
}

#define GET_SIZED_INT_TYPE(T, U) \
  do \
    { \
      switch (sizeof (T)) \
        { \
        case 1: \
          retval = dt_ ## U ## int8; \
          break; \
 \
        case 2: \
          retval = dt_ ## U ## int16; \
          break; \
 \
        case 4: \
          retval = dt_ ## U ## int32; \
          break; \
 \
        case 8: \
          retval = dt_ ## U ## int64; \
          break; \
 \
        default: \
          retval = dt_unknown; \
          break; \
        } \
    } \
  while (0)

oct_data_conv::data_type
oct_data_conv::string_to_data_type (const std::string& str)
{
  data_type retval = dt_unknown;

  static bool initialized = false;

  static data_type sized_type_table[3][4];

  if (! initialized)
    {
      init_sized_type_lookup_table (sized_type_table);

      initialized = true;
    }

  std::string s = strip_spaces (str);

  if (s == "int8" || s == "integer*1")
    retval = dt_int8;
  else if (s == "uint8")
    retval = dt_uint8;
  else if (s == "int16" || s == "integer*2")
    retval = dt_int16;
  else if (s == "uint16")
    retval = dt_uint16;
  else if (s == "int32" || s == "integer*4")
    retval = dt_int32;
  else if (s == "uint32")
    retval = dt_uint32;
  else if (s == "int64" || s == "integer*8")
    retval = dt_int64;
  else if (s == "uint64")
    retval = dt_uint64;
  else if (s == "single" || s == "float32" || s == "real*4")
    retval = dt_single;
  else if (s == "double" || s == "float64" || s == "real*8")
    retval = dt_double;
  else if (s == "char" || s == "char*1")
    retval = dt_char;
  else if (s == "schar" || s == "signedchar")
    retval = dt_schar;
  else if (s == "uchar" || s == "unsignedchar")
    retval = dt_uchar;
  else if (s == "short")
    GET_SIZED_INT_TYPE (short, );
  else if (s == "ushort" || s == "unsignedshort")
    GET_SIZED_INT_TYPE (unsigned short, u);
  else if (s == "int")
    GET_SIZED_INT_TYPE (int, );
  else if (s == "uint" || s == "unsignedint")
    GET_SIZED_INT_TYPE (unsigned int, u);
  else if (s == "long")
    GET_SIZED_INT_TYPE (long, );
  else if (s == "ulong" || s == "unsignedlong")
    GET_SIZED_INT_TYPE (unsigned long, u);
  else if (s == "longlong")
    GET_SIZED_INT_TYPE (long long, );
  else if (s == "ulonglong" || s == "unsignedlonglong")
    GET_SIZED_INT_TYPE (unsigned long long, u);
  else if (s == "float")
    {
      if (sizeof (float) == sizeof (double))
        retval = dt_double;
      else
        retval = dt_single;
    }
  else if (s == "logical")
    retval = dt_logical;
  else
    (*current_liboctave_error_handler) ("invalid data type specified");

  if (retval == dt_unknown)
    (*current_liboctave_error_handler)
      ("unable to find matching native data type for %s", s.c_str ());

  return retval;
}

void
oct_data_conv::string_to_data_type
  (const std::string& str, int& block_size,
   oct_data_conv::data_type& input_type,
   oct_data_conv::data_type& output_type)
{
  block_size = 1;
  input_type = dt_uchar;
  output_type = dt_double;

  bool input_is_output = false;

  std::string s = strip_spaces (str);

  size_t pos = 0;

  if (s[0] == '*')
    input_is_output = true;
  else
    {
      size_t len = s.length ();

      while (pos < len && isdigit (s[pos]))
        pos++;

      if (pos > 0)
        {
          if (s[pos] == '*')
            {
              block_size = atoi (s.c_str ());
              s = s.substr (pos+1);
            }
          else
            {
              (*current_liboctave_error_handler)
                ("invalid repeat count in `%s'", str.c_str ());

              return;
            }
        }
    }

  pos = s.find ('=');

  if (pos != std::string::npos)
    {
      if (s[pos+1] == '>')
        {
          std::string s1;

          if (input_is_output)
            {
              input_is_output = false;

              s1 = s.substr (1, pos-1);

              (*current_liboctave_warning_handler)
                ("warning: ignoring leading * in fread precision");
            }
          else
            s1 = s.substr (0, pos);

          input_type = string_to_data_type (s1);
          output_type = string_to_data_type (s.substr (pos+2));
        }
      else
        (*current_liboctave_error_handler)
          ("fread: invalid precision specified");
    }
  else
    {
      if (input_is_output)
        s = s.substr (1);

      input_type = string_to_data_type (s);

      if (input_is_output)
        output_type = input_type;
    }
}

void
oct_data_conv::string_to_data_type
  (const std::string& str, int& block_size,
   oct_data_conv::data_type& output_type)
{
  block_size = 1;
  output_type = dt_double;

  std::string s = strip_spaces (str);

  size_t pos = 0;

  size_t len = s.length ();

  while (pos < len && isdigit (s[pos]))
    pos++;

  if (pos > 0)
    {
      if (s[pos] == '*')
        {
          block_size = atoi (s.c_str ());
          s = s.substr (pos+1);
        }
      else
        {
          (*current_liboctave_error_handler)
            ("invalid repeat count in `%s'", str.c_str ());

          return;
        }
    }

  output_type = string_to_data_type (s);
}

std::string
oct_data_conv::data_type_as_string (oct_data_conv::data_type dt)
{
  std::string retval;

  switch (dt)
    {
    case oct_data_conv::dt_int8:
      retval = "int8";
      break;

    case oct_data_conv::dt_uint8:
      retval = "uint8";
      break;

    case oct_data_conv::dt_int16:
      retval = "int16";
      break;

    case oct_data_conv::dt_uint16:
      retval = "uint16";
      break;

    case oct_data_conv::dt_int32:
      retval = "int32";
      break;

    case oct_data_conv::dt_uint32:
      retval = "uint32";
      break;

    case oct_data_conv::dt_int64:
      retval = "int64";
      break;

    case oct_data_conv::dt_uint64:
      retval = "uint64";
      break;

    case oct_data_conv::dt_single:
      retval = "single";
      break;

    case oct_data_conv::dt_double:
      retval = "double";
      break;

    case oct_data_conv::dt_char:
      retval = "char";
      break;

    case oct_data_conv::dt_schar:
      retval = "signed char";
      break;

    case oct_data_conv::dt_uchar:
      retval = "usigned char";
      break;

    case oct_data_conv::dt_short:
      retval = "short";
      break;

    case oct_data_conv::dt_ushort:
      retval = "unsigned short";
      break;

    case oct_data_conv::dt_int:
      retval = "int";
      break;

    case oct_data_conv::dt_uint:
      retval = "usigned int";
      break;

    case oct_data_conv::dt_long:
      retval = "long";
      break;

    case oct_data_conv::dt_ulong:
      retval = "usigned long";
      break;

    case oct_data_conv::dt_longlong:
      retval = "long long";
      break;

    case oct_data_conv::dt_ulonglong:
      retval = "unsigned long long";
      break;

    case oct_data_conv::dt_float:
      retval = "float";
      break;

    case oct_data_conv::dt_logical:
      retval = "logical";
      break;

    case oct_data_conv::dt_unknown:
    default:
      retval = "unknown";
      break;
    }

  return retval;
}

#define LS_DO_READ(TYPE, swap, data, size, len, stream) \
  do \
    { \
      if (len > 0) \
        { \
          OCTAVE_LOCAL_BUFFER (TYPE, ptr, len); \
          stream.read (reinterpret_cast<char *>  (ptr), size * len); \
          if (swap) \
            swap_bytes< size > (ptr, len); \
          for (octave_idx_type i = 0; i < len; i++) \
            data[i] = ptr[i]; \
        } \
    } \
  while (0)

// Have to use copy here to avoid writing over data accessed via
// Matrix::data().

#define LS_DO_WRITE(TYPE, data, size, len, stream) \
  do \
    { \
      if (len > 0) \
        { \
          char tmp_type = type; \
          stream.write (&tmp_type, 1); \
          OCTAVE_LOCAL_BUFFER (TYPE, ptr, len); \
          for (octave_idx_type i = 0; i < len; i++) \
            ptr[i] = static_cast <TYPE> (data[i]);         \
          stream.write (reinterpret_cast<char *> (ptr), size * len); \
        } \
    } \
  while (0)

// Loading variables from files.

static void
gripe_unrecognized_float_fmt (void)
{
  (*current_liboctave_error_handler)
    ("unrecognized floating point format requested");
}

static void
gripe_data_conversion (const char *from, const char *to)
{
  (*current_liboctave_error_handler)
    ("unable to convert from %s to %s format", from, to);
}

// But first, some data conversion routines.

// Currently, we only handle conversions for the IEEE types.  To fix
// that, make more of the following routines work.

// FIXME -- assumes sizeof (Complex) == 8
// FIXME -- assumes sizeof (double) == 8
// FIXME -- assumes sizeof (float) == 4

static void
IEEE_big_double_to_IEEE_little_double (void *d, octave_idx_type len)
{
  swap_bytes<8> (d, len);
}

static void
VAX_D_double_to_IEEE_little_double (void * /* d */, octave_idx_type /* len */)
{
  gripe_data_conversion ("VAX D float", "IEEE little endian format");
}

static void
VAX_G_double_to_IEEE_little_double (void * /* d */, octave_idx_type /* len */)
{
  gripe_data_conversion ("VAX G float", "IEEE little endian format");
}

static void
Cray_to_IEEE_little_double (void * /* d */, octave_idx_type /* len */)
{
  gripe_data_conversion ("Cray", "IEEE little endian format");
}

static void
IEEE_big_float_to_IEEE_little_float (void *d, octave_idx_type len)
{
  swap_bytes<4> (d, len);
}

static void
VAX_D_float_to_IEEE_little_float (void * /* d */, octave_idx_type /* len */)
{
  gripe_data_conversion ("VAX D float", "IEEE little endian format");
}

static void
VAX_G_float_to_IEEE_little_float (void * /* d */, octave_idx_type /* len */)
{
  gripe_data_conversion ("VAX G float", "IEEE little endian format");
}

static void
Cray_to_IEEE_little_float (void * /* d */, octave_idx_type /* len */)
{
  gripe_data_conversion ("Cray", "IEEE little endian format");
}

static void
IEEE_little_double_to_IEEE_big_double (void *d, octave_idx_type len)
{
  swap_bytes<8> (d, len);
}

static void
VAX_D_double_to_IEEE_big_double (void * /* d */, octave_idx_type /* len */)
{
  gripe_data_conversion ("VAX D float", "IEEE big endian format");
}

static void
VAX_G_double_to_IEEE_big_double (void * /* d */, octave_idx_type /* len */)
{
  gripe_data_conversion ("VAX G float", "IEEE big endian format");
}

static void
Cray_to_IEEE_big_double (void * /* d */, octave_idx_type /* len */)
{
  gripe_data_conversion ("Cray", "IEEE big endian format");
}

static void
IEEE_little_float_to_IEEE_big_float (void *d, octave_idx_type len)
{
  swap_bytes<4> (d, len);
}

static void
VAX_D_float_to_IEEE_big_float (void * /* d */, octave_idx_type /* len */)
{
  gripe_data_conversion ("VAX D float", "IEEE big endian format");
}

static void
VAX_G_float_to_IEEE_big_float (void * /* d */, octave_idx_type /* len */)
{
  gripe_data_conversion ("VAX G float", "IEEE big endian format");
}

static void
Cray_to_IEEE_big_float (void * /* d */, octave_idx_type /* len */)
{
  gripe_data_conversion ("Cray", "IEEE big endian format");
}

static void
IEEE_little_double_to_VAX_D_double (void * /* d */, octave_idx_type /* len */)
{
  gripe_data_conversion ("IEEE little endian", "VAX D");
}

static void
IEEE_big_double_to_VAX_D_double (void * /* d */, octave_idx_type /* len */)
{
  gripe_data_conversion ("IEEE big endian", "VAX D");
}

static void
VAX_G_double_to_VAX_D_double (void * /* d */, octave_idx_type /* len */)
{
  gripe_data_conversion ("VAX G float", "VAX D");
}

static void
Cray_to_VAX_D_double (void * /* d */, octave_idx_type /* len */)
{
  gripe_data_conversion ("Cray", "VAX D");
}

static void
IEEE_little_float_to_VAX_D_float (void * /* d */, octave_idx_type /* len */)
{
  gripe_data_conversion ("IEEE little endian", "VAX D");
}

static void
IEEE_big_float_to_VAX_D_float (void * /* d */, octave_idx_type /* len */)
{
  gripe_data_conversion ("IEEE big endian", "VAX D");
}

static void
VAX_G_float_to_VAX_D_float (void * /* d */, octave_idx_type /* len */)
{
  gripe_data_conversion ("VAX G float", "VAX D");
}

static void
Cray_to_VAX_D_float (void * /* d */, octave_idx_type /* len */)
{
  gripe_data_conversion ("Cray", "VAX D");
}

static void
IEEE_little_double_to_VAX_G_double (void * /* d */, octave_idx_type /* len */)
{
  gripe_data_conversion ("IEEE little endian", "VAX G");
}

static void
IEEE_big_double_to_VAX_G_double (void * /* d */, octave_idx_type /* len */)
{
  gripe_data_conversion ("IEEE big endian", "VAX G");
}

static void
VAX_D_double_to_VAX_G_double (void * /* d */, octave_idx_type /* len */)
{
  gripe_data_conversion ("VAX D float", "VAX G");
}

static void
Cray_to_VAX_G_double (void * /* d */, octave_idx_type /* len */)
{
  gripe_data_conversion ("VAX G float", "VAX G");
}

static void
IEEE_little_float_to_VAX_G_float (void * /* d */, octave_idx_type /* len */)
{
  gripe_data_conversion ("IEEE little endian", "VAX G");
}

static void
IEEE_big_float_to_VAX_G_float (void * /* d */, octave_idx_type /* len */)
{
  gripe_data_conversion ("IEEE big endian", "VAX G");
}

static void
VAX_D_float_to_VAX_G_float (void * /* d */, octave_idx_type /* len */)
{
  gripe_data_conversion ("VAX D float", "VAX G");
}

static void
Cray_to_VAX_G_float (void * /* d */, octave_idx_type /* len */)
{
  gripe_data_conversion ("VAX G float", "VAX G");
}

void
do_double_format_conversion (void *data, octave_idx_type len,
                             oct_mach_info::float_format from_fmt,
                             oct_mach_info::float_format to_fmt)
{
  switch (to_fmt)
    {
    case oct_mach_info::flt_fmt_ieee_little_endian:
      switch (from_fmt)
        {
        case oct_mach_info::flt_fmt_ieee_little_endian:
          break;

        case oct_mach_info::flt_fmt_ieee_big_endian:
          IEEE_big_double_to_IEEE_little_double (data, len);
          break;

        case oct_mach_info::flt_fmt_vax_d:
          VAX_D_double_to_IEEE_little_double (data, len);
          break;

        case oct_mach_info::flt_fmt_vax_g:
          VAX_G_double_to_IEEE_little_double (data, len);
          break;

        case oct_mach_info::flt_fmt_cray:
          Cray_to_IEEE_little_double (data, len);
          break;

        default:
          gripe_unrecognized_float_fmt ();
          break;
        }
      break;

    case oct_mach_info::flt_fmt_ieee_big_endian:
      switch (from_fmt)
        {
        case oct_mach_info::flt_fmt_ieee_little_endian:
          IEEE_little_double_to_IEEE_big_double (data, len);
          break;

        case oct_mach_info::flt_fmt_ieee_big_endian:
          break;

        case oct_mach_info::flt_fmt_vax_d:
          VAX_D_double_to_IEEE_big_double (data, len);
          break;

        case oct_mach_info::flt_fmt_vax_g:
          VAX_G_double_to_IEEE_big_double (data, len);
          break;

        case oct_mach_info::flt_fmt_cray:
          Cray_to_IEEE_big_double (data, len);
          break;

        default:
          gripe_unrecognized_float_fmt ();
          break;
        }
      break;

    case oct_mach_info::flt_fmt_vax_d:
      switch (from_fmt)
        {
        case oct_mach_info::flt_fmt_ieee_little_endian:
          IEEE_little_double_to_VAX_D_double (data, len);
          break;

        case oct_mach_info::flt_fmt_ieee_big_endian:
          IEEE_big_double_to_VAX_D_double (data, len);
          break;

        case oct_mach_info::flt_fmt_vax_d:
          break;

        case oct_mach_info::flt_fmt_vax_g:
          VAX_G_double_to_VAX_D_double (data, len);
          break;

        case oct_mach_info::flt_fmt_cray:
          Cray_to_VAX_D_double (data, len);
          break;

        default:
          gripe_unrecognized_float_fmt ();
          break;
        }
      break;

    case oct_mach_info::flt_fmt_vax_g:
      switch (from_fmt)
        {
        case oct_mach_info::flt_fmt_ieee_little_endian:
          IEEE_little_double_to_VAX_G_double (data, len);
          break;

        case oct_mach_info::flt_fmt_ieee_big_endian:
          IEEE_big_double_to_VAX_G_double (data, len);
          break;

        case oct_mach_info::flt_fmt_vax_d:
          VAX_D_double_to_VAX_G_double (data, len);
          break;

        case oct_mach_info::flt_fmt_vax_g:
          break;

        case oct_mach_info::flt_fmt_cray:
          Cray_to_VAX_G_double (data, len);
          break;

        default:
          gripe_unrecognized_float_fmt ();
          break;
        }
      break;

    default:
      (*current_liboctave_error_handler)
        ("impossible state reached in file `%s' at line %d",
         __FILE__, __LINE__);
      break;
    }
}

void
do_float_format_conversion (void *data, octave_idx_type len,
                            oct_mach_info::float_format from_fmt,
                            oct_mach_info::float_format to_fmt)
{
  switch (to_fmt)
    {
    case oct_mach_info::flt_fmt_ieee_little_endian:
      switch (from_fmt)
        {
        case oct_mach_info::flt_fmt_ieee_little_endian:
          break;

        case oct_mach_info::flt_fmt_ieee_big_endian:
          IEEE_big_float_to_IEEE_little_float (data, len);
          break;

        case oct_mach_info::flt_fmt_vax_d:
          VAX_D_float_to_IEEE_little_float (data, len);
          break;

        case oct_mach_info::flt_fmt_vax_g:
          VAX_G_float_to_IEEE_little_float (data, len);
          break;

        case oct_mach_info::flt_fmt_cray:
          Cray_to_IEEE_little_float (data, len);
          break;

        default:
          gripe_unrecognized_float_fmt ();
          break;
        }
      break;

    case oct_mach_info::flt_fmt_ieee_big_endian:
      switch (from_fmt)
        {
        case oct_mach_info::flt_fmt_ieee_little_endian:
          IEEE_little_float_to_IEEE_big_float (data, len);
          break;

        case oct_mach_info::flt_fmt_ieee_big_endian:
          break;

        case oct_mach_info::flt_fmt_vax_d:
          VAX_D_float_to_IEEE_big_float (data, len);
          break;

        case oct_mach_info::flt_fmt_vax_g:
          VAX_G_float_to_IEEE_big_float (data, len);
          break;

        case oct_mach_info::flt_fmt_cray:
          Cray_to_IEEE_big_float (data, len);
          break;

        default:
          gripe_unrecognized_float_fmt ();
          break;
        }
      break;

    case oct_mach_info::flt_fmt_vax_d:
      switch (from_fmt)
        {
        case oct_mach_info::flt_fmt_ieee_little_endian:
          IEEE_little_float_to_VAX_D_float (data, len);
          break;

        case oct_mach_info::flt_fmt_ieee_big_endian:
          IEEE_big_float_to_VAX_D_float (data, len);
          break;

        case oct_mach_info::flt_fmt_vax_d:
          break;

        case oct_mach_info::flt_fmt_vax_g:
          VAX_G_float_to_VAX_D_float (data, len);
          break;

        case oct_mach_info::flt_fmt_cray:
          Cray_to_VAX_D_float (data, len);
          break;

        default:
          gripe_unrecognized_float_fmt ();
          break;
        }
      break;

    case oct_mach_info::flt_fmt_vax_g:
      switch (from_fmt)
        {
        case oct_mach_info::flt_fmt_ieee_little_endian:
          IEEE_little_float_to_VAX_G_float (data, len);
          break;

        case oct_mach_info::flt_fmt_ieee_big_endian:
          IEEE_big_float_to_VAX_G_float (data, len);
          break;

        case oct_mach_info::flt_fmt_vax_d:
          VAX_D_float_to_VAX_G_float (data, len);
          break;

        case oct_mach_info::flt_fmt_vax_g:
          break;

        case oct_mach_info::flt_fmt_cray:
          Cray_to_VAX_G_float (data, len);
          break;

        default:
          gripe_unrecognized_float_fmt ();
          break;
        }
      break;

    default:
      (*current_liboctave_error_handler)
        ("impossible state reached in file `%s' at line %d",
         __FILE__, __LINE__);
      break;
    }
}

void
do_float_format_conversion (void *data, size_t sz, octave_idx_type len,
                            oct_mach_info::float_format from_fmt,
                            oct_mach_info::float_format to_fmt)
{
  switch (sz)
    {
    case sizeof (float):
      do_float_format_conversion (data, len, from_fmt, to_fmt);
      break;

    case sizeof (double):
      do_double_format_conversion (data, len, from_fmt, to_fmt);
      break;

    default:
      (*current_liboctave_error_handler)
        ("impossible state reached in file `%s' at line %d",
         __FILE__, __LINE__);
      break;
    }
}


void
read_doubles (std::istream& is, double *data, save_type type,
              octave_idx_type len, bool swap,
              oct_mach_info::float_format fmt)
{
  switch (type)
    {
    case LS_U_CHAR:
      LS_DO_READ (uint8_t, swap, data, 1, len, is);
      break;

    case LS_U_SHORT:
      LS_DO_READ (uint16_t, swap, data, 2, len, is);
      break;

    case LS_U_INT:
      LS_DO_READ (uint32_t, swap, data, 4, len, is);
      break;

    case LS_CHAR:
      LS_DO_READ (int8_t, swap, data, 1, len, is);
      break;

    case LS_SHORT:
      LS_DO_READ (int16_t, swap, data, 2, len, is);
      break;

    case LS_INT:
      LS_DO_READ (int32_t, swap, data, 4, len, is);
      break;

    case LS_FLOAT:
      {
        OCTAVE_LOCAL_BUFFER (float, ptr, len);
        is.read (reinterpret_cast<char *> (ptr), 4 * len);
        do_float_format_conversion (ptr, len, fmt);
        for (octave_idx_type i = 0; i < len; i++)
          data[i] = ptr[i];
      }
      break;

    case LS_DOUBLE: // No conversion necessary.
      {
        is.read (reinterpret_cast<char *> (data), 8 * len);
        do_double_format_conversion (data, len, fmt);

        for (int i = 0; i < len; i++)
          data[i] = __lo_ieee_replace_old_NA (data[i]);
      }
      break;

    default:
      is.clear (std::ios::failbit|is.rdstate ());
      break;
    }
}

void
read_floats (std::istream& is, float *data, save_type type,
             octave_idx_type len, bool swap,
             oct_mach_info::float_format fmt)
{
  switch (type)
    {
    case LS_U_CHAR:
      LS_DO_READ (uint8_t, swap, data, 1, len, is);
      break;

    case LS_U_SHORT:
      LS_DO_READ (uint16_t, swap, data, 2, len, is);
      break;

    case LS_U_INT:
      LS_DO_READ (uint32_t, swap, data, 4, len, is);
      break;

    case LS_CHAR:
      LS_DO_READ (int8_t, swap, data, 1, len, is);
      break;

    case LS_SHORT:
      LS_DO_READ (int16_t, swap, data, 2, len, is);
      break;

    case LS_INT:
      LS_DO_READ (int32_t, swap, data, 4, len, is);
      break;

    case LS_FLOAT: // No conversion necessary.
      is.read (reinterpret_cast<char *> (data), 4 * len);
      do_float_format_conversion (data, len, fmt);
      break;

    case LS_DOUBLE:
      {
        OCTAVE_LOCAL_BUFFER (double, ptr, len);
        is.read (reinterpret_cast<char *> (ptr), 8 * len);
        do_double_format_conversion (ptr, len, fmt);
        for (octave_idx_type i = 0; i < len; i++)
          data[i] = ptr[i];
      }
      break;

    default:
      is.clear (std::ios::failbit|is.rdstate ());
      break;
    }
}

void
write_doubles (std::ostream& os, const double *data, save_type type,
               octave_idx_type len)
{
  switch (type)
    {
    case LS_U_CHAR:
      LS_DO_WRITE (uint8_t, data, 1, len, os);
      break;

    case LS_U_SHORT:
      LS_DO_WRITE (uint16_t, data, 2, len, os);
      break;

    case LS_U_INT:
      LS_DO_WRITE (uint32_t, data, 4, len, os);
      break;

    case LS_CHAR:
      LS_DO_WRITE (int8_t, data, 1, len, os);
      break;

    case LS_SHORT:
      LS_DO_WRITE (int16_t, data, 2, len, os);
      break;

    case LS_INT:
      LS_DO_WRITE (int32_t, data, 4, len, os);
      break;

    case LS_FLOAT:
      LS_DO_WRITE (float, data, 4, len, os);
      break;

    case LS_DOUBLE: // No conversion necessary.
      {
        char tmp_type = static_cast<char> (type);
        os.write (&tmp_type, 1);
        os.write (reinterpret_cast <const char *> (data), 8 * len);
      }
      break;

    default:
      (*current_liboctave_error_handler)
        ("unrecognized data format requested");
      break;
    }
}

void
write_floats (std::ostream& os, const float *data, save_type type,
              octave_idx_type len)
{
  switch (type)
    {
    case LS_U_CHAR:
      LS_DO_WRITE (uint8_t, data, 1, len, os);
      break;

    case LS_U_SHORT:
      LS_DO_WRITE (uint16_t, data, 2, len, os);
      break;

    case LS_U_INT:
      LS_DO_WRITE (uint32_t, data, 4, len, os);
      break;

    case LS_CHAR:
      LS_DO_WRITE (int8_t, data, 1, len, os);
      break;

    case LS_SHORT:
      LS_DO_WRITE (int16_t, data, 2, len, os);
      break;

    case LS_INT:
      LS_DO_WRITE (int32_t, data, 4, len, os);
      break;

    case LS_FLOAT: // No conversion necessary.
      {
        char tmp_type = static_cast<char> (type);
        os.write (&tmp_type, 1);
        os.write (reinterpret_cast <const char *> (data), 4 * len);
      }
      break;

    case LS_DOUBLE:
      LS_DO_WRITE (double, data, 8, len, os);
      break;

    default:
      (*current_liboctave_error_handler)
        ("unrecognized data format requested");
      break;
    }
}
