/*

Copyright (C) 1996, 1997, 2003, 2004, 2005, 2006, 2007 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/>.

*/

// Author: James R. Van Zandt <jrv@vanzandt.mv.com>

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

#include <cfloat>
#include <cstring>
#include <cctype>

#include <fstream>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>

#include "byte-swap.h"
#include "data-conv.h"
#include "file-ops.h"
#include "glob-match.h"
#include "lo-mappers.h"
#include "mach-info.h"
#include "oct-env.h"
#include "oct-time.h"
#include "quit.h"
#include "str-vec.h"
#include "file-stat.h"

#include "Cell.h"
#include "defun.h"
#include "error.h"
#include "gripes.h"
#include "load-save.h"
#include "load-path.h"
#include "oct-obj.h"
#include "oct-map.h"
#include "ov-cell.h"
#include "ov-fcn-inline.h"
#include "pager.h"
#include "pt-exp.h"
#include "symtab.h"
#include "sysdep.h"
#include "unwind-prot.h"
#include "utils.h"
#include "variables.h"
#include "version.h"
#include "dMatrix.h"

#include "ls-utils.h"
#include "ls-mat5.h"

#include "parse.h"
#include "defaults.h"

#ifdef HAVE_ZLIB
#include <zlib.h>
#endif

#define PAD(l) (((l) > 0 && (l) <= 4) ? 4 : (((l)+7)/8)*8)


// The subsystem data block
static octave_value subsys_ov;

// FIXME -- the following enum values should be the same as the
// mxClassID values in mexproto.h, but it seems they have also changed
// over time.  What is the correct way to handle this and maintain
// backward compatibility with old MAT files?  For now, use
// "MAT_FILE_" instead of "mx" as the prefix for these names to avoid
// conflict with the mxClassID enum in mexproto.h.

enum arrayclasstype
  {
    MAT_FILE_CELL_CLASS=1,		// cell array
    MAT_FILE_STRUCT_CLASS,		// structure
    MAT_FILE_OBJECT_CLASS,		// object
    MAT_FILE_CHAR_CLASS,		// character array
    MAT_FILE_SPARSE_CLASS,		// sparse array
    MAT_FILE_DOUBLE_CLASS,		// double precision array
    MAT_FILE_SINGLE_CLASS,		// single precision floating point
    MAT_FILE_INT8_CLASS,		// 8 bit signed integer
    MAT_FILE_UINT8_CLASS,		// 8 bit unsigned integer
    MAT_FILE_INT16_CLASS,		// 16 bit signed integer
    MAT_FILE_UINT16_CLASS,		// 16 bit unsigned integer
    MAT_FILE_INT32_CLASS,		// 32 bit signed integer
    MAT_FILE_UINT32_CLASS,		// 32 bit unsigned integer
    MAT_FILE_INT64_CLASS,		// 64 bit signed integer
    MAT_FILE_UINT64_CLASS,		// 64 bit unsigned integer
    MAT_FILE_FUNCTION_CLASS,            // Function handle
    MAT_FILE_WORKSPACE_CLASS		// Workspace (undocumented)
  };

// Read COUNT elements of data from IS in the format specified by TYPE,
// placing the result in DATA.  If SWAP is TRUE, swap the bytes of
// each element before copying to DATA.  FLT_FMT specifies the format
// of the data if we are reading floating point numbers.

static void
read_mat5_binary_data (std::istream& is, double *data,
		       int count, bool swap, mat5_data_type type,
		       oct_mach_info::float_format flt_fmt)
{
  
  switch (type)
    {
    case miINT8:
      read_doubles (is, data, LS_CHAR, count, swap, flt_fmt);
      break;

    case miUTF8:
    case miUINT8:
      read_doubles (is, data, LS_U_CHAR, count, swap, flt_fmt);
      break;

    case miINT16:
      read_doubles (is, data, LS_SHORT, count, swap, flt_fmt);
      break;

    case miUTF16:
    case miUINT16:
      read_doubles (is, data, LS_U_SHORT, count, swap, flt_fmt);
      break;

    case miINT32:
      read_doubles (is, data, LS_INT, count, swap, flt_fmt);
      break;

    case miUTF32:
    case miUINT32:
      read_doubles (is, data, LS_U_INT, count, swap, flt_fmt);
      break;

    case miSINGLE:
      read_doubles (is, data, LS_FLOAT, count, swap, flt_fmt);
      break;

    case miRESERVE1:
      break;

    case miDOUBLE:
      read_doubles (is, data, LS_DOUBLE, count, swap, flt_fmt);
      break;

    case miRESERVE2:
    case miRESERVE3:
      break;

    // FIXME -- how are the 64-bit cases supposed to work here?
    case miINT64:
      read_doubles (is, data, LS_LONG, count, swap, flt_fmt);
      break;

    case miUINT64:
      read_doubles (is, data, LS_U_LONG, count, swap, flt_fmt);
      break;

    case miMATRIX:
    default:
      break;
    }
}

template <class T>
void
read_mat5_integer_data (std::istream& is, T *m, int count, bool swap,
			mat5_data_type type)
{

#define READ_INTEGER_DATA(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 (int i = 0; i < len; i++) \
	    data[i] = ptr[i]; \
	} \
    } \
  while (0)

  switch (type)
    {
    case miINT8:
      READ_INTEGER_DATA (int8_t, swap, m, 1, count, is);
      break;

    case miUINT8:
      READ_INTEGER_DATA (uint8_t, swap, m, 1, count, is);
      break;

    case miINT16:
      READ_INTEGER_DATA (int16_t, swap, m, 2, count, is);
      break;

    case miUINT16:
      READ_INTEGER_DATA (uint16_t, swap, m, 2, count, is);
      break;

    case miINT32:
      READ_INTEGER_DATA (int32_t, swap, m, 4, count, is);
      break;

    case miUINT32:
      READ_INTEGER_DATA (uint32_t, swap, m, 4, count, is);
      break;

    case miSINGLE:
    case miRESERVE1:
    case miDOUBLE:
    case miRESERVE2:
    case miRESERVE3:
      break;

    case miINT64:
      READ_INTEGER_DATA (int64_t, swap, m, 8, count, is);
      break;

    case miUINT64:
      READ_INTEGER_DATA (uint64_t, swap, m, 8, count, is);
      break;

    case miMATRIX:
    default:
      break;
    }

#undef READ_INTEGER_DATA

}

template void read_mat5_integer_data (std::istream& is, octave_int8 *m,
				      int count, bool swap,
				      mat5_data_type type);
template void read_mat5_integer_data (std::istream& is, octave_int16 *m,
				      int count, bool swap,
				      mat5_data_type type);
template void read_mat5_integer_data (std::istream& is, octave_int32 *m,
				      int count, bool swap,
				      mat5_data_type type);
template void read_mat5_integer_data (std::istream& is, octave_int64 *m,
				      int count, bool swap,
				      mat5_data_type type);
template void read_mat5_integer_data (std::istream& is, octave_uint8 *m,
				      int count, bool swap,
				      mat5_data_type type);
template void read_mat5_integer_data (std::istream& is, octave_uint16 *m,
				      int count, bool swap,
				      mat5_data_type type);
template void read_mat5_integer_data (std::istream& is, octave_uint32 *m,
				      int count, bool swap,
				      mat5_data_type type);
template void read_mat5_integer_data (std::istream& is, octave_uint64 *m,
				      int count, bool swap,
				      mat5_data_type type);

template void read_mat5_integer_data (std::istream& is, int *m,
				      int count, bool swap,
				      mat5_data_type type);

#define OCTAVE_MAT5_INTEGER_READ(TYP) \
  { \
	TYP re (dims); \
  \
	std::streampos tmp_pos; \
  \
	if (read_mat5_tag (is, swap, type, len)) \
	  { \
	    error ("load: reading matrix data for `%s'", retval.c_str ()); \
	    goto data_read_error; \
	  } \
  \
	int n = re.length (); \
	tmp_pos = is.tellg (); \
	read_mat5_integer_data (is, re.fortran_vec (), n, swap,	\
				static_cast<enum mat5_data_type> (type)); \
  \
	if (! is || error_state) \
	  { \
	    error ("load: reading matrix data for `%s'", retval.c_str ()); \
	    goto data_read_error; \
	  } \
  \
	is.seekg (tmp_pos + static_cast<std::streamoff> (PAD (len))); \
  \
	if (imag) \
	  { \
	    /* We don't handle imag integer types, convert to an array */ \
	    NDArray im (dims); \
  \
	    if (read_mat5_tag (is, swap, type, len)) \
	      { \
		error ("load: reading matrix data for `%s'", \
		       retval.c_str ()); \
		goto data_read_error; \
	      } \
  \
	    n = im.length (); \
	    read_mat5_binary_data (is, im.fortran_vec (), n, swap, \
				   static_cast<enum mat5_data_type> (type), flt_fmt); \
  \
	    if (! is || error_state) \
	      { \
		error ("load: reading imaginary matrix data for `%s'", \
		       retval.c_str ()); \
		goto data_read_error; \
	      } \
  \
	    ComplexNDArray ctmp (dims); \
  \
	    for (int i = 0; i < n; i++) \
	      ctmp(i) = Complex (re(i).double_value (), im(i)); \
  \
            tc = ctmp;  \
	  } \
	else \
	  tc = re; \
  }
  
// Read one element tag from stream IS, 
// place the type code in TYPE and the byte count in BYTES
// return nonzero on error
static int
read_mat5_tag (std::istream& is, bool swap, int32_t& type, int32_t& bytes)
{
  unsigned int upper;
  int32_t temp;

  if (! is.read (reinterpret_cast<char *> (&temp), 4 ))
    goto data_read_error;

  if (swap)
    swap_bytes<4> (&temp);

  upper = (temp >> 16) & 0xffff;
  type = temp & 0xffff;

  if (upper)
    {
      // "compressed" format
      bytes = upper;
    }
  else
    {
      if (! is.read (reinterpret_cast<char *> (&temp), 4 ))
	goto data_read_error;
      if (swap)
	swap_bytes<4> (&temp);
      bytes = temp;
    }

  return 0;

 data_read_error:
  return 1;
}

static void
read_int (std::istream& is, bool swap, int32_t& val)
{
  is.read (reinterpret_cast<char *> (&val), 4);

  if (swap)
    swap_bytes<4> (&val);
}

// Extract one data element (scalar, matrix, string, etc.) from stream
// IS and place it in TC, returning the name of the variable.
//
// The data is expected to be in Matlab's "Version 5" .mat format,
// though not all the features of that format are supported.
//
// FILENAME is used for error messages.

std::string
read_mat5_binary_element (std::istream& is, const std::string& filename,
			  bool swap, bool& global, octave_value& tc)
{
  std::string retval;

  // These are initialized here instead of closer to where they are
  // first used to avoid errors from gcc about goto crossing
  // initialization of variable.

  oct_mach_info::float_format flt_fmt = oct_mach_info::flt_fmt_unknown;
  int32_t type = 0;
  std::string classname;
  bool isclass = false;
  bool imag;
  bool logicalvar;
  enum arrayclasstype arrayclass;
  int32_t nzmax;
  int32_t flags;
  dim_vector dims;
  int32_t len;
  int32_t element_length;
  std::streampos pos;
  int16_t number;
  number = *(reinterpret_cast<const int16_t *>("\x00\x01"));

  global = false;

  // MAT files always use IEEE floating point
  if ((number == 1) ^ swap)
    flt_fmt = oct_mach_info::flt_fmt_ieee_big_endian;
  else
    flt_fmt = oct_mach_info::flt_fmt_ieee_little_endian;

  // element type and length
  if (read_mat5_tag (is, swap, type, element_length))
    return retval;			// EOF

#ifdef HAVE_ZLIB
  if (type == miCOMPRESSED)
    {
      // If C++ allowed us direct access to the file descriptor of an ifstream 
      // in a uniform way, the code below could be vastly simplified, and 
      // additional copies of the data in memory wouldn't be needed!!

      OCTAVE_LOCAL_BUFFER (char, inbuf, element_length);
      is.read (inbuf, element_length);

      // We uncompress the first 8 bytes of the header to get the buffer length
      // This will fail with an error Z_MEM_ERROR
      uLongf destLen = 8;
      OCTAVE_LOCAL_BUFFER (unsigned int, tmp, 2);
      if (uncompress (reinterpret_cast<Bytef *> (tmp), &destLen, 
		      reinterpret_cast<Bytef *> (inbuf), element_length)
	  !=  Z_MEM_ERROR)
	{
	  // Why should I have to initialize outbuf as I'll just overwrite!!
	  if (swap)
	    swap_bytes<4> (tmp, 2);

	  destLen = tmp[1] + 8;
	  std::string outbuf (destLen, ' '); 

	  // FIXME -- find a way to avoid casting away const here!

	  int err = uncompress (reinterpret_cast<Bytef *> (const_cast<char *> (outbuf.c_str ())), &destLen, 
				reinterpret_cast<Bytef *> (inbuf), element_length);

	  if (err != Z_OK)
	    error ("load: error uncompressing data element");
	  else
	    {
	      std::istringstream gz_is (outbuf);
	      retval = read_mat5_binary_element (gz_is, filename, 
						 swap, global, tc);
	    }
	}
      else
	error ("load: error probing size of compressed data element");

      return retval;
    }
#endif

  if (type != miMATRIX)
    {
      pos = is.tellg ();
      error ("load: invalid element type = %d", type);
      goto early_read_error;
    }

  if (element_length == 0)
    {
      tc = Matrix ();
      return retval;
    }

  pos = is.tellg ();

  // array flags subelement
  if (read_mat5_tag (is, swap, type, len) || type != miUINT32 || len != 8)
    {
      error ("load: invalid array flags subelement");
      goto early_read_error;
    }

  read_int (is, swap, flags);
  imag = (flags & 0x0800) != 0;	// has an imaginary part?
  global = (flags & 0x0400) != 0; // global variable?
  logicalvar = (flags & 0x0200) != 0; // boolean ?
  arrayclass = static_cast<arrayclasstype> (flags & 0xff);
  read_int (is, swap, nzmax);	// max number of non-zero in sparse
  
  // dimensions array subelement
  if (arrayclass != MAT_FILE_WORKSPACE_CLASS)
    {
      int32_t dim_len;

      if (read_mat5_tag (is, swap, type, dim_len) || type != miINT32)
	{
	  error ("load: invalid dimensions array subelement");
	  goto early_read_error;
	}

      int ndims = dim_len / 4;
      dims.resize (ndims);
      for (int i = 0; i < ndims; i++)
	{
	  int32_t n;
	  read_int (is, swap, n);
	  dims(i) = n;
	}

      std::streampos tmp_pos = is.tellg ();
      is.seekg (tmp_pos + static_cast<std::streamoff> (PAD (dim_len) - dim_len));
    }
  else
    {
      // Why did mathworks decide to not have dims for a workspace!!!
      dims.resize(2);
      dims(0) = 1;
      dims(1) = 1;
    }

  if (read_mat5_tag (is, swap, type, len) || type != miINT8)
    {
      error ("load: invalid array name subelement");
      goto early_read_error;
    }

  {
    OCTAVE_LOCAL_BUFFER (char, name, len+1);

    // Structure field subelements have zero-length array name subelements.

    std::streampos tmp_pos = is.tellg ();

    if (len)
      {
	if (! is.read (name, len ))
	  goto data_read_error;
	
	is.seekg (tmp_pos + static_cast<std::streamoff> (PAD (len)));
      }

    name[len] = '\0';
    retval = name;
  }

  switch (arrayclass)
    {
    case MAT_FILE_CELL_CLASS:
      {
	Cell cell_array (dims);

	int n = cell_array.length ();

	for (int i = 0; i < n; i++)
	  {
	    octave_value tc2;

	    std::string nm
	      = read_mat5_binary_element (is, filename, swap, global, tc2);

	    if (! is || error_state)
	      {
		error ("load: reading cell data for `%s'", nm.c_str ());
		goto data_read_error;
	      }

	    cell_array(i) = tc2;
	  }

	tc = cell_array;
      }
      break;

    case MAT_FILE_SPARSE_CLASS:
#if SIZEOF_INT != SIZEOF_OCTAVE_IDX_TYPE
      warning ("load: sparse objects are not implemented");
      goto skip_ahead;
#else
      {
	int nr = dims(0);
	int nc = dims(1);
	SparseMatrix sm;
	SparseComplexMatrix scm;
	int *ridx;
	int *cidx;
	double *data;

	// Setup return value
	if (imag)
	  {
	    scm = SparseComplexMatrix (static_cast<octave_idx_type> (nr),
				       static_cast<octave_idx_type> (nc),
				       static_cast<octave_idx_type> (nzmax));
	    ridx = scm.ridx ();
	    cidx = scm.cidx ();
	    data = 0;
	  }
	else
	  {
	    sm = SparseMatrix (static_cast<octave_idx_type> (nr),
			       static_cast<octave_idx_type> (nc),
			       static_cast<octave_idx_type> (nzmax));
	    ridx = sm.ridx ();
	    cidx = sm.cidx ();
	    data = sm.data ();
	  }

	// row indices
	std::streampos tmp_pos;
	  
	if (read_mat5_tag (is, swap, type, len))
	  {
	    error ("load: reading sparse row data for `%s'", retval.c_str ());
	    goto data_read_error;
	  }

	tmp_pos = is.tellg ();

	read_mat5_integer_data (is, ridx, nzmax, swap,
				static_cast<enum mat5_data_type> (type));

	if (! is || error_state)
	  {
	    error ("load: reading sparse row data for `%s'", retval.c_str ());
	    goto data_read_error;
	  }

	is.seekg (tmp_pos + static_cast<std::streamoff> (PAD (len)));

	// col indices
	if (read_mat5_tag (is, swap, type, len))
	  {
	    error ("load: reading sparse column data for `%s'", retval.c_str ());
	    goto data_read_error;
	  }

	tmp_pos = is.tellg ();

	read_mat5_integer_data (is, cidx, nc + 1, swap,
				static_cast<enum mat5_data_type> (type));

	if (! is || error_state)
	  {
	    error ("load: reading sparse column data for `%s'", retval.c_str ());
	    goto data_read_error;
	  }

	is.seekg (tmp_pos + static_cast<std::streamoff> (PAD (len)));

	// real data subelement
	if (read_mat5_tag (is, swap, type, len))
	  {
	    error ("load: reading sparse matrix data for `%s'", retval.c_str ());
	    goto data_read_error;
	  }

	int32_t nnz = cidx[nc];
	NDArray re;
	if (imag)
	  {
	    re = NDArray (dim_vector (static_cast<int> (nnz)));
	    data = re.fortran_vec ();
	  }

	tmp_pos = is.tellg ();
	read_mat5_binary_data (is, data, nnz, swap,
			       static_cast<enum mat5_data_type> (type), flt_fmt);

	if (! is || error_state)
	  {
	    error ("load: reading sparse matrix data for `%s'", retval.c_str ());
	    goto data_read_error;
	  }

	is.seekg (tmp_pos + static_cast<std::streamoff> (PAD (len)));

	// imaginary data subelement
	if (imag)
	  {
	    NDArray im (dim_vector (static_cast<int> (nnz)));
	  
	    if (read_mat5_tag (is, swap, type, len))
	      {
		error ("load: reading sparse matrix data for `%s'", retval.c_str ());
		goto data_read_error;
	      }

	    read_mat5_binary_data (is, im.fortran_vec (), nnz, swap,
				   static_cast<enum mat5_data_type> (type), flt_fmt);

	    if (! is || error_state)
	      {
		error ("load: reading imaginary sparse matrix data for `%s'",
		       retval.c_str ());
		goto data_read_error;
	      }

	    for (int i = 0; i < nnz; i++)
	      scm.xdata (i) = Complex (re (i), im (i));

	    tc = scm;
	  }
	else
	  tc = sm;
      }
#endif
      break;

    case MAT_FILE_FUNCTION_CLASS:
      {
	octave_value tc2;
	std::string nm
	  = read_mat5_binary_element (is, filename, swap, global, tc2);

	if (! is || error_state)
	  goto data_read_error;

	// Octave can handle both "/" and "\" as a directry seperator
	// and so can ignore the seperator field of m0. I think the
	// sentinel field is also save to ignore.
	Octave_map m0 = tc2.map_value();
	Octave_map m1 = m0.contents("function_handle")(0).map_value();
	std::string ftype = m1.contents("type")(0).string_value();
	std::string fname = m1.contents("function")(0).string_value();
	std::string fpath = m1.contents("file")(0).string_value();

	if (ftype == "simple" || ftype == "scopedfunction")
	  {
	    if (fpath.length() == 0)
	      // We have a builtin function
	      tc = make_fcn_handle (fname);
	    else
	      {
		std::string mroot = 
		  m0.contents("matlabroot")(0).string_value();

		if ((fpath.length () >= mroot.length ()) &&
		    fpath.substr(0, mroot.length()) == mroot &&
		    OCTAVE_EXEC_PREFIX != mroot)
		  {
		    // If fpath starts with matlabroot, and matlabroot
		    // doesn't equal octave_config_info ("exec_prefix")
		    // then the function points to a version of Octave
		    // or Matlab other than the running version. In that
		    // case we replace with the same function in the
		    // running version of Octave?
		    
		    // First check if just replacing matlabroot is enough
		    std::string str = OCTAVE_EXEC_PREFIX + 
		      fpath.substr (mroot.length ());		    
		    file_stat fs (str);

		    if (fs.exists ())
		      {
			symbol_record *sr = fbi_sym_tab->lookup (str, true);
		    
			if (sr)
			  {
			    load_fcn_from_file (sr, false);

			    tc = octave_value (new octave_fcn_handle 
					       (sr->def (), fname));

			    // The next two lines are needed to force the 
			    // definition of the function back to the one 
			    // that is on the user path.
			    sr = fbi_sym_tab->lookup (fname, true);

			    load_fcn_from_file (sr, false);
			  }
		      }
		    else
		      {
			// Next just search for it anywhere in the
			// system path
			string_vector names(3);
			names(0) = fname + ".oct";
			names(1) = fname + ".mex";
			names(2) = fname + ".m";

			dir_path p (load_path::system_path ());

			str = octave_env::make_absolute 
			  (p.find_first_of (names), octave_env::getcwd ());

			symbol_record *sr = fbi_sym_tab->lookup (str, true);

			if (sr)
			  {
			    load_fcn_from_file (sr, false);

			    tc = octave_value (new octave_fcn_handle 
					       (sr->def (), fname));

			    // The next two lines are needed to force the 
			    // definition of the function back to the one 
			    // that is on the user path.
			    sr = fbi_sym_tab->lookup (fname, true);

			    load_fcn_from_file (sr, false);
			  }
			else
			  {
			    warning ("load: can't find the file %s", 
				     fpath.c_str());
			    goto skip_ahead;
			  }
		      }
		  }
		else
		  {
		    symbol_record *sr = fbi_sym_tab->lookup (fpath, true);

		    if (sr)
		      {
			load_fcn_from_file (sr, false);

			tc = octave_value (new octave_fcn_handle (sr->def (), 
								  fname));

			sr = fbi_sym_tab->lookup (fname, true);

			load_fcn_from_file (sr, false);
		      }
		    else
		      {
			warning ("load: can't find the file %s", 
				 fpath.c_str());
			goto skip_ahead;
		      }
		  }
	      }
	  }
	else if (ftype == "nested")
	  {
	    warning ("load: can't load nested function");
	    goto skip_ahead;
	  }
	else if (ftype == "anonymous")
	  {
	    Octave_map m2 = m1.contents("workspace")(0).map_value();
	    uint32NDArray MCOS = m2.contents("MCOS")(0).uint32_array_value();
	    octave_idx_type off = static_cast<octave_idx_type>(MCOS(4).double_value ());
	    m2 = subsys_ov.map_value();
	    m2 = m2.contents("MCOS")(0).map_value();
	    tc2 = m2.contents("MCOS")(0).cell_value()(1 + off).cell_value()(1);
	    m2 = tc2.map_value();
	    symbol_table *local_sym_tab = 0;
	    if (m2.nfields() > 0)
	      {
		octave_value tmp;

		local_sym_tab = new symbol_table (((m2.nfields() + 1) & ~1), 
						  "LOCAL");
	      
		for (Octave_map::iterator p0 = m2.begin() ; 
		     p0 != m2.end(); p0++)
		  {
		    std::string key = m2.key(p0);
		    octave_value val = m2.contents(p0)(0);

		    symbol_record *sr = local_sym_tab->lookup (key, true);

		    if (sr)
		      sr->define (val);
		    else
		      {
			error ("load: failed to load anonymous function handle");
			goto skip_ahead;
		      }
                  }
	      }
	    
	    unwind_protect::begin_frame ("anon_mat5_load");
	    unwind_protect_ptr (curr_sym_tab);

	    if (local_sym_tab)
	      curr_sym_tab = local_sym_tab;

	    int parse_status;
	    octave_value anon_fcn_handle = 
	      eval_string (fname.substr (4), true, parse_status);

	    if (parse_status == 0)
	      {
		octave_fcn_handle *fh = 
		  anon_fcn_handle.fcn_handle_value ();
		if (fh)
		  tc = new octave_fcn_handle (fh->fcn_val(), "@<anonymous>");
		else
		  {
		    error ("load: failed to load anonymous function handle");
		    goto skip_ahead;
		  }
	      }
	    else
	      {
		error ("load: failed to load anonymous function handle");
		goto skip_ahead;
	      }

	    unwind_protect::run_frame ("anon_mat5_load");

	    if (local_sym_tab)
	      delete local_sym_tab;	    
	  }
	else
	  {
	    error ("load: invalid function handle type");
	    goto skip_ahead;
	  }
      }
      break;

    case MAT_FILE_WORKSPACE_CLASS:
      {
	Octave_map m (dim_vector (1, 1));
	int n_fields = 2;
	string_vector field (n_fields);

	for (int i = 0; i < n_fields; i++)
	  {
	    int32_t fn_type;
	    int32_t fn_len;
	    if (read_mat5_tag (is, swap, fn_type, fn_len) || fn_type != miINT8)
	      {
		error ("load: invalid field name subelement");
		goto data_read_error;
	      }

	    OCTAVE_LOCAL_BUFFER (char, elname, fn_len + 1);

	    std::streampos tmp_pos = is.tellg ();

	    if (fn_len)
	      {
		if (! is.read (elname, fn_len))
		  goto data_read_error;

		is.seekg (tmp_pos + 
			  static_cast<std::streamoff> (PAD (fn_len)));
	      }

	    elname[fn_len] = '\0';

	    field(i) = elname;
	  }

	std::vector<Cell> elt (n_fields);

	for (octave_idx_type i = 0; i < n_fields; i++)
	  elt[i] = Cell (dims);

	octave_idx_type n = dims.numel ();

	// fields subelements
	for (octave_idx_type j = 0; j < n; j++)
	  {
	    for (octave_idx_type i = 0; i < n_fields; i++)
	      {
		if (field(i) == "MCOS")
		  {
		    octave_value fieldtc;
		    read_mat5_binary_element (is, filename, swap, global,
					      fieldtc); 
		    if (! is || error_state)
		      goto data_read_error;

		    elt[i](j) = fieldtc;
		  }
		else
		  elt[i](j) = octave_value ();
	      }
	  }

	for (octave_idx_type i = 0; i < n_fields; i++)
	  m.assign (field (i), elt[i]);
	tc = m;
      }
      break;

    case MAT_FILE_OBJECT_CLASS:
      {
	isclass = true;

	if (read_mat5_tag (is, swap, type, len) || type != miINT8)
	  {
	    error ("load: invalid class name");
	    goto skip_ahead;
	  }

	{
	  OCTAVE_LOCAL_BUFFER (char, name, len+1);

	  std::streampos tmp_pos = is.tellg ();

	  if (len)
	    {
	      if (! is.read (name, len ))
		goto data_read_error;
	
	      is.seekg (tmp_pos + static_cast<std::streamoff> (PAD (len)));
	    }

	  name[len] = '\0';
	  classname = name;
	}
      }
      // Fall-through
    case MAT_FILE_STRUCT_CLASS:
      {
	Octave_map m (dim_vector (1, 1));
	int32_t fn_type;
	int32_t fn_len;
	int32_t field_name_length;

	// field name length subelement -- actually the maximum length
	// of a field name.  The Matlab docs promise this will always
	// be 32.  We read and use the actual value, on the theory
	// that eventually someone will recognize that's a waste of
	// space.
	if (read_mat5_tag (is, swap, fn_type, fn_len) || fn_type != miINT32)
	  {
	    error ("load: invalid field name length subelement");
	    goto data_read_error;
	  }

	if (! is.read (reinterpret_cast<char *> (&field_name_length), fn_len ))
	  goto data_read_error;

	if (swap)
	  swap_bytes<4> (&field_name_length);

	// field name subelement.  The length of this subelement tells
	// us how many fields there are.
	if (read_mat5_tag (is, swap, fn_type, fn_len) || fn_type != miINT8)
	  {
	    error ("load: invalid field name subelement");
	    goto data_read_error;
	  }

	octave_idx_type n_fields = fn_len/field_name_length;

	if (n_fields > 0)
	  {
	    fn_len = PAD (fn_len);

	    OCTAVE_LOCAL_BUFFER (char, elname, fn_len);

	    if (! is.read (elname, fn_len))
	      goto data_read_error;

	    std::vector<Cell> elt (n_fields);

	    for (octave_idx_type i = 0; i < n_fields; i++)
	      elt[i] = Cell (dims);

	    octave_idx_type n = dims.numel ();

	    // fields subelements
	    for (octave_idx_type j = 0; j < n; j++)
	      {
		for (octave_idx_type i = 0; i < n_fields; i++)
		  {
		    octave_value fieldtc;
		    read_mat5_binary_element (is, filename, swap, global,
					      fieldtc); 
		    elt[i](j) = fieldtc;
		  }
	      }

	    for (octave_idx_type i = 0; i < n_fields; i++)
	      {
		const char *key = elname + i*field_name_length;

		m.assign (key, elt[i]);
	      }
	  }

	if (isclass)
	  {
	    if (classname == "inline")
	      {
		// inline is not an object in Octave but rather an
		// overload of a function handle. Special case.
		tc =  
		  new octave_fcn_inline (m.contents("expr")(0).string_value(),
					 m.contents("args")(0).string_value());
	      }
	    else
	      {
		warning ("load: objects are not implemented");
		goto skip_ahead;
	      }
	  }
	else
	  tc = m;
      }
      break;

    case MAT_FILE_INT8_CLASS:
      OCTAVE_MAT5_INTEGER_READ (int8NDArray);
      break;

    case MAT_FILE_UINT8_CLASS:
      {
	OCTAVE_MAT5_INTEGER_READ (uint8NDArray);

	// Logical variables can either be MAT_FILE_UINT8_CLASS or
	// MAT_FILE_DOUBLE_CLASS, so check if we have a logical
	// variable and convert it.

	if (logicalvar)
	  {
	    uint8NDArray in = tc.uint8_array_value ();
	    int nel = in.nelem ();
	    boolNDArray out (dims);
	    
	    for (int i = 0; i < nel; i++)
	      out (i) = in(i).bool_value ();

	    tc = out;
	  }
      }
      break;

    case MAT_FILE_INT16_CLASS:
      OCTAVE_MAT5_INTEGER_READ (int16NDArray);
      break;

    case MAT_FILE_UINT16_CLASS:
      OCTAVE_MAT5_INTEGER_READ (uint16NDArray);
      break;

    case MAT_FILE_INT32_CLASS:
      OCTAVE_MAT5_INTEGER_READ (int32NDArray);
      break;

    case MAT_FILE_UINT32_CLASS:
      OCTAVE_MAT5_INTEGER_READ (uint32NDArray);
      break;

    case MAT_FILE_INT64_CLASS:
      OCTAVE_MAT5_INTEGER_READ (int64NDArray);
      break;

    case MAT_FILE_UINT64_CLASS:
      OCTAVE_MAT5_INTEGER_READ (uint64NDArray);
      break;

    case MAT_FILE_CHAR_CLASS:
      // handle as a numerical array to start with

    case MAT_FILE_DOUBLE_CLASS:
    case MAT_FILE_SINGLE_CLASS:
    default:
      {
	NDArray re (dims);
      
	// real data subelement

	std::streampos tmp_pos;
	  
	if (read_mat5_tag (is, swap, type, len))
	  {
	    error ("load: reading matrix data for `%s'", retval.c_str ());
	    goto data_read_error;
	  }

	int n = re.length ();
	tmp_pos = is.tellg ();
	read_mat5_binary_data (is, re.fortran_vec (), n, swap,
			       static_cast<enum mat5_data_type> (type), flt_fmt);

	if (! is || error_state)
	  {
	    error ("load: reading matrix data for `%s'", retval.c_str ());
	    goto data_read_error;
	  }

	is.seekg (tmp_pos + static_cast<std::streamoff> (PAD (len)));

	if (logicalvar)
	  {
	    // Logical variables can either be MAT_FILE_UINT8_CLASS or
	    // MAT_FILE_DOUBLE_CLASS, so check if we have a logical
	    // variable and convert it.

	    boolNDArray out (dims);
	    
	    for (int i = 0; i < n; i++)
	      out (i) = static_cast<bool> (re (i));

	    tc = out;
	  }
	else if (imag)
	  {
	    // imaginary data subelement

	    NDArray im (dims);
	  
	    if (read_mat5_tag (is, swap, type, len))
	      {
		error ("load: reading matrix data for `%s'", retval.c_str ());
		goto data_read_error;
	      }

	    n = im.length ();
	    read_mat5_binary_data (is, im.fortran_vec (), n, swap,
				   static_cast<enum mat5_data_type> (type), flt_fmt);

	    if (! is || error_state)
	      {
		error ("load: reading imaginary matrix data for `%s'",
		       retval.c_str ());
		goto data_read_error;
	      }

	    ComplexNDArray ctmp (dims);

	    for (int i = 0; i < n; i++)
	      ctmp(i) = Complex (re(i), im(i));

	    tc = ctmp;
	  }
	else
	  {
	    if (arrayclass == MAT_FILE_CHAR_CLASS)
	      {
		if (type == miUTF16 || type == miUTF32)
		  {
		    bool found_big_char = false;
		    for (int i = 0; i < n; i++)
		      {
			if (re(i) > 127) {
			  re(i) = '?';
			  found_big_char = true;
			}
		      }

		    if (found_big_char)
		      {
			warning ("load: can not read non-ASCII portions of UTF characters.");
			warning ("      Replacing unreadable characters with '?'.");
		      }
		  }
		else if (type == miUTF8)
		  {
		    // Search for multi-byte encoded UTF8 characters and
		    // replace with 0x3F for '?'... Give the user a warning

		    bool utf8_multi_byte = false;
		    for (int i = 0; i < n; i++)
		      {
			unsigned char a = static_cast<unsigned char> (re(i));
			if (a > 0x7f)
			  utf8_multi_byte = true;
		      }
		    
		    if (utf8_multi_byte)
		      {
			warning ("load: can not read multi-byte encoded UTF8 characters.");
			warning ("      Replacing unreadable characters with '?'.");
			for (int i = 0; i < n; i++)
			  {
			    unsigned char a = static_cast<unsigned char> (re(i));
			    if (a > 0x7f)
			      re(i) = '?';
			  }
		      }
		  }
		tc = re;
		tc = tc.convert_to_str (false, true, '\'');
	      }
	    else
	      tc = re;
	  }
      }
    }

  is.seekg (pos + static_cast<std::streamoff> (element_length));

  if (is.eof ())
    is.clear ();

  return retval;

 data_read_error:
 early_read_error:
  error ("load: trouble reading binary file `%s'", filename.c_str ());
  return std::string ();

 skip_ahead:
  warning ("skipping over `%s'", retval.c_str ());
  is.seekg (pos + static_cast<std::streamoff> (element_length));
  return read_mat5_binary_element (is, filename, swap, global, tc);
}

int
read_mat5_binary_file_header (std::istream& is, bool& swap, bool quiet, 
			      const std::string& filename)
{
  int16_t version=0, magic=0;
  uint64_t subsys_offset;

  is.seekg (116, std::ios::beg);
  is.read (reinterpret_cast<char *> (&subsys_offset), 8);

  is.seekg (124, std::ios::beg);
  is.read (reinterpret_cast<char *> (&version), 2);
  is.read (reinterpret_cast<char *> (&magic), 2);

  if (magic == 0x4d49)
    swap = 0;
  else if (magic == 0x494d)
    swap = 1;
  else
    {
      if (! quiet)
	error ("load: can't read binary file");
      return -1;
    }

  if (! swap)			// version number is inverse swapped!
    version = ((version >> 8) & 0xff) + ((version & 0xff) << 8);

  if (version != 1 && !quiet)
    warning ("load: found version %d binary MAT file, "
	     "but only prepared for version 1", version);

  if (swap)
    swap_bytes<8> (&subsys_offset, 1);

  if (subsys_offset != 0x2020202020202020ULL && subsys_offset != 0ULL)
    {
      // Read the subsystem data block
      is.seekg (subsys_offset, std::ios::beg);

      octave_value tc;
      bool global;
      read_mat5_binary_element (is, filename, swap, global, tc);

      if (!is || error_state)
	return -1;

      if (tc.is_uint8_type ())
	{
	  const uint8NDArray itmp = tc.uint8_array_value();
	  octave_idx_type ilen = itmp.nelem ();

	  // Why should I have to initialize outbuf as just overwrite
	  std::string outbuf (ilen - 7, ' ');

	  // FIXME -- find a way to avoid casting away const here
	  char *ctmp = const_cast<char *> (outbuf.c_str ());
	  for (octave_idx_type j = 8; j < ilen; j++)
	    ctmp[j-8] = itmp(j).char_value ();

	  std::istringstream fh_ws (outbuf);

	  read_mat5_binary_element (fh_ws, filename, swap, global, subsys_ov);

	  if (error_state)
	    return -1;
	}
      else
	return -1;

      // Reposition to just after the header
      is.seekg (128, std::ios::beg);
    }

  return 0;
}

static int 
write_mat5_tag (std::ostream& is, int type, int bytes)
{
  int32_t temp;

  if (bytes > 0 && bytes <= 4)
    temp = (bytes << 16) + type;
  else
    {
      temp = type;
      if (! is.write (reinterpret_cast<char *> (&temp), 4))
	goto data_write_error;
      temp = bytes;
    }

  if (! is.write (reinterpret_cast<char *> (&temp), 4))
    goto data_write_error;

  return 0;

 data_write_error:
  return 1;
}

// write out the numeric values in M to OS,
// preceded by the appropriate tag.
static void 
write_mat5_array (std::ostream& os, const NDArray& m, bool save_as_floats)
{
  int nel = m.nelem ();
  double max_val, min_val;
  save_type st = LS_DOUBLE;
  mat5_data_type mst;
  int size;
  unsigned len;
  const double *data = m.data ();

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

#define MAT5_DO_WRITE(TYPE, data, count, stream) \
  do \
    { \
      OCTAVE_LOCAL_BUFFER (TYPE, ptr, count); \
      for (int i = 0; i < count; i++) \
        ptr[i] = static_cast<TYPE> (data[i]); \
      stream.write (reinterpret_cast<char *> (ptr), count * sizeof (TYPE)); \
    } \
  while (0)

  if (save_as_floats)
    {
      if (m.too_large_for_float ())
	{
	  warning ("save: some values too large to save as floats --");
	  warning ("save: saving as doubles instead");
	}
      else
	st = LS_FLOAT;
    }

  if (m.all_integers (max_val, min_val))
    st = get_save_type (max_val, min_val);

  switch (st)
    {
    default:
    case LS_DOUBLE:  mst = miDOUBLE; size = 8; break;
    case LS_FLOAT:   mst = miSINGLE; size = 4; break;
    case LS_U_CHAR:  mst = miUINT8;  size = 1; break;
    case LS_U_SHORT: mst = miUINT16; size = 2; break;
    case LS_U_INT:   mst = miUINT32; size = 4; break;
    case LS_CHAR:    mst = miINT8;   size = 1; break;
    case LS_SHORT:   mst = miINT16;  size = 2; break;
    case LS_INT:     mst = miINT32;  size = 4; break;
    }

  len = nel*size;
  write_mat5_tag (os, mst, len);

  {
    switch (st)
      {
      case LS_U_CHAR:
	MAT5_DO_WRITE (uint8_t, data, nel, os);
	break;
	
      case LS_U_SHORT:
	MAT5_DO_WRITE (uint16_t, data, nel, os);
	break;
	
      case LS_U_INT:
	MAT5_DO_WRITE (uint32_t, data, nel, os);
	break;
	
      case LS_U_LONG:
	MAT5_DO_WRITE (uint64_t, data, nel, os);
	break;

      case LS_CHAR:
	MAT5_DO_WRITE (int8_t, data, nel, os);
	break;
	
      case LS_SHORT:
	MAT5_DO_WRITE (int16_t, data, nel, os);
	break;

      case LS_INT:
	MAT5_DO_WRITE (int32_t, data, nel, os);
	break;

      case LS_LONG:
	MAT5_DO_WRITE (int64_t, data, nel, os);
	break;

      case LS_FLOAT:
	MAT5_DO_WRITE (float, data, nel, os);
	break;

      case LS_DOUBLE: // No conversion necessary.
	os.write (reinterpret_cast<const char *> (data), len);
	break;

      default:
	(*current_liboctave_error_handler)
	  ("unrecognized data format requested");
	break;
      }
  }
  if (PAD (len) > len)
    {
      static char buf[9]="\x00\x00\x00\x00\x00\x00\x00\x00";
      os.write (buf, PAD (len) - len);
    }
}

template <class T>
void 
write_mat5_integer_data (std::ostream& os, const T *m, int size, int nel)
{
  mat5_data_type mst;
  unsigned len;

  switch (size)
    {
    case 1:
      mst = miUINT8;
      break;
    case 2:
      mst = miUINT16;
      break;
    case 4:
      mst = miUINT32;
      break;
    case 8:
      mst = miUINT64;
      break;
    case -1:
      mst = miINT8;
      size = - size;
      break;
    case -2:
      mst = miINT16;
      size = - size;
      break;
    case -4:
      mst = miINT32;
      size = - size;
      break;
    case -8:
    default:
      mst = miINT64;
      size = - size;
      break;
    }

  len = nel*size;
  write_mat5_tag (os, mst, len);

  os.write (reinterpret_cast<const char *> (m), len);

  if (PAD (len) > len)
    {
      static char buf[9]="\x00\x00\x00\x00\x00\x00\x00\x00";
      os.write (buf, PAD (len) - len);
    }
}

template void write_mat5_integer_data (std::ostream& os, const octave_int8 *m,
				       int size, int nel);
template void write_mat5_integer_data (std::ostream& os, const octave_int16 *m,
				       int size, int nel);
template void write_mat5_integer_data (std::ostream& os, const octave_int32 *m,
				       int size, int nel);
template void write_mat5_integer_data (std::ostream& os, const octave_int64 *m,
				       int size, int nel);
template void write_mat5_integer_data (std::ostream& os, const octave_uint8 *m,
				       int size, int nel);
template void write_mat5_integer_data (std::ostream& os, const octave_uint16 *m,
				       int size, int nel);
template void write_mat5_integer_data (std::ostream& os, const octave_uint32 *m,
				       int size, int nel);
template void write_mat5_integer_data (std::ostream& os, const octave_uint64 *m,
				       int size, int nel);
template void write_mat5_integer_data (std::ostream& os, const int *m, 
				       int size, int nel);

// Write out cell element values in the cell array to OS, preceded by
// the appropriate tag.

static bool 
write_mat5_cell_array (std::ostream& os, const Cell& cell,
		       bool mark_as_global, bool save_as_floats)
{
  int nel = cell.nelem ();

  for (int i = 0; i < nel; i++)
    {
      octave_value ov = cell(i);

      if (! save_mat5_binary_element (os, ov, "", mark_as_global,
				      false, save_as_floats))
	return false;
    }

  return true;
}

int
save_mat5_array_length (const double* val, int nel, bool save_as_floats)
{
  if (nel > 0)
    {
      int size = 8;

      if (save_as_floats)
	{
	  bool too_large_for_float = false;
	  for (int i = 0; i < nel; i++)
	    {
	      double tmp = val [i];

	      if (! (xisnan (tmp) || xisinf (tmp))
		  && fabs (tmp) > FLT_MAX)
		{
		  too_large_for_float = true;
		  break;
		}
	    }

	  if (!too_large_for_float)
	    size = 4;
	}

      // The code below is disabled since get_save_type currently doesn't
      // deal with integer types. This will need to be activated if get_save_type
      // is changed.

      // double max_val = val[0];
      // double min_val = val[0];
      // bool all_integers =  true;
      //
      // for (int i = 0; i < nel; i++)
      //   {
      //     double val = val[i];
      //
      //     if (val > max_val)
      //       max_val = val;
      //
      //     if (val < min_val)
      //       min_val = val;
      //
      //     if (D_NINT (val) != val)
      //       {
      //         all_integers = false;
      //         break;
      //       }
      //   }
      //
      // if (all_integers)
      //   {
      //     if (max_val < 256 && min_val > -1)
      //       size = 1;
      //     else if (max_val < 65536 && min_val > -1)
      //       size = 2;
      //     else if (max_val < 4294967295UL && min_val > -1)
      //       size = 4;
      //     else if (max_val < 128 && min_val >= -128)
      //       size = 1;
      //     else if (max_val < 32768 && min_val >= -32768)
      //       size = 2;
      //     else if (max_val <= 2147483647L && min_val >= -2147483647L)
      //       size = 4;
      //   }

      return 8 + nel * size;
    }
  else
    return 8;
}

int
save_mat5_array_length (const Complex* val, int nel, bool save_as_floats)
{
  int ret;

  OCTAVE_LOCAL_BUFFER (double, tmp, nel);

  for (int i = 1; i < nel; i++)
    tmp[i] = std::real (val[i]);

  ret = save_mat5_array_length (tmp, nel, save_as_floats);

  for (int i = 1; i < nel; i++)
    tmp[i] = std::imag (val[i]);

  ret += save_mat5_array_length (tmp, nel, save_as_floats);

  return ret;
}

int
save_mat5_element_length (const octave_value& tc, const std::string& name, 
			  bool save_as_floats, bool mat7_format)
{
  int max_namelen = (mat7_format ? 63 : 31);
  int len = name.length ();
  std::string cname = tc.class_name ();
  int ret = 32;

  if (len > 4)
    ret += PAD (len > max_namelen ? max_namelen : len);

  ret += PAD (4 * tc.ndims ());
  
  if (tc.is_string ())
    {
      charNDArray chm = tc.char_array_value ();
      ret += 8;
      if (chm.nelem () > 2)
	ret += PAD (2 * chm.nelem ());
    }
  else if (tc.is_sparse_type ())
    {
      if (tc.is_complex_type ())
	{
	  SparseComplexMatrix m = tc.sparse_complex_matrix_value ();
	  int nc = m.cols ();
	  int nnz = m.nzmax ();

	  ret += 16 + PAD (nnz * sizeof (int)) + PAD ((nc + 1) * sizeof (int)) +
	    save_mat5_array_length (m.data (), m.nelem (), save_as_floats);
	}
      else
	{
	  SparseMatrix m = tc.sparse_matrix_value ();
	  int nc = m.cols ();
	  int nnz = m.nzmax ();

	  ret += 16 + PAD (nnz * sizeof (int)) + PAD ((nc + 1) * sizeof (int)) +
	    save_mat5_array_length (m.data (), m.nelem (), save_as_floats);
	}
    }

#define INT_LEN(nel, size) \
  { \
    ret += 8; \
    int sz = nel * size; \
    if (sz > 4) \
      ret += PAD (sz);	\
  }

  else if (cname == "int8")
    INT_LEN (tc.int8_array_value ().nelem (), 1)
  else if (cname == "int16")
    INT_LEN (tc.int16_array_value ().nelem (), 2)
  else if (cname == "int32")
    INT_LEN (tc.int32_array_value ().nelem (), 4)
  else if (cname == "int64")
    INT_LEN (tc.int64_array_value ().nelem (), 8)
  else if (cname == "uint8")
    INT_LEN (tc.uint8_array_value ().nelem (), 1)
  else if (cname == "uint16")
    INT_LEN (tc.uint16_array_value ().nelem (), 2)
  else if (cname == "uint32")
    INT_LEN (tc.uint32_array_value ().nelem (), 4)
  else if (cname == "uint64")
    INT_LEN (tc.uint64_array_value ().nelem (), 8)
  else if (tc.is_bool_type ())
    INT_LEN (tc.bool_array_value ().nelem (), 1)
  else if (tc.is_real_scalar () || tc.is_real_matrix () || tc.is_range ())
    {
      NDArray m = tc.array_value ();
      ret += save_mat5_array_length (m.fortran_vec (), m.nelem (),
				     save_as_floats);
    }
  else if (tc.is_cell ())
    {
      Cell cell = tc.cell_value ();
      int nel = cell.nelem ();

      for (int i = 0; i < nel; i++)
	ret += 8 + 
	  save_mat5_element_length (cell (i), "", save_as_floats, mat7_format);
    }
  else if (tc.is_complex_scalar () || tc.is_complex_matrix ()) 
    {
      ComplexNDArray m = tc.complex_array_value ();
      ret += save_mat5_array_length (m.fortran_vec (), m.nelem (),
				     save_as_floats);
    }
  else if (tc.is_map () || tc.is_inline_function ()) 
    {
      int fieldcnt = 0;
      const Octave_map m = tc.map_value ();
      int nel = m.numel ();

      if (tc.is_inline_function ())
	// length of "inline" is 6
	ret += 8 + PAD (6 > max_namelen ? max_namelen : 6);

      for (Octave_map::const_iterator i = m.begin (); i != m.end (); i++)
	fieldcnt++;

      ret += 16 + fieldcnt * (max_namelen + 1);


      for (int j = 0; j < nel; j++)
	{

	  for (Octave_map::const_iterator i = m.begin (); i != m.end (); i++)
	    {
	      Cell elts = m.contents (i);

	      ret += 8 + save_mat5_element_length (elts (j), "", 
					       save_as_floats, mat7_format);
	    }
	}
    }
  else
    ret = -1;

  return ret;
}

// save the data from TC along with the corresponding NAME on stream
// OS in the MatLab version 5 binary format.  Return true on success.

bool
save_mat5_binary_element (std::ostream& os,
			  const octave_value& tc, const std::string& name,
			  bool mark_as_global, bool mat7_format,
			  bool save_as_floats, bool compressing) 
{
  int32_t flags=0;
  int32_t nnz=0;
  std::streampos fixup, contin;
  std::string cname = tc.class_name ();
  int max_namelen = (mat7_format ? 63 : 31);

#ifdef HAVE_ZLIB
  if (mat7_format && !compressing)
    {
      bool ret = false;

      std::ostringstream buf;

      // The code seeks backwards in the stream to fix the header. Can't
      // do this with zlib, so use a stringstream.
      ret = save_mat5_binary_element (buf, tc, name, mark_as_global, true,
				      save_as_floats, true);

      if (ret)
	{
	  // destLen must be at least 0.1% larger than source buffer 
	  // + 12 bytes. Reality is it must be larger again than that.
	  std::string buf_str = buf.str ();
	  uLongf srcLen = buf_str.length ();
	  uLongf destLen = srcLen * 101 / 100 + 12; 
	  OCTAVE_LOCAL_BUFFER (char, out_buf, destLen);

	  if (compress (reinterpret_cast<Bytef *> (out_buf), &destLen, 
			reinterpret_cast<const Bytef *> (buf_str.c_str ()), srcLen) == Z_OK)
	    {
	      write_mat5_tag (os, miCOMPRESSED, static_cast<int> (destLen)); 
	      os.write (out_buf, destLen);
	    }
	  else
	    {
	      error ("save: error compressing data element");
	      ret = false;
	    }
	}

      return ret;
    }
#endif

  // element type and length
  fixup = os.tellp ();
  write_mat5_tag (os, miMATRIX, save_mat5_element_length 
		  (tc, name, save_as_floats, mat7_format));
  
  // array flags subelement
  write_mat5_tag (os, miUINT32, 8);

  if (tc.is_bool_type ())
    flags |= 0x0200;

  if (mark_as_global)
    flags |= 0x0400;

  if (tc.is_complex_scalar () || tc.is_complex_matrix ())
    flags |= 0x0800;

  if (tc.is_string ())
    flags |= MAT_FILE_CHAR_CLASS;
  else if (cname == "int8")
    flags |= MAT_FILE_INT8_CLASS;
  else if (cname == "int16")
    flags |= MAT_FILE_INT16_CLASS;
  else if (cname == "int32")
    flags |= MAT_FILE_INT32_CLASS;
  else if (cname == "int64")
    flags |= MAT_FILE_INT64_CLASS;
  else if (cname == "uint8" || tc.is_bool_type ())
    flags |= MAT_FILE_UINT8_CLASS;
  else if (cname == "uint16")
    flags |= MAT_FILE_UINT16_CLASS;
  else if (cname == "uint32")
    flags |= MAT_FILE_UINT32_CLASS;
  else if (cname == "uint64")
    flags |= MAT_FILE_UINT64_CLASS;
  else if (tc.is_sparse_type ())
    {
      flags |= MAT_FILE_SPARSE_CLASS;
      if (tc.is_complex_type ())
	{
	  SparseComplexMatrix scm = tc.sparse_complex_matrix_value ();
	  nnz = scm.nzmax ();
	}
      else
	{
	  SparseMatrix sm = tc.sparse_matrix_value ();
	  nnz = sm.nzmax ();
	}
    }
  else if (tc.is_real_scalar ())
    flags |= MAT_FILE_DOUBLE_CLASS;
  else if (tc.is_real_matrix () || tc.is_range ())
    flags |= MAT_FILE_DOUBLE_CLASS;
  else if (tc.is_complex_scalar ())
    flags |= MAT_FILE_DOUBLE_CLASS;
  else if (tc.is_complex_matrix ())
    flags |= MAT_FILE_DOUBLE_CLASS;
  else if (tc.is_map ()) 
    flags |= MAT_FILE_STRUCT_CLASS;
  else if (tc.is_cell ())
    flags |= MAT_FILE_CELL_CLASS;
  else if (tc.is_inline_function ())
    flags |= MAT_FILE_OBJECT_CLASS;
  else
    {
      gripe_wrong_type_arg ("save", tc, false);
      goto error_cleanup;
    }

  os.write (reinterpret_cast<char *> (&flags), 4);
  os.write (reinterpret_cast<char *> (&nnz), 4);

  {
    dim_vector dv = tc.dims ();
    int nd = tc.ndims ();
    int dim_len = 4*nd;

    write_mat5_tag (os, miINT32, dim_len);

    for (int i = 0; i < nd; i++)
      {
	int32_t n = dv(i);
	os.write (reinterpret_cast<char *> (&n), 4);
      }

    if (PAD (dim_len) > dim_len)
      {
	static char buf[9]="\x00\x00\x00\x00\x00\x00\x00\x00";
	os.write (buf, PAD (dim_len) - dim_len);
      }
  }

  // array name subelement
  {
    int namelen = name.length ();

    if (namelen > max_namelen)
      namelen = max_namelen; // only 31 or 63 char names permitted in mat file

    int paddedlength = PAD (namelen);

    write_mat5_tag (os, miINT8, namelen);
    OCTAVE_LOCAL_BUFFER (char, paddedname, paddedlength);
    memset (paddedname, 0, paddedlength);
    strncpy (paddedname, name.c_str (), namelen);
    os.write (paddedname, paddedlength);
  }

  // data element
  if (tc.is_string ())
    {
      charNDArray chm = tc.char_array_value ();
      int nel = chm.nelem ();
      int len = nel*2;
      int paddedlength = PAD (len);

      OCTAVE_LOCAL_BUFFER (int16_t, buf, nel+3);
      write_mat5_tag (os, miUINT16, len);

      const char *s = chm.data ();

      for (int i = 0; i < nel; i++)
	buf[i] = *s++ & 0x00FF;

      os.write (reinterpret_cast<char *> (buf), len);
      
      if (paddedlength > len)
	{
	  static char padbuf[9]="\x00\x00\x00\x00\x00\x00\x00\x00";
	  os.write (padbuf, paddedlength - len);
	}
    }
  else if (tc.is_sparse_type ())
    {
      if (tc.is_complex_type ())
	{
	  SparseComplexMatrix m = tc.sparse_complex_matrix_value ();
	  int nc = m.cols ();

	  int tmp = sizeof (int);

	  write_mat5_integer_data (os, m.ridx (), -tmp, nnz);
	  write_mat5_integer_data (os, m.cidx (), -tmp, nc + 1);

	  NDArray buf (dim_vector (nnz, 1));

	  for (int i = 0; i < nnz; i++)
	    buf (i) = std::real (m.data (i));

	  write_mat5_array (os, buf, save_as_floats);

	  for (int i = 0; i < nnz; i++)
	    buf (i) = std::imag (m.data (i));

	  write_mat5_array (os, buf, save_as_floats);
	}
      else
	{
	  SparseMatrix m = tc.sparse_matrix_value ();
	  int nc = m.cols ();

	  int tmp = sizeof (int);

	  write_mat5_integer_data (os, m.ridx (), -tmp, nnz);
	  write_mat5_integer_data (os, m.cidx (), -tmp, nc + 1);

	  // FIXME
	  // Is there a way to easily do without this buffer
	  NDArray buf (dim_vector (nnz, 1));

	  for (int i = 0; i < nnz; i++)
	    buf (i) = m.data (i);

	  write_mat5_array (os, buf, save_as_floats);
	}
    }
  else if (cname == "int8")
    {
      int8NDArray m = tc.int8_array_value ();

      write_mat5_integer_data (os, m.fortran_vec (), -1, m.nelem ());
    }
  else if (cname == "int16")
    {
      int16NDArray m = tc.int16_array_value ();

      write_mat5_integer_data (os, m.fortran_vec (), -2, m.nelem ());
    }
  else if (cname == "int32")
    {
      int32NDArray m = tc.int32_array_value ();

      write_mat5_integer_data (os, m.fortran_vec (), -4, m.nelem ());
    }
  else if (cname == "int64")
    {
      int64NDArray m = tc.int64_array_value ();

      write_mat5_integer_data (os, m.fortran_vec (), -8, m.nelem ());
    }
  else if (cname == "uint8")
    {
      uint8NDArray m = tc.uint8_array_value ();

      write_mat5_integer_data (os, m.fortran_vec (), 1, m.nelem ());
    }
  else if (cname == "uint16")
    {
      uint16NDArray m = tc.uint16_array_value ();

      write_mat5_integer_data (os, m.fortran_vec (), 2, m.nelem ());
    }
  else if (cname == "uint32")
    {
      uint32NDArray m = tc.uint32_array_value ();

      write_mat5_integer_data (os, m.fortran_vec (), 4, m.nelem ());
    }
  else if (cname == "uint64")
    {
      uint64NDArray m = tc.uint64_array_value ();

      write_mat5_integer_data (os, m.fortran_vec (), 8, m.nelem ());
    }
  else if (tc.is_bool_type ())
    {
      uint8NDArray m (tc.bool_array_value ());

      write_mat5_integer_data (os, m.fortran_vec (), 1, m.nelem ());
    }
  else if (tc.is_real_scalar () || tc.is_real_matrix () || tc.is_range ())
    {
      NDArray m = tc.array_value ();

      write_mat5_array (os, m, save_as_floats);
    }
  else if (tc.is_cell ())
    {
      Cell cell = tc.cell_value ();

      if (! write_mat5_cell_array (os, cell, mark_as_global, save_as_floats))
	goto error_cleanup;
    }
  else if (tc.is_complex_scalar () || tc.is_complex_matrix ()) 
    {
      ComplexNDArray m_cmplx = tc.complex_array_value ();

      write_mat5_array (os, ::real (m_cmplx), save_as_floats);
      write_mat5_array (os, ::imag (m_cmplx), save_as_floats);
    }
  else if (tc.is_map () || tc.is_inline_function()) 
    {
      const Octave_map m = tc.map_value ();
      if (tc.is_inline_function ())
	{
	  std::string classname = "inline";
	  int namelen = classname.length ();

	  if (namelen > max_namelen)
	    namelen = max_namelen; // only 31 or 63 char names permitted

	  int paddedlength = PAD (namelen);

	  write_mat5_tag (os, miINT8, namelen);
	  OCTAVE_LOCAL_BUFFER (char, paddedname, paddedlength);
	  memset (paddedname, 0, paddedlength);
	  strncpy (paddedname, classname.c_str (), namelen);
	  os.write (paddedname, paddedlength);
	}

      // an Octave structure */
      // recursively write each element of the structure
      {
	char buf[64];
	int32_t maxfieldnamelength = max_namelen + 1;
	int fieldcnt = 0;

	for (Octave_map::const_iterator i = m.begin (); i != m.end (); i++)
	  fieldcnt++;

	write_mat5_tag (os, miINT32, 4);
	os.write (reinterpret_cast<char *> (&maxfieldnamelength), 4);
	write_mat5_tag (os, miINT8, fieldcnt*maxfieldnamelength);

	for (Octave_map::const_iterator i = m.begin (); i != m.end (); i++)
	  {
	    // write the name of each element
	    std::string tstr = m.key (i);
	    memset (buf, 0, max_namelen + 1);
	    strncpy (buf, tstr.c_str (), max_namelen); // only 31 or 63 char names permitted
	    os.write (buf, max_namelen + 1);
	  }

	int len = m.numel ();

	for (int j = 0; j < len; j++)
	  {
	    // write the data of each element

	    for (Octave_map::const_iterator i = m.begin (); i != m.end (); i++)
	      {
		Cell elts = m.contents (i);

		bool retval2 = save_mat5_binary_element (os, elts(j), "",
							 mark_as_global,
							 false, 
							 save_as_floats);
		if (! retval2)
		  goto error_cleanup;
	      }
	  }
      }
    }
  else
    gripe_wrong_type_arg ("save", tc, false);

  contin = os.tellp ();

  return true;

 error_cleanup:
  error ("save: error while writing `%s' to MAT file", name.c_str ());

  return false;
}

/*
;;; Local Variables: ***
;;; mode: C++ ***
;;; End: ***
*/

