/*
 * Copyright (c) 2002-2006 Samit Basu
 * Copyright (c) 2006 Thomas Beutlich
 *
 * This program 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 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

#include "Array.hpp"
#include <stdio.h>
#include <errno.h>
#include <math.h>
#include "Malloc.hpp"
#include "HandleList.hpp"
#include "Interpreter.hpp"
#include "File.hpp"
#include "Serialize.hpp"
#include "IEEEFP.hpp"
#include "Sparse.hpp"
#include "Core.hpp"
#include <QThread>
#include <QImage>
#include <QColor>
#include <QFile>
#include <QTextStream>
#include <QFileInfo>
#include "Utils.hpp"
#include "PathSearch.hpp"
#if HAVE_PORTAUDIO19 || HAVE_PORTAUDIO18
#include "portaudio.h"
#endif
#include "Print.hpp"

class FilePtr {
public:
  FILE *fp;
  bool swapflag;
};
  
HandleList<FilePtr*> fileHandles;

static bool init = false;

void InitializeFileSubsystem() {
  if (init) 
    return;
  FilePtr *fptr = new FilePtr();
  fptr->fp = stdin;
  fptr->swapflag = false;
  fileHandles.assignHandle(fptr);
  fptr = new FilePtr();
  fptr->fp = stdout;
  fptr->swapflag = false;
  fileHandles.assignHandle(fptr);
  fptr = new FilePtr();
  fptr->fp = stderr;
  fptr->swapflag = false;
  fileHandles.assignHandle(fptr);
  init = true;
}


const int FRAMES_PER_BUFFER = 64;

class PlayBack {
public:
  char *data;
  int length;
  int channels;
  int framesToGo;
  int ptr;
  int elementSize;
  Array x;
};

#if HAVE_PORTAUDIO19 || HAVE_PORTAUDIO18

#if HAVE_PORTAUDIO19
static int DoPlayBackCallback(const void *, void *output,
			      unsigned long framesPerBuffer,
			      const PaStreamCallbackTimeInfo*,
			      PaStreamCallbackFlags,
			      void *userData) {
#endif

#if HAVE_PORTAUDIO18
static int DoPlayBackCallback(void *, void *output,
			      unsigned long framesPerBuffer,
			      PaTimestamp,
			      void *userData) {
#endif

  PlayBack *pb = (PlayBack*) userData;
  char *out = (char*) output;
  const char *dp = pb->data;
  int framesToCalc;
  int finished = 0;
  int i;
  if( pb->framesToGo < framesPerBuffer )
    {
      framesToCalc = pb->framesToGo;
      pb->framesToGo = 0;
      finished = 1;
    }
  else
    {
      framesToCalc = framesPerBuffer;
      pb->framesToGo -= framesPerBuffer;
    }
  
  for( i=0; i<framesToCalc; i++ )
    {
      if (pb->channels == 1) {
	for (int j=0;j<pb->elementSize;j++)
	  *out++ = dp[pb->ptr*pb->elementSize + j];
	pb->ptr++;
      } else {
	for (int j=0;j<pb->elementSize;j++) 
	  *out++ = dp[pb->ptr*pb->elementSize + j];
	for (int j=0;j<pb->elementSize;j++)
	  *out++ = dp[(pb->ptr+pb->length)*pb->elementSize + j];
	pb->ptr++;
      }
    }
  /* zero remainder of final buffer */
  for( ; i<(int)framesPerBuffer; i++ )
    {
      if (pb->channels == 1)
	for (int j=0;j<pb->elementSize;j++)
	  *out++ = 0;
      else {
	for (int j=0;j<pb->elementSize;j++) {
	  *out++ = 0; /* left */
	  *out++ = 0; /* right */
	}
      }
    }
  return finished;
}

#if HAVE_PORTAUDIO19
static int DoRecordCallback(const void *input, void *,
			    unsigned long framesPerBuffer,
			    const PaStreamCallbackTimeInfo*,
			    PaStreamCallbackFlags,
			    void *userData) {
#endif

#if HAVE_PORTAUDIO18
static int DoRecordCallback(void *input, void *,
			    unsigned long framesPerBuffer,
			    PaTimestamp,
			    void *userData) {
#endif

  PlayBack *pb = (PlayBack*) userData;
  char *in = (char*) input;
  char *dp = pb->data;
  int framesToCalc;
  int finished = 0;
  int i;
  if( pb->framesToGo < framesPerBuffer )
    {
      framesToCalc = pb->framesToGo;
      pb->framesToGo = 0;
      finished = 1;
    }
  else
    {
      framesToCalc = framesPerBuffer;
      pb->framesToGo -= framesPerBuffer;
    }
  
  for( i=0; i<framesToCalc; i++ )
    {
      if (pb->channels == 1) {
	for (int j=0;j<pb->elementSize;j++)
	  dp[pb->ptr*pb->elementSize + j] = *in++;
	pb->ptr++;
      } else {
	for (int j=0;j<pb->elementSize;j++) 
	  dp[pb->ptr*pb->elementSize + j] = *in++;
	for (int j=0;j<pb->elementSize;j++)
	  dp[(pb->ptr+pb->length)*pb->elementSize + j] = *in++;
	pb->ptr++;
      }
    }
  return finished;
}

static PaError err;
static bool Initialized = false;
static bool RunningStream = false;
static PaStream *stream;
static PlayBack *pb_obj;

void PAInit() {
  err = Pa_Initialize();
  if (err != paNoError) 
    throw Exception(string("An error occured while using the portaudio stream: ") + Pa_GetErrorText(err));
}

void PAShutdown() {
#ifdef HAVE_PORTAUDIO19
  while ( ( err = Pa_IsStreamActive( stream ) ) == 1 )
    Pa_Sleep(100);
#endif

#ifdef HAVE_PORTAUDIO18
  while ( ( err = Pa_StreamActive( stream ) ) == 1 )
    Pa_Sleep(100);
#endif

  err = Pa_CloseStream(stream);
  if (err != paNoError) 
    throw Exception(string("An error occured while using the portaudio stream: ") + Pa_GetErrorText(err));
  Pa_Terminate();
  delete pb_obj;
  RunningStream = false;
}

void DoPlayBack(const void *data, int count, int channels, 
		int elementSize, unsigned long SampleFormat,
		int Rate, bool asyncMode, Array x) {
  if (RunningStream) 
    PAShutdown();
  PAInit();
  pb_obj = new PlayBack;
  pb_obj->x = x;
  pb_obj->data = (char *)data;
  pb_obj->length = count;
  pb_obj->channels = channels;
  pb_obj->ptr = 0;
  pb_obj->framesToGo = count;
  pb_obj->elementSize = elementSize;
#ifdef HAVE_PORTAUDIO19
  PaStreamParameters outputParameters;
  outputParameters.device = Pa_GetDefaultOutputDevice();
  outputParameters.channelCount = channels;
  outputParameters.sampleFormat = SampleFormat;
  outputParameters.suggestedLatency = Pa_GetDeviceInfo(outputParameters.device)->defaultLowOutputLatency;
  outputParameters.hostApiSpecificStreamInfo = NULL;
  err = Pa_OpenStream(&stream,
		      NULL, /* no input */
		      &outputParameters,
		      Rate,
		      FRAMES_PER_BUFFER,
		      paNoFlag,
		      DoPlayBackCallback,
		      pb_obj);
#endif
#ifdef HAVE_PORTAUDIO18
  err = Pa_OpenStream(&stream,
		      paNoDevice, // No input device
		      0,          // No input
		      SampleFormat,
		      NULL,
		      Pa_GetDefaultOutputDeviceID(),
		      channels,
		      SampleFormat,
		      NULL,
		      Rate,
		      FRAMES_PER_BUFFER,
		      0,
		      paClipOff,
		      DoPlayBackCallback,
		      pb_obj);
#endif
  if (err != paNoError) 
    throw Exception(string("An error occured while using the portaudio stream: ") + Pa_GetErrorText(err));
  err = Pa_StartStream(stream);
  if (err != paNoError) 
    throw Exception(string("An error occured while using the portaudio stream: ") + Pa_GetErrorText(err));
  if (!asyncMode)
    PAShutdown();
  else
    RunningStream = true;
}

void DoRecord(void *data, int count, int channels, 
	      int elementSize, unsigned long SampleFormat,
	      int Rate) {
  if (RunningStream) 
    PAShutdown();
  PAInit();
  pb_obj = new PlayBack;
  pb_obj->data = (char *)data;
  pb_obj->length = count;
  pb_obj->channels = channels;
  pb_obj->ptr = 0;
  pb_obj->framesToGo = count;
  pb_obj->elementSize = elementSize;
#if HAVE_PORTAUDIO19
  PaStreamParameters inputParameters;
  inputParameters.device = Pa_GetDefaultInputDevice();
  inputParameters.channelCount = channels;
  inputParameters.sampleFormat = SampleFormat;
  inputParameters.suggestedLatency = Pa_GetDeviceInfo(inputParameters.device)->defaultLowInputLatency;
  inputParameters.hostApiSpecificStreamInfo = NULL;
  err = Pa_OpenStream(&stream,
		      &inputParameters,
		      NULL,
		      Rate,
		      FRAMES_PER_BUFFER,
		      paNoFlag,
		      DoRecordCallback,
		      pb_obj);
#endif
#if HAVE_PORTAUDIO18
  err = Pa_OpenStream(&stream,
		      Pa_GetDefaultInputDeviceID(),
		      channels,
		      SampleFormat,
		      NULL,
		      paNoDevice,
		      0,
		      SampleFormat,
		      NULL,
		      Rate,
		      FRAMES_PER_BUFFER,
		      0,
		      0,
		      DoRecordCallback,
		      pb_obj);
#endif
  if (err != paNoError) 
    throw Exception(string("An error occured while using the portaudio stream: ") + Pa_GetErrorText(err));
  err = Pa_StartStream(stream);
  if (err != paNoError) 
    throw Exception(string("An error occured while using the portaudio stream: ") + Pa_GetErrorText(err));
  PAShutdown();
}
#endif


//!
//@Module WAVPLAY
//@@Section IO
//@@Usage
//Plays a linear PCM set of samples through the audio system.  This
//function is only available if the @|portaudio| library was available
//when FreeMat was built.  The syntax for the command is one of:
//@[
//   wavplay(y)
//   wavplay(y,sampling_rate)
//   wavplay(...,mode)
//@]
//where @|y| is a matrix of audio samples.  If @|y| has two columns, then
//the audio playback is in stereo.  The @|y| input can be of types 
//@|float, double, int32, int16, int8, uint8|.  For @|float| and 
//@|double| types, the sample values in @|y| must be between @|-1| and
//@|1|.  The @|sampling_rate| specifies the rate at which the data is 
//recorded.  If not specified, the @|sampling_rate| defaults to @|11025Hz|.
//Finally, you can specify a playback mode of @|'sync'| which is synchronous
//playback or a playback mode of @|'async'| which is asynchronous playback.
//For @|'sync'| playback, the wavplay function returns when the playback is
//complete.  For @|'async'| playback, the function returns immediately (unless
//a former playback is still issuing).
//!
ArrayVector WavPlayFunction(int nargout, const ArrayVector& argv) {
#if HAVE_PORTAUDIO18 || HAVE_PORTAUDIO19
  if(argv.size() == 0)
    throw Exception("wavplay requires at least one argument (the audio data to playback)");
  Array y(argv[0]);
  int SampleRate = 11025;
  string mode = "SYNC";
  if (argv.size() > 1)
    SampleRate = ArrayToInt32(argv[1]);
  if (argv.size() > 2)
    mode = argv[2].getContentsAsStringUpper();
  // Validate that the data is reasonable
  if ((!y.is2D())  || (!y.isVector() && (y.columns() > 2)) || 
      y.isReferenceType() || y.isComplex())
    throw Exception("wavplay only supports playback of 1 or 2 channel signals, which means a 1 or 2 column matrix");
  if (y.dataClass() == FM_DOUBLE)
    y.promoteType(FM_FLOAT);
  if ((y.dataClass() == FM_INT64) || (y.dataClass() == FM_UINT64))
    throw Exception("wavplay does not support 64 bit data types.");
  if (y.dataClass() == FM_UINT32)
    throw Exception("wavplay does not support unsigned 32-bit data types.");
  if (y.dataClass() == FM_UINT16)
    throw Exception("wavplay does not support unsigned 16-bit data types.");
  int samples;
  int channels;
  if (!y.isVector()) {
    channels = y.columns();
    samples = y.rows();
  } else {
    channels = 1;
    samples = y.getLength();
  }
  if (y.dataClass() == FM_FLOAT)
    DoPlayBack(y.getDataPointer(),samples,channels,sizeof(float),
	       paFloat32,SampleRate,mode != "SYNC",y);
  else if (y.dataClass() == FM_INT32)
    DoPlayBack(y.getDataPointer(),samples,channels,sizeof(int32),
	       paInt32,SampleRate,mode != "SYNC",y);
  else if (y.dataClass() == FM_INT16)
    DoPlayBack(y.getDataPointer(),samples,channels,sizeof(int16),
	       paInt16,SampleRate,mode != "SYNC",y);
  else if (y.dataClass() == FM_INT8)
    DoPlayBack(y.getDataPointer(),samples,channels,sizeof(int8),
	       paInt8,SampleRate,mode != "SYNC",y);
  else if (y.dataClass() == FM_UINT8)
    DoPlayBack(y.getDataPointer(),samples,channels,sizeof(uint8),
	       paUInt8,SampleRate,mode != "SYNC",y);
  else
    throw Exception("wavplay does not support this data types.");
#else
  throw Exception("Audio read/write support not available.  Please build the PortAudio library and rebuild FreeMat to enable this functionality.");
#endif
  return ArrayVector();
}

//!
//@Module WAVRECORD
//@@Section IO
//@@Usage
//Records linear PCM sound from the audio system.  This function is
//only available if the @|portaudio| library was available when FreeMat
//was built.  The syntax for this command is one of:
//@[
//  y = wavrecord(samples,rate)
//  y = wavrecord(...,channels)
//  y = wavrecord(...,'datatype')
//@]
//where @|samples| is the number of samples to record, and @|rate| is the
//sampling rate.  If not specified, the @|rate| defaults to @|11025Hz|.
//If you want to record in stero, specify @|channels = 2|.  Finally, you
//can specify the type of the recorded data (defaults to @|FM_DOUBLE|).
//Valid choices are @|float, double, int32, int16, int8, uint8|.
//!
ArrayVector WavRecordFunction(int nargout, const ArrayVector& argv) {
#if HAVE_PORTAUDIO18 || HAVE_PORTAUDIO19
  if (argv.size() < 1)
    throw Exception("wavrecord requires at least 1 argument (the number of samples to record)");
  int samples = ArrayToInt32(argv[0]);
  int rate = 11025;
  int channels = 1;
  Class datatype = FM_DOUBLE;
  ArrayVector argvCopy(argv);
  if ((argvCopy.size() > 1) && (argvCopy.back().isString())) {
    string typestring = argvCopy.back().getContentsAsStringUpper();
    if ((typestring == "FLOAT") || (typestring == "SINGLE"))
      datatype = FM_FLOAT;
    else if (typestring == "DOUBLE")
      datatype = FM_DOUBLE;
    else if (typestring == "INT32")
      datatype = FM_INT32;
    else if (typestring == "INT16")
      datatype == FM_INT16;
    else if (typestring == "INT8")
      datatype == FM_INT8;
    else if (typestring == "UINT8")
      datatype == FM_UINT8;
    else
      throw Exception("unrecognized data type - expecting one of: double, float, single, int32, int16, int8, uint8");
    argvCopy.pop_back();
  }
  // Check for a channel spec and a sampling rate
  while (argvCopy.size() > 1) {
    int ival = ArrayToInt32(argvCopy.back());
    if (ival > 2)
      rate = ival;
    else
      channels = ival;
    argvCopy.pop_back();
  }
  Class rdatatype(datatype);
  Array retvec;
  if (rdatatype == FM_DOUBLE) rdatatype = FM_FLOAT;
  void *dp = Array::allocateArray(rdatatype,samples*channels);
  switch(rdatatype) {
  case FM_FLOAT:
    DoRecord(dp,samples,channels,sizeof(float),paFloat32,rate);
    retvec = Array(FM_FLOAT,Dimensions(samples,channels),dp);
    break;
  case FM_INT32:
    DoRecord(dp,samples,channels,sizeof(int32),paInt32,rate);
    retvec = Array(FM_INT32,Dimensions(samples,channels),dp);
    break;
  case FM_INT16:
    DoRecord(dp,samples,channels,sizeof(int16),paInt16,rate);
    retvec = Array(FM_INT16,Dimensions(samples,channels),dp);
    break;
  case FM_INT8:
    DoRecord(dp,samples,channels,sizeof(int8),paInt8,rate);
    retvec = Array(FM_INT8,Dimensions(samples,channels),dp);
    break;
  case FM_UINT8:
    DoRecord(dp,samples,channels,sizeof(uint8),paUInt8,rate);
    retvec = Array(FM_UINT8,Dimensions(samples,channels),dp);
    break;
  }
  retvec.promoteType(datatype);
  return ArrayVector() << retvec;
#else
  throw Exception("Audio read/write support not available.  Please build the PortAudio library and rebuild FreeMat to enable this functionality.");
#endif
  return ArrayVector();
}

//!
//@Module FORMAT Control the Format of Matrix Display
//@@Section IO
//@@Usage
//FreeMat supports several modes for displaying matrices (either through the
//@|disp| function or simply by entering expressions on the command line.  
//There are several options for the format command.  The default mode is equivalent
//to
//@[
//   format short
//@]
//which generally displays matrices with 4 decimals, and scales matrices if the entries
//have magnitudes larger than roughly @|1e2| or smaller than @|1e-2|.   For more 
//information you can use 
//@[
//   format long
//@]
//which displays roughly 7 decimals for @|float| and @|complex| arrays, and 14 decimals
//for @|double| and @|dcomplex|.  You can also use
//@[
//   format short e
//@]
//to get exponential format with 4 decimals.  Matrices are not scaled for exponential 
//formats.  Similarly, you can use
//@[
//   format long e
//@]
//which displays the same decimals as @|format long|, but in exponential format.
//You can also use the @|format| command to retrieve the current format:
//@[
//   s = format
//@]
//where @|s| is a string describing the current format.
//@@Example
//We start with the short format, and two matrices, one of double precision, and the
//other of single precision.
//@<
//format short
//a = randn(4)
//b = float(randn(4))
//@>
//Note that in the short format, these two matrices are displayed with the same format.
//In @|long| format, however, they display differently
//@<
//format long
//a
//b
//@>
//Note also that we we scale the contents of the matrices, FreeMat rescales the entries
//with a scale premultiplier.
//@<
//format short
//a*1e4
//a*1e-4
//b*1e4
//b*1e-4
//@>
//Next, we use the exponential formats:
//@<
//format short e
//a*1e4
//a*1e-4
//b*1e4
//b*1e-4
//@>
//Finally, if we assign the @|format| function to a variable, we can retrieve the 
//current format:
//@<
//format short
//t = format
//@>
//!
ArrayVector FormatFunction(int nargout, const ArrayVector& arg) {
  if (arg.size() > 0) {
    string argtxt;
    for (int i=0;i<arg.size();i++) argtxt += arg[i].getContentsAsStringUpper();
    if (argtxt == "NATIVE") SetPrintFormatMode(format_native);
    else if (argtxt == "SHORT") SetPrintFormatMode(format_short);
    else if (argtxt == "LONG") SetPrintFormatMode(format_long);
    else if (argtxt == "SHORTE") SetPrintFormatMode(format_short_e);
    else if (argtxt == "LONGE") SetPrintFormatMode(format_long_e);
    else throw Exception("unrecognized argument to the format command");
  }
  if (nargout > 0) {
    switch(GetPrintFormatMode()) {
    case format_native:
      return ArrayVector() << Array::stringConstructor("native");
    case format_short:
      return ArrayVector() << Array::stringConstructor("short");
    case format_long:
      return ArrayVector() << Array::stringConstructor("long");
    case format_short_e:
      return ArrayVector() << Array::stringConstructor("short e");
    case format_long_e:
      return ArrayVector() << Array::stringConstructor("long e");
    }
    return ArrayVector() << Array::stringConstructor("unknown?");
  }
  return ArrayVector();
}

//!
//@Module IMREAD Read Image File To Matrix
//@@Section IO
//@@Usage
//Reads the image data from the given file into a matrix.  Note that
//FreeMat's support for @|imread| is not complete.  Only some of the
//formats specified in the MATLAB API are implemented.  The syntax
//for its use is
//@[
//  [A,map,alpha] = imread(filename)
//@]
//where @|filename| is the name of the file to read from.  The returned
//arrays @|A| contain the image data, @|map| contains the colormap information
//(for indexed images), and @|alpha| contains the alphamap (transparency).
//The returned values will depend on the type of the original image.  Generally
//you can read images in the @|jpg,png,xpm,ppm| and some other formats.
//!
static ArrayVector imreadHelperIndexed(QImage img) {
  QVector<QRgb> colorTable(img.colorTable());
  double *ctable_dp = (double*) 
    Array::allocateArray(FM_DOUBLE,colorTable.size()*3);
  int numcol = colorTable.size();
  for (int i=0;i<numcol;i++) {
    QColor c(colorTable[i]);
    ctable_dp[i] = (double) c.redF();
    ctable_dp[i+numcol] = (double) c.greenF();
    ctable_dp[i+2*numcol] = (double) c.blueF();
  }
  Array ctable(FM_DOUBLE,Dimensions(numcol,3),ctable_dp);
  uint8 *img_data_dp = (uint8*) 
    Array::allocateArray(FM_UINT8,img.width()*img.height());
  for (int row=0;row<img.height();row++) {
    uchar *p = img.scanLine(row);
    for (int col=0;col<img.width();col++) 
      img_data_dp[row+col*img.height()] = p[col];
  }
  Array A(FM_UINT8,Dimensions(img.height(),img.width()),img_data_dp);
  QImage alpha(img.alphaChannel());
  uint8 *img_alpha_dp = (uint8*)
    Array::allocateArray(FM_UINT8,img.width()*img.height());
  for (int row=0;row<alpha.height();row++) {
    uchar *p = alpha.scanLine(row);
    for (int col=0;col<alpha.width();col++)
      img_alpha_dp[row+col*img.height()] = p[col];
  }
  Array trans(FM_UINT8,Dimensions(img.height(),img.width()),img_alpha_dp);
  return ArrayVector() << A << ctable << trans;
}

static ArrayVector imreadHelperRGB32(QImage img) {
  uint8 *img_data_dp = (uint8*) 
    Array::allocateArray(FM_UINT8,img.width()*img.height()*3);
  int imgcnt = img.height()*img.width();
  for (int row=0;row<img.height();row++) {
    QRgb *p = (QRgb*) img.scanLine(row);
    for (int col=0;col<img.width();col++) {
      int ndx = row+col*img.height();
      img_data_dp[ndx] = qRed(p[col]);
      img_data_dp[ndx+1*imgcnt] = qGreen(p[col]);
      img_data_dp[ndx+2*imgcnt] = qBlue(p[col]);
    }
  }
  return ArrayVector() << 
    Array(FM_UINT8,Dimensions(img.height(),img.width(),3),img_data_dp)
		       << Array::emptyConstructor() 
		       << Array::emptyConstructor();
}

static ArrayVector imreadHelperARGB32(QImage img) {
  uint8 *img_alpha_dp = (uint8*) 
    Array::allocateArray(FM_UINT8,img.width()*img.height());
  uint8 *img_data_dp = (uint8*) 
    Array::allocateArray(FM_UINT8,img.width()*img.height()*3);
  int imgcnt = img.height()*img.width();
  for (int row=0;row<img.height();row++) {
    QRgb *p = (QRgb*) img.scanLine(row);
    for (int col=0;col<img.width();col++) {
      int ndx = row+col*img.height();
      img_data_dp[ndx] = qRed(p[col]);
      img_data_dp[ndx+1*imgcnt] = qGreen(p[col]);
      img_data_dp[ndx+2*imgcnt] = qBlue(p[col]);
      img_alpha_dp[ndx] = qAlpha(p[col]);
    }
  }
  return ArrayVector() << 
    Array(FM_UINT8,Dimensions(img.height(),img.width(),3),img_data_dp)
		       << Array::emptyConstructor() 
		       << 
    Array(FM_UINT8,Dimensions(img.height(),img.width()),img_alpha_dp);
}

ArrayVector ImReadFunction(int nargout, const ArrayVector& arg, 
			   Interpreter* eval) {
  PathSearcher psearch(eval->getTotalPath());
  if (arg.size() == 0)
    throw Exception("imread requires a filename to read.");
  string filename(ArrayToString(arg[0]));
  string completename;
  try {
    completename = psearch.ResolvePath(filename);
  } catch (Exception& e) {
    throw Exception("unable to find file " + completename);
  }
  // Construct the QImage object
  QImage img(QString::fromStdString(completename));
  if (img.isNull())
    throw Exception("unable to read file " + completename);
  if (img.format() == QImage::Format_Invalid)
    throw Exception("file " + completename + " is invalid");
  if (img.format() == QImage::Format_Indexed8) return imreadHelperIndexed(img);
  if (img.format() == QImage::Format_RGB32) return imreadHelperRGB32(img);
  if (img.format() == QImage::Format_ARGB32) return imreadHelperARGB32(img);
  throw Exception("unsupported image format - only 8 bit indexed and 24 bit RGB and 32 bit ARGB images are supported");
  return ArrayVector();
}

//!
//@Module SETPRINTLIMIT Set Limit For Printing Of Arrays
//@@Section IO
//@@Usage
//Changes the limit on how many elements of an array are printed
//using either the @|disp| function or using expressions on the
//command line without a semi-colon.  The default is set to 
//one thousand elements.  You can increase or decrease this
//limit by calling
//@[
//  setprintlimit(n)
//@]
//where @|n| is the new limit to use.
//@@Example
//Setting a smaller print limit avoids pages of output when you forget the semicolon on an expression.
//@<
//A = randn(512);
//setprintlimit(10)
//A
//setprintlimit(1000)
//@>
//!
ArrayVector SetPrintLimitFunction(int nargout, const ArrayVector& arg, Interpreter* eval) {
  if (arg.size() != 1)
    throw Exception("setprintlimit requires one, scalar integer argument");
  Array tmp(arg[0]);
  eval->setPrintLimit(tmp.getContentsAsIntegerScalar());
  return ArrayVector();
}

//!
//@Module GETPRINTLIMIT Get Limit For Printing Of Arrays
//@@Section IO
//@@Usage
//Returns the limit on how many elements of an array are printed
//using either the @|disp| function or using expressions on the
//command line without a semi-colon.  The default is set to 
//one thousand elements.  You can increase or decrease this
//limit by calling @|setprintlimit|.  This function is provided
//primarily so that you can temporarily change the output truncation
//and then restore it to the previous value (see the examples).
//@[
//   n=getprintlimit
//@]
//where @|n| is the current limit in use.
//@@Example
//Here is an example of using @|getprintlimit| along with @|setprintlimit| to temporarily change the output behavior of FreeMat.
//@<
//A = randn(100,1);
//n = getprintlimit
//setprintlimit(5);
//A
//setprintlimit(n)
//@>
//!
ArrayVector GetPrintLimitFunction(int nargout, const ArrayVector& arg, Interpreter* eval) {
  Array tmp(Array::uint32Constructor(eval->getPrintLimit()));
  ArrayVector retval;
  retval.push_back(tmp);
  return retval;
}

void SwapBuffer(char* cp, int count, int elsize) {
  char tmp;
  for (int i=0;i<count;i++)
    for (int j=0;j<elsize/2;j++) {
      tmp = cp[i*elsize+j];
      cp[i*elsize+j] = cp[i*elsize+elsize-1-j];
      cp[i*elsize+elsize-1-j] = tmp;
    }    
}

#define MATCH(x) (prec==x)
void processPrecisionString(string prec, Class &dataClass, int& elementSize, int& swapSize) {
  if (MATCH("uint8") || MATCH("uchar") || MATCH("unsigned char")) {
    dataClass = FM_UINT8;
    elementSize = 1;
    swapSize = 1;
    return;
  }
  if (MATCH("int8") || MATCH("char") || MATCH("integer*1")) {
    dataClass = FM_INT8;
    elementSize = 1;
    swapSize = 1;
    return;
  }
  if (MATCH("uint16") || MATCH("unsigned short")) {
    dataClass = FM_UINT16;
    elementSize = 2;
    swapSize = 2;
    return;
  }
  if (MATCH("int16") || MATCH("short") || MATCH("integer*2")) {
    dataClass = FM_INT16;
    elementSize = 2;
    swapSize = 2;
    return;
  }
  if (MATCH("uint32") || MATCH("unsigned int")) {
    dataClass = FM_UINT32;
    elementSize = 4;
    swapSize = 4;
    return;
  }
  if (MATCH("int32") || MATCH("int") || MATCH("integer*4")) {
    dataClass = FM_INT32;
    elementSize = 4;
    swapSize = 4;
    return;
  }
  if (MATCH("uint64")) {
    dataClass = FM_UINT64;
    elementSize = 8;
    swapSize = 8;
    return;
  }
  if (MATCH("int64") || MATCH("integer*8")) {
    dataClass = FM_INT64;
    elementSize = 8;
    swapSize = 8;
    return;
  }
  if (MATCH("single") || MATCH("float32") || MATCH("float") || MATCH("real*4")) {
    dataClass = FM_FLOAT;
    elementSize = 4;
    swapSize = 4;
    return;
  }
  if (MATCH("double") || MATCH("float64") || MATCH("real*8")) {
    dataClass = FM_DOUBLE;
    elementSize = 8;
    swapSize = 8;
    return;
  }
  if (MATCH("complex") || MATCH("complex*8")) {
    dataClass = FM_COMPLEX;
    elementSize = 8;
    swapSize = 4;
    return;
  }
  if (MATCH("dcomplex") || MATCH("complex*16")) {
    dataClass = FM_DCOMPLEX;
    elementSize = 16;
    swapSize = 8;
    return;
  }
  throw Exception("invalid precision type");
}
#undef MATCH

//!
//@Module FOPEN File Open Function
//@@Section IO
//@@Usage
//Opens a file and returns a handle which can be used for subsequent
//file manipulations.  The general syntax for its use is
//@[
//  fp = fopen(fname,mode,byteorder)
//@]
//Here @|fname| is a string containing the name of the file to be 
//opened.  @|mode| is the mode string for the file open command.
//The first character of the mode string is one of the following:
//\begin{itemize}
//  \item @|'r'|  Open  file  for  reading.  The file pointer is placed at
//          the beginning of the file.  The file can be read from, but
//	  not written to.
//  \item @|'r+'|   Open for reading and writing.  The file pointer is
//          placed at the beginning of the file.  The file can be read
//	  from and written to, but must exist at the outset.
//  \item @|'w'|    Open file for writing.  If the file already exists, it is
//          truncated to zero length.  Otherwise, a new file is
//	  created.  The file pointer is placed at the beginning of
//	  the file.
//  \item @|'w+'|   Open for reading and writing.  The file is created  if  
//          it  does not  exist, otherwise it is truncated to zero
//	  length.  The file pointer placed at the beginning of the file.
//  \item @|'a'|    Open for appending (writing at end of file).  The file  is  
//          created  if it does not exist.  The file pointer is placed at
//	  the end of the file.
//  \item @|'a+'|   Open for reading and appending (writing at end of file).   The
//          file  is created if it does not exist.  The file pointer is
//	  placed at the end of the file.
//\end{itemize}
//On some platforms (e.g. Win32) it is necessary to add a 'b' for 
//binary files to avoid the operating system's 'CR/LF<->CR' translation.
//
//Finally, FreeMat has the ability to read and write files of any
//byte-sex (endian).  The third (optional) input indicates the 
//byte-endianness of the file.  If it is omitted, the native endian-ness
//of the machine running FreeMat is used.  Otherwise, the third
//argument should be one of the following strings:
//\begin{itemize}
//   \item @|'le','ieee-le','little-endian','littleEndian','little'|
//   \item @|'be','ieee-be','big-endian','bigEndian','big'|
//\end{itemize}
//	
//If the file cannot be opened, or the file mode is illegal, then
//an error occurs. Otherwise, a file handle is returned (which is
//an integer).  This file handle can then be used with @|fread|,
//@|fwrite|, or @|fclose| for file access.
//
//Note that three handles are assigned at initialization time:
//\begin{itemize}
//   \item Handle 0 - is assigned to standard input
//   \item Handle 1 - is assigned to standard output
//   \item Handle 2 - is assigned to standard error
//\end{itemize}
//These handles cannot be closed, so that user created file handles start at @|3|.
//
//@@Examples
//Here are some examples of how to use @|fopen|.  First, we create a new 
//file, which we want to be little-endian, regardless of the type of the machine.
//We also use the @|fwrite| function to write some floating point data to
//the file.
//@<
//fp = fopen('test.dat','wb','ieee-le')
//fwrite(fp,float([1.2,4.3,2.1]))
//fclose(fp)
//@>
//Next, we open the file and read the data back
//@<
//fp = fopen('test.dat','rb','ieee-le')
//fread(fp,[1,3],'float')
//fclose(fp)
//@>
//Now, we re-open the file in append mode and add two additional @|float|s to the
//file.
//@<
//fp = fopen('test.dat','a+','le')
//fwrite(fp,float([pi,e]))
//fclose(fp)
//@>
//Finally, we read all 5 @|float| values from the file
//@<
//fp = fopen('test.dat','rb','ieee-le')
//fread(fp,[1,5],'float')
//fclose(fp)
//@>
//!
ArrayVector FopenFunction(int nargout, const ArrayVector& arg) {
  uint32 testEndian = 0xFEEDFACE;
  uint8 *dp;
  bool bigEndian;

  dp = (uint8*) &testEndian;
  bigEndian = (dp[0] == 0xFE);

  if (arg.size() > 3)
    throw Exception("too many arguments to fopen");
  if (arg.size() < 1)
    throw Exception("fopen requires at least one argument (a filename)");
  if (!(arg[0].isString()))
    throw Exception("First argument to fopen must be a filename");
  string fname = arg[0].getContentsAsString();
  string mode = "rb";
  if (arg.size() > 1) {
    if (!arg[1].isString())
      throw Exception("Access mode to fopen must be a string");
    mode = arg[1].getContentsAsString();
  }
  bool swapendian = false;
  if (arg.size() > 2) {
    string swapflag = arg[2].getContentsAsString();
    if (swapflag=="swap") {
      swapendian = true;
    } else if ((swapflag=="le") ||
	       (swapflag=="ieee-le") ||
	       (swapflag=="little-endian") ||
	       (swapflag=="littleEndian") ||
	       (swapflag=="little")) {
      swapendian = bigEndian;
    } else if ((swapflag=="be") ||
	       (swapflag=="ieee-be") ||
	       (swapflag=="big-endian") ||
	       (swapflag=="bigEndian") ||
	       (swapflag=="big")) {
      swapendian = !bigEndian;
    } else if (!arg[2].isEmpty())
      throw Exception("swap flag must be 'swap' or an endian spec ('le','ieee-le','little-endian','littleEndian','little','be','ieee-be','big-endian','bigEndian','big')");
  }
  FILE *fp = fopen(fname.c_str(),mode.c_str());
  if (!fp)
    throw Exception(strerror(errno) + string(" for fopen argument ") + fname);
  FilePtr *fptr = new FilePtr();
  fptr->fp = fp;
  fptr->swapflag = swapendian;
  unsigned int rethan = fileHandles.assignHandle(fptr);
  ArrayVector retval;
  retval.push_back(Array::uint32Constructor(rethan-1));
  return retval;
}

//!
//@Module FCLOSE File Close Function
//@@Section IO
//@@Usage
//Closes a file handle, or all open file handles.  The general syntax
//for its use is either
//@[
//  fclose(handle)
//@]
//or
//@[
//  fclose('all')
//@]
//In the first case a specific file is closed,  In the second, all open
//files are closed.  Note that until a file is closed the file buffers
//are not flushed.  Returns a '0' if the close was successful and a '-1' if
//the close failed for some reason.
//@@Example
//A simple example of a file being opened with @|fopen| and then closed with @|fclose|.
//@<
//fp = fopen('test.dat','wb','ieee-le')
//fclose(fp)
//@>
//!
ArrayVector FcloseFunction(int nargout, const ArrayVector& arg) {
  if (arg.size() != 1)
    throw Exception("Fclose must have one argument, either 'all' or a file handle");
  bool closingAll = false;
  int retval = 0;
  if (arg[0].isString()) {
    string allflag = arg[0].getContentsAsString();
    if (allflag == "all") {
      closingAll = true;
      int maxHandle(fileHandles.maxHandle());
      for (int i=3;i<maxHandle;i++) {
	try {
	  FilePtr* fptr = fileHandles.lookupHandle(i+1);
	  fclose(fptr->fp);
	  fileHandles.deleteHandle(i+1);
	  delete fptr;
	} catch (Exception & e) {
	}
      }
    } else
      throw Exception("Fclose must have one argument, either 'all' or a file handle");
  } else {
    Array tmp(arg[0]);
    int handle = tmp.getContentsAsIntegerScalar();
    if (handle <= 2)
      throw Exception("Cannot close handles 0-2, the standard in/out/error file handles");
    FilePtr* fptr = (fileHandles.lookupHandle(handle+1));
    if (fclose(fptr->fp))
      retval = -1;
    fileHandles.deleteHandle(handle+1);
    delete fptr;
  }
  return singleArrayVector(Array::int32Constructor(retval));
}

//!
//@Module FREAD File Read Function
//@@Section IO
//@@Usage
//Reads a block of binary data from the given file handle into a variable
//of a given shape and precision.  The general use of the function is
//@[
//  A = fread(handle,size,precision)
//@]
//The @|handle| argument must be a valid value returned by the fopen 
//function, and accessable for reading.  The @|size| argument determines
//the number of values read from the file.  The @|size| argument is simply
//a vector indicating the size of the array @|A|.  The @|size| argument
//can also contain a single @|inf| dimension, indicating that FreeMat should
//calculate the size of the array along that dimension so as to read as
//much data as possible from the file (see the examples listed below for
//more details).  The data is stored as columns in the file, not 
//rows.
//    
//Alternately, you can specify two return values to the @|fread| function,
//in which case the second value contains the number of elements read
//@[
//   [A,count] = fread(...)
//@]
//where @|count| is the number of elements in @|A|.
//
//The third argument determines the type of the data.  Legal values for this
//argument are listed below:
//\begin{itemize}
//   \item 'uint8','uchar','unsigned char' for an unsigned, 8-bit integer.
//   \item 'int8','char','integer*1' for a signed, 8-bit integer.
//   \item 'uint16','unsigned short' for an unsigned, 16-bit  integer.
//   \item 'int16','short','integer*2' for a signed, 16-bit integer.
//   \item 'uint32','unsigned int' for an unsigned, 32-bit integer.
//   \item 'int32','int','integer*4' for a signed, 32-bit integer.
//   \item 'single','float32','float','real*4' for a 32-bit floating point.
//   \item 'double','float64','real*8' for a 64-bit floating point.
//   \item 'complex','complex*8' for a 64-bit complex floating point (32 bits for the real and imaginary part).
//   \item 'dcomplex','complex*16' for a 128-bit complex floating point (64 bits for the real and imaginary part).
//\end{itemize}
//@@Example
//First, we create an array of @|512 x 512| Gaussian-distributed @|float| random variables, and then writing them to a file called @|test.dat|.
//@<
//A = float(randn(512));
//fp = fopen('test.dat','wb');
//fwrite(fp,A);
//fclose(fp);
//@>
//Read as many floats as possible into a row vector
//@<
//fp = fopen('test.dat','rb');
//x = fread(fp,[1,inf],'float');
//who x
//@>
//Read the same floats into a 2-D float array.
//@<
//fp = fopen('test.dat','rb');
//x = fread(fp,[512,inf],'float');
//who x
//@>
//!
ArrayVector FreadFunction(int nargout, const ArrayVector& arg) {
  if (arg.size() < 3)
    throw Exception("fread requires three arguments, the file handle, size, and precision");
  Array tmp(arg[0]);
  int handle = tmp.getContentsAsIntegerScalar();
  FilePtr *fptr=(fileHandles.lookupHandle(handle+1));
  if (!arg[2].isString())
    throw Exception("second argument to fread must be a precision");
  string prec = arg[2].getContentsAsString();
  // Get the size argument
  Array sze(arg[1]);
  // Promote sze to a float argument
  sze.promoteType(FM_FLOAT);
  // Check for a single infinity
  int dimCount(sze.getLength());
  float *dp = ((float *) sze.getReadWriteDataPointer());
  bool infinityFound = false;
  int elementCount = 1;
  int infiniteDim = 0;
  for (int i=0;i<dimCount;i++) {
    if (IsNaN(dp[i])) throw Exception("nan not allowed in size argument");
    if (IsInfinite(dp[i])) {
      if (infinityFound) throw Exception("only a single inf is allowed in size argument");
      infinityFound = true;
      infiniteDim = i;
    } else {
      if (dp[i] < 0) throw Exception("illegal negative size argument");
      elementCount *= (int) dp[i];
    }
  }
  // Map the precision string to a data class
  Class dataClass;
  int elementSize;
  int swapSize;
  processPrecisionString(prec,dataClass,elementSize,swapSize);
  // If there is an infinity in the dimensions, we have to calculate the
  // appropriate value
  if (infinityFound) {
    long fsize;
    long fcpos;
    fcpos = ftell(fptr->fp);
    fseek(fptr->fp,0L,SEEK_END);
    fsize = ftell(fptr->fp) - fcpos;
    fseek(fptr->fp,fcpos,SEEK_SET);
    dp[infiniteDim] = ceil((double)(fsize/elementSize/elementCount));
    elementCount *= (int) dp[infiniteDim];
  }
  // Next, we allocate space for the result
  void *qp = Malloc(elementCount*elementSize);
  // Read in the requested number of data points...
  size_t g = fread(qp,elementSize,elementCount,fptr->fp);
  if (g != elementCount)
    throw Exception("Insufficient data remaining in file to fill out requested size");
  if (ferror(fptr->fp)) 
    throw Exception(strerror(errno));
  if (fptr->swapflag)
    SwapBuffer((char *)qp,elementCount*elementSize/swapSize,swapSize);
  // Convert dp to a Dimensions
  Dimensions dims(dimCount);
  for (int j=0;j<dimCount;j++)
    dims.set(j,(int) dp[j]);
  if (dimCount == 1)
    dims.set(1,1);
  ArrayVector retval;
  retval.push_back(Array::Array(dataClass,dims,qp));
  if (nargout == 2)
    retval.push_back(Array::uint32Constructor(retval[0].getLength()));
  return retval;
}

//!
//@Module FWRITE File Write Function
//@@Section IO
//@@Usage
//Writes an array to a given file handle as a block of binary (raw) data.
//The general use of the function is
//@[
//  n = fwrite(handle,A)
//@]
//The @|handle| argument must be a valid value returned by the fopen 
//function, and accessable for writing. The array @|A| is written to
//the file a column at a time.  The form of the output data depends
//on (and is inferred from) the precision of the array @|A|.  If the 
//write fails (because we ran out of disk space, etc.) then an error
//is returned.  The output @|n| indicates the number of elements
//successfully written.
//@@Example
//Heres an example of writing an array of @|512 x 512| Gaussian-distributed @|float| random variables, and then writing them to a file called @|test.dat|.
//@<
//A = float(randn(512));
//fp = fopen('test.dat','wb');
//fwrite(fp,A);
//fclose(fp);
//@>
//!
ArrayVector FwriteFunction(int nargout, const ArrayVector& arg) {
  if (arg.size() < 2)
    throw Exception("fwrite requires two arguments, the file handle, and the variable to be written");
  Array tmp(arg[0]);
  int handle = tmp.getContentsAsIntegerScalar();
  FilePtr *fptr=(fileHandles.lookupHandle(handle+1));
  if (arg[1].isReferenceType())
    throw Exception("cannot write reference data types with fwrite");
  Array toWrite(arg[1]);
  unsigned int written;
  unsigned int count(toWrite.getLength());
  unsigned int elsize(toWrite.getElementSize());
  if (!fptr->swapflag || (elsize == 1)) {
    const void *dp=(toWrite.getDataPointer());
    written = fwrite(dp,elsize,count,fptr->fp);      
  } else {
    void *dp=(toWrite.getReadWriteDataPointer());
    SwapBuffer((char*) dp, count, elsize);
    written = fwrite(dp,elsize,count,fptr->fp);
  }
  ArrayVector retval;
  retval.push_back(Array::uint32Constructor(written));
  return retval;    
}

//!
//@Module FFLUSH Force File Flush
//@@Section IO
//@@Usage
//Flushes any pending output to a given file.  The general use of
//this function is
//@[
//   fflush(handle)
//@]
//where @|handle| is an active file handle (as returned by @|fopen|).
//!
 ArrayVector FflushFunction(int nargout, const ArrayVector& arg) {
   if (arg.size() != 1)
     throw Exception("fflush requires an argument, the file handle.");
   int handle = ArrayToInt32(arg[0]);
   FilePtr *fptr = (fileHandles.lookupHandle(handle+1));
   fflush(fptr->fp);
   return ArrayVector();
 }

//!
//@Module FTELL File Position Function
//@@Section IO
//@@Usage
//Returns the current file position for a valid file handle.
//The general use of this function is
//@[
//  n = ftell(handle)
//@]
//The @|handle| argument must be a valid and active file handle.  The
//return is the offset into the file relative to the start of the
//file (in bytes).
//@@Example
//Here is an example of using @|ftell| to determine the current file 
//position.  We read 512 4-byte floats, which results in the file 
//pointer being at position 512*4 = 2048.
//@<
//fp = fopen('test.dat','wb');
//fwrite(fp,randn(512,1));
//fclose(fp);
//fp = fopen('test.dat','rb');
//x = fread(fp,[512,1],'float');
//ftell(fp)
//@>
//!
ArrayVector FtellFunction(int nargout, const ArrayVector& arg) {
  if (arg.size() != 1)
    throw Exception("ftell requires an argument, the file handle.");
  Array tmp(arg[0]);
  int handle = tmp.getContentsAsIntegerScalar();
  FilePtr *fptr=(fileHandles.lookupHandle(handle+1));
  unsigned int fpos;
  fpos = ftell(fptr->fp);
  ArrayVector retval;
  retval.push_back(Array::uint32Constructor(fpos));
  return retval;
}

//!
//@Module FEOF End Of File Function
//@@Section IO
//@@Usage
//Check to see if we are at the end of the file.  The usage is
//@[
//  b = feof(handle)
//@]
//The @|handle| argument must be a valid and active file handle.  The
//return is true (logical 1) if the current position is at the end of
//the file, and false (logical 0) otherwise.  Note that simply reading
//to the end of a file will not cause @|feof| to return @|true|.  
//You must read past the end of the file (which will cause an error 
//anyway).  See the example for more details.
//@@Example
//Here, we read to the end of the file to demonstrate how @|feof| works.
//At first pass, we force a read of the contents of the file by specifying
//@|inf| for the dimension of the array to read.  We then test the
//end of file, and somewhat counter-intuitively, the answer is @|false|.
//We then attempt to read past the end of the file, which causes an
//error.  An @|feof| test now returns the expected value of @|true|.
//@<1
//fp = fopen('test.dat','rb');
//x = fread(fp,[512,inf],'float');
//feof(fp)
//x = fread(fp,[1,1],'float');
//feof(fp)
//@>
//!
ArrayVector FeofFunction(int nargout, const ArrayVector& arg) {
  if (arg.size() != 1)
    throw Exception("feof requires an argument, the file handle.");
  Array tmp(arg[0]);
  int handle = tmp.getContentsAsIntegerScalar();
  FilePtr *fptr=(fileHandles.lookupHandle(handle+1));
  int ateof;
  ateof = feof(fptr->fp);
  ArrayVector retval;
  retval.push_back(Array::logicalConstructor(ateof));
  return retval;
}
  
//!
//@Module FSEEK Seek File To A Given Position
//@@Section IO
//@@Usage
//Moves the file pointer associated with the given file handle to 
//the specified offset (in bytes).  The usage is
//@[
//  fseek(handle,offset,style)
//@]
//The @|handle| argument must be a value and active file handle.  The
//@|offset| parameter indicates the desired seek offset (how much the
//file pointer is moved in bytes).  The @|style| parameter determines
//how the offset is treated.  Three values for the @|style| parameter
//are understood:
//\begin{itemize}
//\item string @|'bof'| or the value -1, which indicate the seek is relative
//to the beginning of the file.  This is equivalent to @|SEEK_SET| in
//ANSI C.
//\item string @|'cof'| or the value 0, which indicates the seek is relative
//to the current position of the file.  This is equivalent to 
//@|SEEK_CUR| in ANSI C.
//\item string @|'eof'| or the value 1, which indicates the seek is relative
//to the end of the file.  This is equivalent to @|SEEK_END| in ANSI
//C.
//\end{itemize}
//The offset can be positive or negative.
//@@Example
//The first example reads a file and then ``rewinds'' the file pointer by seeking to the beginning.
//The next example seeks forward by 2048 bytes from the files current position, and then reads a line of 512 floats.
//@<
//% First we create the file
//fp = fopen('test.dat','wb');
//fwrite(fp,float(rand(4096,1)));
//fclose(fp);
//% Now we open it
//fp = fopen('test.dat','rb');
//% Read the whole thing
//x = fread(fp,[1,inf],'float');
//% Rewind to the beginning
//fseek(fp,0,'bof');
//% Read part of the file
//y = fread(fp,[1,1024],'float');
//who x y
//% Seek 2048 bytes into the file
//fseek(fp,2048,'cof');
//% Read 512 floats from the file
//x = fread(fp,[512,1],'float');
//% Close the file
//fclose(fp);
//@>
//!
ArrayVector FseekFunction(int nargout, const ArrayVector& arg) {
  if (arg.size() != 3)
    throw Exception("fseek requires three arguments, the file handle, the offset, and style");
  Array tmp1(arg[0]);
  int handle = tmp1.getContentsAsIntegerScalar();
  FilePtr *fptr=(fileHandles.lookupHandle(handle+1));
  Array tmp2(arg[1]);
  int offset = tmp2.getContentsAsIntegerScalar();
  Array tmp3(arg[2]);
  int style;
  if (tmp3.isString()) {
    string styleflag = arg[2].getContentsAsString();
    if (styleflag=="bof" || styleflag=="BOF")
      style = -1;
    else if (styleflag=="cof" || styleflag=="COF")
      style = 0;
    else if (styleflag=="eof" || styleflag=="EOF")
      style = 1;
    else
      throw Exception("unrecognized style format for fseek");
  } else {
    style = tmp3.getContentsAsIntegerScalar();
    if ((style != -1) && (style != 0) && (style != 1))
      throw Exception("unrecognized style format for fseek");	
  }
  switch (style) {
  case -1:
    if (fseek(fptr->fp,offset,SEEK_SET))
      throw Exception(strerror(errno));
    break;
  case 0:
    if (fseek(fptr->fp,offset,SEEK_CUR))
      throw Exception(strerror(errno));
    break;
  case 1:
    if (fseek(fptr->fp,offset,SEEK_END))
      throw Exception(strerror(errno));
    break;
  }
  return ArrayVector();
}

int flagChar(char c) {
  return ((c == '#') ||  (c == '0') || (c == '-') ||  
	  (c == ' ') ||  (c == '+'));
}
  
int convspec(char c) {
  return ((c == 'd') || (c == 'i') || (c == 'o') || 
	  (c == 'u') || (c == 'x') || (c == 'X') ||
	  (c == 'e') || (c == 'E') || (c == 'f') || 
	  (c == 'F') || (c == 'g') || (c == 'G') ||
	  (c == 'a') || (c == 'A') || (c == 'c') || 
	  (c == 's'));
}
    
char* validateFormatSpec(char* cp) {
  if (*cp == '%') return cp+1;
  while ((*cp) && flagChar(*cp)) cp++;
  while ((*cp) && isdigit(*cp)) cp++;
  while ((*cp) && (*cp == '.')) cp++;
  while ((*cp) && isdigit(*cp)) cp++;
  if ((*cp) && convspec(*cp)) 
    return cp+1;
  else
    return 0;
}

char* validateScanFormatSpec(char* cp) {
  if (*cp == '%') return cp+1;
  while ((*cp) && flagChar(*cp)) cp++;
  while ((*cp) && isdigit(*cp)) cp++;
  while ((*cp) && (*cp == '.')) cp++;
  while ((*cp) && isdigit(*cp)) cp++;
  if ((*cp) && (convspec(*cp) || (*cp == 'h') || (*cp == 'l')))
    return cp+1;
  else
    return 0;
}
    
bool isEscape(char *dp) {
  return ((dp[0] == '\\') &&
	  ((dp[1] == 'n') ||
	   (dp[1] == 't') ||
	   (dp[1] == 'r') ||
	   (dp[1] == '\\')));
}
  
void convertEscapeSequences(char *dst, char* src) {
  char *sp;
  char *dp;
  sp = src;
  dp = dst;
  while (*sp) {
    // Is this an escape sequence?
    if (isEscape(sp)) {
      switch (sp[1]) {
      case '\\':
	*(dp++) = '\\';
	break;
      case 'n':
	*(dp++) = '\n';
	break;
      case 't':
	*(dp++) = '\t';
	break;
      case 'r':
	*(dp++) = '\r';
	break;
      }
      sp += 2;
    } else
      *(dp++) = *(sp++);
  }
  // Null terminate
  *dp = 0;
}

  
//Common routine used by sprintf,printf,fprintf.  They all
//take the same inputs, and output either to a string, the
//console or a file.  For output to a console or a file, 
//we want escape-translation on.  For output to a string, we
//want escape-translation off.  So this common routine prints
//the contents to a string, which is then processed by each 
//subroutine.
char* xprintfFunction(int nargout, const ArrayVector& arg) {
  Array format(arg[0]);
  string frmt = format.getContentsAsString();
  char *buff = strdup(frmt.c_str());
  // Search for the start of a format subspec
  char *dp = buff;
  char *np;
  char sv;
  // Buffer to hold each sprintf result
#define BUFSIZE 65536
  char nbuff[BUFSIZE];
  // Buffer to hold the output
  char *op;
  op = (char*) malloc(sizeof(char));
  *op = 0;
  int nextArg = 1;
  // Scan the string
  while (*dp) {
    np = dp;
    while ((*dp) && (*dp != '%')) dp++;
    // Print out the formatless segment
    sv = *dp;
    *dp = 0;
    snprintf(nbuff,BUFSIZE,np);
    op = (char*) realloc(op,strlen(op)+strlen(nbuff)+1);
    strcat(op,nbuff);
    *dp = sv;
    // Process the format spec
    if (*dp) {
      np = validateFormatSpec(dp+1);
      if (!np)
	throw Exception("erroneous format specification " + std::string(dp));
      else {
	if (*(np-1) == '%') {
	  snprintf(nbuff,BUFSIZE,"%%");
	  op = (char*) realloc(op,strlen(op)+strlen(nbuff)+1);
	  strcat(op,nbuff);
	  dp+=2;
	} else {
	  sv = *np;
	  *np = 0;
	  if (arg.size() <= nextArg)
	    throw Exception("not enough arguments to satisfy format specification");
	  Array nextVal(arg[nextArg++]);
	  if ((*(np-1) != 's') && (nextVal.isEmpty())) {
	    op = (char*) realloc(op,strlen(op)+strlen("[]")+1);
	    strcat(op,"[]");
	  } else {
	    switch (*(np-1)) {
	    case 'd':
	    case 'i':
	    case 'o':
	    case 'u':
	    case 'x':
	    case 'X':
	    case 'c':
	      nextVal.promoteType(FM_INT32);
	      snprintf(nbuff,BUFSIZE,dp,*((int32*)nextVal.getDataPointer()));
	      op = (char*) realloc(op,strlen(op)+strlen(nbuff)+1);
	      strcat(op,nbuff);
	      break;
	    case 'e':
	    case 'E':
	    case 'f':
	    case 'F':
	    case 'g':
	    case 'G':
	      nextVal.promoteType(FM_DOUBLE);
	      snprintf(nbuff,BUFSIZE,dp,*((double*)nextVal.getDataPointer()));
	      op = (char*) realloc(op,strlen(op)+strlen(nbuff)+1);
	      strcat(op,nbuff);
	      break;
	    case 's':
	      snprintf(nbuff,BUFSIZE,dp,nextVal.getContentsAsString().c_str());
	      op = (char*) realloc(op,strlen(op)+strlen(nbuff)+1);
	      strcat(op,nbuff);
	    }
	  }
	  *np = sv;
	  dp = np;
	}
      }
    }
  }
  free(buff);
  return op;
}

//!
//@Module NUM2STR Convert Numbers To Strings
//@@Section ARRAY
//@@Usage
//Converts an array into its string representation.  The general syntax
//for this function is
//@[
//   s = num2str(X)
//@]
//where @|s| is a string (or string matrix) and @|X| is an array.  By
//default, the @|num2str| function uses 4 digits of precision and an 
//exponent if required.  If you want more digits of precision, you can 
//specify the precition via the form
//@[
//   s = num2str(X, precision)
//@]
//where @|precision| is the number of digits to include in the string
//representation.  For more control over the format of the output, you 
//can also specify a format specifier (see @|printf| for more details).
//@[
//   s = num2str(X, format)
//@]
//where @|format| is the specifier string.
//!
template <class T>
Array Num2StrHelperReal(const T*dp, Dimensions Xdims, const char *formatspec) {
  int rows(Xdims.getRows());
  int cols(Xdims.getColumns());
  stringVector row_string;
  if (Xdims.getLength() == 2)
    Xdims.set(3,1);
  Dimensions Wdims(Xdims.getLength());
  int offset = 0;
  while (Wdims.inside(Xdims)) {
    for (int i=0;i<rows;i++) {
      string colbuffer;
      for (int j=0;j<cols;j++) {
	char elbuffer[1024];
	sprintf(elbuffer,formatspec,dp[i+j*rows+offset]);
	char elbuffer2[1024];
	convertEscapeSequences(elbuffer2,elbuffer);
	colbuffer += string(elbuffer2) + " ";
      }
      row_string.push_back(colbuffer);
    }
    offset += rows*cols;
    Wdims.incrementModulo(Xdims,2);
  }
  // Next we compute the length of the largest string
  int maxlen = 0;
  for (int n=0;n<row_string.size();n++)
    if (row_string[n].size() > maxlen)
      maxlen = row_string[n].size();
  // Next we allocate a character array large enough to
  // hold the string array.
  char *sp = (char*) Array::allocateArray(FM_STRING,maxlen*row_string.size());
  // Now we copy 
  int slices = row_string.size() / rows;
  for (int i=0;i<slices;i++)
    for (int j=0;j<rows;j++) {
      string line(row_string[j+i*rows]);
      for (int k=0;k<line.size();k++)
	sp[j+i*rows*maxlen+k*rows] = line[k];
    }
  Dimensions odims(Xdims);
  odims.set(1,maxlen);
  odims.simplify();
  return Array(FM_STRING,odims,sp);
}

template <class T>
Array Num2StrHelperComplex(const T*dp, Dimensions Xdims, const char *formatspec) {
  int rows(Xdims.getRows());
  int cols(Xdims.getColumns());
  stringVector row_string;
  if (Xdims.getLength() == 2)
    Xdims.set(3,1);
  Dimensions Wdims(Xdims.getLength());
  int offset = 0;
  while (Wdims.inside(Xdims)) {
    for (int i=0;i<rows;i++) {
      string colbuffer;
      for (int j=0;j<cols;j++) {
	char elbuffer[1024];
	char elbuffer2[1024];
	sprintf(elbuffer,formatspec,dp[2*(i+j*rows+offset)]);
	convertEscapeSequences(elbuffer2,elbuffer);
	colbuffer += string(elbuffer2) + " ";
	sprintf(elbuffer,formatspec,dp[2*(i+j*rows+offset)+1]);
	convertEscapeSequences(elbuffer2,elbuffer);
	if (dp[2*(i+j*rows+offset)+1]>=0) 
	  colbuffer += "+";
	colbuffer += string(elbuffer2) + "i ";
      }
      row_string.push_back(colbuffer);
    }
    offset += rows*cols;
    Wdims.incrementModulo(Xdims,2);
  }
  // Next we compute the length of the largest string
  int maxlen = 0;
  for (int n=0;n<row_string.size();n++)
    if (row_string[n].size() > maxlen)
      maxlen = row_string[n].size();
  // Next we allocate a character array large enough to
  // hold the string array.
  char *sp = (char*) Array::allocateArray(FM_STRING,maxlen*row_string.size());
  // Now we copy 
  int slices = row_string.size() / rows;
  for (int i=0;i<slices;i++)
    for (int j=0;j<rows;j++) {
      string line(row_string[j+i*rows]);
      for (int k=0;k<line.size();k++)
	sp[j+i*rows*maxlen+k*rows] = line[k];
    }
  Dimensions odims(Xdims);
  odims.set(1,maxlen);
  odims.simplify();
  return Array(FM_STRING,odims,sp);
}


ArrayVector Num2StrFunction(int nargout, const ArrayVector& arg) {
  if (arg.size() == 0)
    throw Exception("num2str function requires at least one argument");
  Array X(arg[0]);
  if (X.isReferenceType())
    throw Exception("num2str function requires a numeric input");
  char formatspec[1024];
  if (X.isIntegerClass())
    sprintf(formatspec,"%%d");
  else
    sprintf(formatspec,"%%11.4g");
  if (arg.size() > 1) {
    Array format(arg[1]);
    if (format.isString())
      strcpy(formatspec,ArrayToString(format).c_str());
  }
  switch (X.dataClass()) 
    {
    case FM_UINT8:
      return ArrayVector() << Num2StrHelperReal((const uint8*) X.getDataPointer(),
						X.dimensions(),formatspec);
    case FM_INT8:
      return ArrayVector() << Num2StrHelperReal((const int8*) X.getDataPointer(),
						X.dimensions(),formatspec);
    case FM_UINT16:
      return ArrayVector() << Num2StrHelperReal((const uint16*) X.getDataPointer(),
						X.dimensions(),formatspec);
    case FM_INT16:
      return ArrayVector() << Num2StrHelperReal((const int16*) X.getDataPointer(),
						X.dimensions(),formatspec);
    case FM_UINT32:
      return ArrayVector() << Num2StrHelperReal((const uint32*) X.getDataPointer(),
						X.dimensions(),formatspec);
    case FM_INT32:
      return ArrayVector() << Num2StrHelperReal((const int32*) X.getDataPointer(),
						X.dimensions(),formatspec);
    case FM_UINT64:
      return ArrayVector() << Num2StrHelperReal((const uint64*) X.getDataPointer(),
						X.dimensions(),formatspec);
    case FM_INT64:
      return ArrayVector() << Num2StrHelperReal((const int64*) X.getDataPointer(),
						X.dimensions(),formatspec);
    case FM_FLOAT:
      return ArrayVector() << Num2StrHelperReal((const float*) X.getDataPointer(),
						X.dimensions(),formatspec);
    case FM_DOUBLE:
      return ArrayVector() << Num2StrHelperReal((const double*) X.getDataPointer(),
						X.dimensions(),formatspec);
    case FM_COMPLEX:
      return ArrayVector() << Num2StrHelperComplex((const float*) X.getDataPointer(),
						   X.dimensions(),formatspec);
    case FM_DCOMPLEX:
      return ArrayVector() << Num2StrHelperComplex((const double*) X.getDataPointer(),
						   X.dimensions(),formatspec);
    case FM_STRING:
      throw Exception("argument to num2str must be numeric type");
    }
  return ArrayVector();
}

//!
//@Module SPRINTF Formated String Output Function (C-Style)
//@@Section IO
//@@Usage
//Prints values to a string.  The general syntax for its use is
//@[
//  y = sprintf(format,a1,a2,...).
//@]
//Here @|format| is the format string, which is a string that
//controls the format of the output.  The values of the variables
//@|a_i| are substituted into the output as required.  It is
//an error if there are not enough variables to satisfy the format
//string.  Note that this @|sprintf| command is not vectorized!  Each
//variable must be a scalar.  The returned value @|y| contains the
//string that would normally have been printed. For
//more details on the format string, see @|printf|.  
//@@Examples
//Here is an example of a loop that generates a sequence of files based on
//a template name, and stores them in a cell array.
//@<
//l = {}; for i = 1:5; s = sprintf('file_%d.dat',i); l(i) = {s}; end;
//l
//@>
//!
ArrayVector SprintfFunction(int nargout, const ArrayVector& arg) {
  if (arg.size() == 0)
    throw Exception("sprintf requires at least one (string) argument");
  Array format(arg[0]);
  if (!format.isString())
    throw Exception("sprintf format argument must be a string");
  char *op = xprintfFunction(nargout,arg);
  char *buff = (char*) malloc(strlen(op)+1);
  convertEscapeSequences(buff,op);
  Array outString(Array::stringConstructor(buff));
  free(op);
  free(buff);
  return singleArrayVector(outString);
}
  
//!
//@Module PRINTF Formated Output Function (C-Style)
//@@Section IO
//@@Usage
//Prints values to the output.  The general syntax for its use is
//@[
//  printf(format,a1,a2,...)
//@]
//Here @|format| is the format string, which is a string that
//controls the format of the output.  The values of the variables
//@|a_i| are substituted into the output as required.  It is
//an error if there are not enough variables to satisfy the format
//string.  Note that this @|printf| command is not vectorized!  Each
//variable must be a scalar.
//
//@@Format of the format string:
//
//The  format  string  is a character string, beginning and ending in its
//initial shift state, if any.  The format string is composed of zero  or
//more   directives:  ordinary  characters  (not  %),  which  are  copied
//unchanged to the output stream; and conversion specifications, each  of
//which results in fetching zero or more subsequent arguments.  Each 
//conversion specification is introduced by the character %, and ends with a
//conversion  specifier.  In between there may be (in this order) zero or
//more flags, an optional minimum field width, and an optional precision.
//
//The  arguments must correspond properly (after type promotion) with the
//conversion specifier, and are used in the order given.
//
//@@The flag characters:
//The character @|%| is followed by zero or more of the following flags:
//\begin{itemize}
//  \item @|\#|   The  value  should be converted to an ``alternate form''.  For @|o| conversions, the first character of the output  string  is  made  zero (by prefixing a @|0| if it was not zero already).  For @|x| and @|X| conversions, a nonzero result has the string @|'0x'| (or @|'0X'| for  @|X|  conversions) prepended to it.  For @|a, A, e, E, f, F, g,| and @|G|  conversions, the result will always  contain  a  decimal  point,  even  if  no digits follow it (normally, a decimal point appears  in the results of those conversions only if  a  digit  follows).  For @|g| and @|G| conversions, trailing zeros are not removed from the  result as they would otherwise be.  For other  conversions,  the  result is undefined.
//  \item @|0|   The value should be zero padded.  For @|d, i, o, u, x, X, a, A, e, E, f, F, g,| and @|G| conversions, the converted value is padded  on the  left  with  zeros rather than blanks.  If the @|0| and @|-| flags  both appear, the @|0| flag is ignored.  If  a  precision  is  given  with  a numeric conversion @|(d, i, o, u, x, and X)|, the @|0| flag is  ignored.  For other conversions, the behavior is undefined.
//  \item @|-|   The converted value is to be left adjusted on the  field  boundary.  (The default is right justification.) Except for @|n| conversions, the converted value is padded on the right  with  blanks, rather than on the left with blanks or zeros.  A @|-| overrides a @|0| if both are given.
//  \item @|' '| (a space) A blank should be left before a  positive  number  (or empty string) produced by a signed conversion.
//  \item @|+| A  sign  (@|+| or @|-|) always be placed before a number produced by a signed conversion.  By default a sign is used only for  negative numbers. A @|+| overrides a space if both are used.
//\end{itemize}
//@@The field width:
//An  optional decimal digit string (with nonzero first digit) specifying a 
//minimum field width.  If the converted  value  has  fewer  characters than 
//the  field  width,  it will be padded with spaces on the left (or right, 
//if the left-adjustment flag has been given).  A  negative  field width is 
//taken as a @|'-'| flag followed by a positive field  width. In no case does 
//a non-existent or small field width cause truncation of a field; if the 
//result of a conversion is wider than the  field  width, the field is 
//expanded to contain the conversion result.
//
//@@The precision:
//
//An  optional  precision,  in the form of a period (@|'.'|)  followed by an optional decimal digit string.  If the precision is given as just @|'.'|, or the precision is negative, the precision is  taken  to  be zero.   This  gives the minimum number of digits to appear for @|d, i, o, u, x|, and @|X| conversions, the number of digits to appear after the radix character  for  @|a, A, e, E, f|, and @|F| conversions, the maximum number of significant digits for @|g| and @|G| conversions, or the  maximum  number  of  characters to be printed from a string for s conversions.
//
//@@The conversion specifier:
//
//A character that specifies the type of conversion to be  applied.   The
//conversion specifiers and their meanings are:
//\begin{itemize}
//\item @|d,i|   The  int  argument is converted to signed decimal notation.  The  precision, if any, gives the minimum number of digits that  must   appear;  if  the  converted  value  requires fewer digits, it is    padded on the left with zeros. The default precision is @|1|.  When @|0|  is printed with an explicit precision @|0|, the output is empty.
//\item @|o,u,x,X|   The unsigned int argument is converted to  unsigned  octal  (@|o|),  unsigned  decimal  (@|u|),  or unsigned hexadecimal (@|x| and @|X|) notation.  The letters @|abcdef| are used for @|x| conversions;  the  letters @|ABCDEF| are used for @|X| conversions.  The precision, if any,  gives the minimum number of digits that must appear; if the converted  value  requires  fewer  digits, it is padded on the left  with zeros. The default precision is @|1|.  When @|0| is printed  with  an explicit precision @|0|, the output is empty.
//\item @|e,E|    The  double  argument  is  rounded  and  converted  in the style  @|[-]d.ddde dd| where there is one digit before  the  decimal-point  character and the number of digits after it is equal to the precision; if the precision is missing, it is taken as  @|6|;  if  the    precision  is  zero,  no  decimal-point character appears.  An @|E|  conversion uses the letter @|E| (rather than @|e|)  to  introduce  the  exponent.   The exponent always contains at least two digits; if  the value is zero, the exponent is @|00|.
//\item @|f,F|   The double argument is rounded and converted to decimal notation  in  the  style  @|[-]ddd.ddd|, where the number of digits after the decimal-point character is equal to the precision specification.  If  the precision is missing, it is taken as @|6|; if the precision  is explicitly zero, no decimal-point character  appears.   If  a   decimal point appears, at least one digit appears before it.
//\item @|g,G|   The double argument is converted in style @|f| or @|e| (or @|F| or @|E|  for  @|G|  conversions).  The precision specifies the number of significant digits.  If the precision is missing, @|6| digits  are  given;  if  the  precision is zero, it is treated as @|1|.  Style e is used   if the exponent from its conversion is less than @|-4|  or  greater than or equal to the precision.  Trailing zeros are removed from  the fractional part of the result; a decimal point appears  only  if it is followed by at least one digit.
//\item @|c| The int argument is  converted  to  an  unsigned  char, and  the resulting character is written.
//\item @|s| The string argument is printed.
//\item @|%|   A @|'%'| is written. No argument is converted. The complete conversion specification is @|'%%'|.
//\end{itemize}
//@@Example
//Here are some examples of the use of @|printf| with various arguments.  First we print out an integer and double value.
//@<
//printf('intvalue is %d, floatvalue is %f\n',3,1.53);
//@>
//Next, we print out a string value.
//@<
//printf('string value is %s\n','hello');
//@>
//Now, we print out an integer using 12 digits, zeros up front.
//@<
//printf('integer padded is %012d\n',32);
//@>
//Print out a double precision value with a sign, a total of 18 characters (zero prepended if necessary), a decimal point, and 12 digit precision.
//@<
//printf('float value is %+018.12f\n',pi);
//@>
//!
ArrayVector PrintfFunction(int nargout, const ArrayVector& arg, 
			   Interpreter* eval) {
  if (arg.size() == 0)
    throw Exception("printf requires at least one (string) argument");
  Array format(arg[0]);
  if (!format.isString())
    throw Exception("printf format argument must be a string");
  char *op = xprintfFunction(nargout,arg);
  char *buff = (char*) malloc(strlen(op)+1);
  convertEscapeSequences(buff,op);
  eval->outputMessage(buff);
  free(buff);
  free(op);
  return ArrayVector();
}

//!
//@Module FGETLINE Read a String from a File
//@@Section IO
//@@Usage
//Reads a string from a file.  The general syntax for its use
//is
//@[
//  s = fgetline(handle)
//@]
//This function reads characters from the file @|handle| into
//a @|string| array @|s| until it encounters the end of the file
//or a newline.  The newline, if any, is retained in the output
//string.  If the file is at its end, (i.e., that @|feof| would
//return true on this handle), @|fgetline| returns an empty
//string.
//@@Example
//First we write a couple of strings to a test file.
//@<
//fp = fopen('testtext','w');
//fprintf(fp,'String 1\n');
//fprintf(fp,'String 2\n');
//fclose(fp);
//@>
//Next, we read then back.
//@<
//fp = fopen('testtext','r')
//fgetline(fp)
//fgetline(fp)
//fclose(fp);
//@>
//!
ArrayVector FgetlineFunction(int nargout, const ArrayVector& arg) {
  if (arg.size() != 1)
    throw Exception("fgetline takes one argument, the file handle");
  Array tmp(arg[0]);
  int handle = tmp.getContentsAsIntegerScalar();
  FilePtr *fptr=(fileHandles.lookupHandle(handle+1));
  char buffer[65535];
  fgets(buffer,sizeof(buffer),fptr->fp);
  if (feof(fptr->fp))
    return singleArrayVector(Array::emptyConstructor());
  return singleArrayVector(Array::stringConstructor(buffer));
}

//!
//@Module STR2NUM Convert a String to a Number
//@@Section IO
//@@Usage
//Converts a string to a number.  The general syntax for its use
//is
//@[
//  x = str2num(string)
//@]
//Here @|string| is the data string, which contains the data to 
//be converted into a number.  The output is in double precision,
//and must be typecasted to the appropriate type based on what
//you need.
//!
ArrayVector Str2NumFunction(int nargout, const ArrayVector& arg) {
  if (arg.size() != 1)
    throw Exception("str2num takes a single argument, the string to convert into a number");
  Array data(arg[0]);
  if (!data.isString())
    throw Exception("the first argument to str2num must be a string");
  return ArrayVector() << Array::doubleConstructor(ArrayToDouble(data));
}

// How does sscanf work...
// a whitespace matches any number/type of whitespace
// a non-whitespace must be matched exactly
// a %d, %ld, etc.  reads a number... 
// a %s matches a sequence of characters that does
// not contain whitespaces...
// a mismatch - 

//!
//@Module SSCANF Formated String Input Function (C-Style)
//@@Section IO
//@@Usage
//Reads values from a string.  The general syntax for its use is
//@[
//  [a1,...,an] = sscanf(text,format)
//@]
//Here @|format| is the format string, which is a string that
//controls the format of the input.  Each value that is parsed
//from the @|text| occupies one output slot.  See @|printf|
//for a description of the format.
//!

class AutoFileCloser {
  FILE *fp;
public:
  AutoFileCloser(FILE *g) {fp = g;}
  ~AutoFileCloser() {fclose(fp);}
};

ArrayVector SscanfFunction(int nargout, const ArrayVector& arg) {
  if ((arg.size() != 2) || (!arg[0].isString()) || (!arg[1].isString()))
    throw Exception("sscanf takes two arguments, the text and the format string");
  Array text(arg[0]);
  Array format(arg[1]);
  string txt = text.getContentsAsString();
  FILE *fp = tmpfile();
  AutoFileCloser afc(fp);
  if (!fp)
    throw Exception("sscanf was unable to open a temp file (and so it won't work)");
  fprintf(fp,"%s",txt.c_str());
  rewind(fp);
  if (feof(fp))
    return singleArrayVector(Array::emptyConstructor());
  string frmt = format.getContentsAsString();
  char *buff = strdup(frmt.c_str());
  // Search for the start of a format subspec
  char *dp = buff;
  char *np;
  char sv;
  int nextArg = 2;
  bool shortarg;
  bool doublearg;
  // Scan the string
  ArrayVector values;
  while (*dp) {
    np = dp;
    while ((*dp) && (*dp != '%')) dp++;
    // Print out the formatless segment
    sv = *dp;
    *dp = 0;
    fscanf(fp,np);
    if (feof(fp))
      values.push_back(Array::emptyConstructor());
    *dp = sv;
    // Process the format spec
    if (*dp) {
      np = validateScanFormatSpec(dp+1);
      if (!np)
	throw Exception("erroneous format specification " + std::string(dp));
      else {
	if (*(np-1) == '%') {
	  fscanf(fp,"%%");
	  dp+=2;
	} else {
	  shortarg = false;
	  doublearg = false;
	  if (*(np-1) == 'h') {
	    shortarg = true;
	    np++;
	  } else if (*(np-1) == 'l') {
	    doublearg = true;
	    np++;
	  } 
	  sv = *np;
	  *np = 0;
	  switch (*(np-1)) {
	  case 'd':
	  case 'i':
	    if (shortarg) {
	      short sdumint;
	      if (feof(fp))
		values.push_back(Array::emptyConstructor());
	      else {
		fscanf(fp,dp,&sdumint);
		values.push_back(Array::int16Constructor(sdumint));
	      }
	    } else {
	      int sdumint;
	      if (feof(fp))
		values.push_back(Array::emptyConstructor());
	      else {
		fscanf(fp,dp,&sdumint);
		values.push_back(Array::int32Constructor(sdumint));
	      }
	    }
	    break;
	  case 'o':
	  case 'u':
	  case 'x':
	  case 'X':
	  case 'c':
	    if (shortarg) {
	      int sdumint;
	      if (feof(fp))
		values.push_back(Array::emptyConstructor());
	      else {
		fscanf(fp,dp,&sdumint);
		values.push_back(Array::int32Constructor(sdumint));
	      }
	    } else {
	      unsigned int dumint;
	      if (feof(fp))
		values.push_back(Array::emptyConstructor());
	      else {
		fscanf(fp,dp,&dumint);
		values.push_back(Array::uint32Constructor(dumint));
	      }
	    }
	    break;
	  case 'e':
	  case 'E':
	  case 'f':
	  case 'F':
	  case 'g':
	  case 'G':
	    if (doublearg) {
	      double dumfloat;
	      if (feof(fp))
		values.push_back(Array::emptyConstructor());
	      else {
		fscanf(fp,dp,&dumfloat);
		values.push_back(Array::doubleConstructor(dumfloat));
	      }
	    } else {
	      float dumfloat;
	      if (feof(fp))
		values.push_back(Array::emptyConstructor());
	      else {
		fscanf(fp,dp,&dumfloat);
		values.push_back(Array::floatConstructor(dumfloat));
	      }
	    }
	    break;
	  case 's':
	    char stbuff[4096];
	    if (feof(fp))
	      values.push_back(Array::emptyConstructor());
	    else {
	      fscanf(fp,dp,stbuff);
	      values.push_back(Array::stringConstructor(stbuff));
	    }
	    break;
	  default:
	    throw Exception("unsupported fscanf configuration");
	  }
	  *np = sv;
	  dp = np;
	}
      }
    }
  }
  free(buff);
  return values;
}

//!
//@Module FSCANF Formatted File Input Function (C-Style)
//@@Section IO
//@@Usage
//Reads values from a file.  The general syntax for its use is
//@[
//  [a1,...,an] = fscanf(handle,format)
//@]
//Here @|format| is the format string, which is a string that
//controls the format of the input.  Each value that is parsed from
//the file described by @|handle| occupies one output slot.
//See @|printf| for a description of the format.  Note that if
//the file is at the end-of-file, the fscanf will return 
//!
ArrayVector FscanfFunction(int nargout, const ArrayVector& arg) {
  if (arg.size() != 2)
    throw Exception("fscanf takes two arguments, the file handle and the format string");
  Array tmp(arg[0]);
  int handle = tmp.getContentsAsIntegerScalar();
  FilePtr *fptr=(fileHandles.lookupHandle(handle+1));
  Array format(arg[1]);
  if (!format.isString())
    throw Exception("fscanf format argument must be a string");
  if (feof(fptr->fp))
    return singleArrayVector(Array::emptyConstructor());
  string frmt = format.getContentsAsString();
  char *buff = strdup(frmt.c_str());
  // Search for the start of a format subspec
  char *dp = buff;
  char *np;
  char sv;
  int nextArg = 2;
  bool shortarg;
  bool doublearg;
  // Scan the string
  ArrayVector values;
  while (*dp) {
    np = dp;
    while ((*dp) && (*dp != '%')) dp++;
    // Print out the formatless segment
    sv = *dp;
    *dp = 0;
    fscanf(fptr->fp,np);
    if (feof(fptr->fp))
      values.push_back(Array::emptyConstructor());
    *dp = sv;
    // Process the format spec
    if (*dp) {
      np = validateScanFormatSpec(dp+1);
      if (!np)
	throw Exception("erroneous format specification " + std::string(dp));
      else {
	if (*(np-1) == '%') {
	  fscanf(fptr->fp,"%%");
	  dp+=2;
	} else {
	  shortarg = false;
	  doublearg = false;
	  if (*(np-1) == 'h') {
	    shortarg = true;
	    np++;
	  } else if (*(np-1) == 'l') {
	    doublearg = true;
	    np++;
	  } 
	  sv = *np;
	  *np = 0;
	  switch (*(np-1)) {
	  case 'd':
	  case 'i':
	    if (shortarg) {
	      short sdumint;
	      if (feof(fptr->fp))
		values.push_back(Array::emptyConstructor());
	      else {
		fscanf(fptr->fp,dp,&sdumint);
		values.push_back(Array::int16Constructor(sdumint));
	      }
	    } else {
	      int sdumint;
	      if (feof(fptr->fp))
		values.push_back(Array::emptyConstructor());
	      else {
		fscanf(fptr->fp,dp,&sdumint);
		values.push_back(Array::int32Constructor(sdumint));
	      }
	    }
	    break;
	  case 'o':
	  case 'u':
	  case 'x':
	  case 'X':
	  case 'c':
	    if (shortarg) {
	      int sdumint;
	      if (feof(fptr->fp))
		values.push_back(Array::emptyConstructor());
	      else {
		fscanf(fptr->fp,dp,&sdumint);
		values.push_back(Array::int32Constructor(sdumint));
	      }
	    } else {
	      unsigned int dumint;
	      if (feof(fptr->fp))
		values.push_back(Array::emptyConstructor());
	      else {
		fscanf(fptr->fp,dp,&dumint);
		values.push_back(Array::uint32Constructor(dumint));
	      }
	    }
	    break;
	  case 'e':
	  case 'E':
	  case 'f':
	  case 'F':
	  case 'g':
	  case 'G':
	    if (doublearg) {
	      double dumfloat;
	      if (feof(fptr->fp))
		values.push_back(Array::emptyConstructor());
	      else {
		fscanf(fptr->fp,dp,&dumfloat);
		values.push_back(Array::doubleConstructor(dumfloat));
	      }
	    } else {
	      float dumfloat;
	      if (feof(fptr->fp))
		values.push_back(Array::emptyConstructor());
	      else {
		fscanf(fptr->fp,dp,&dumfloat);
		values.push_back(Array::floatConstructor(dumfloat));
	      }
	    }
	    break;
	  case 's':
	    char stbuff[4096];
	    if (feof(fptr->fp))
	      values.push_back(Array::emptyConstructor());
	    else {
	      fscanf(fptr->fp,dp,stbuff);
	      values.push_back(Array::stringConstructor(stbuff));
	    }
	    break;
	  default:
	    throw Exception("unsupported fscanf configuration");
	  }
	  *np = sv;
	  dp = np;
	}
      }
    }
  }
  free(buff);
  return values;
}

//!
//@Module FPRINTF Formated File Output Function (C-Style)
//@@Section IO
//@@Usage
//Prints values to a file.  The general syntax for its use is
//@[
//  fprintf(fp,format,a1,a2,...).
//@]
//Here @|format| is the format string, which is a string that
//controls the format of the output.  The values of the variables
//@|ai| are substituted into the output as required.  It is
//an error if there are not enough variables to satisfy the format
//string.  Note that this @|fprintf| command is not vectorized!  Each
//variable must be a scalar.  The value @|fp| is the file handle.  For
//more details on the format string, see @|printf|.  Note also 
//that @|fprintf| to the file handle @|1| is effectively equivalent to @|printf|.
//@@Examples
//A number of examples are present in the Examples section of the @|printf| command.
//!
 ArrayVector FprintfFunction(int nargout, const ArrayVector& arg, 
			     Interpreter* eval) {
  if (arg.size() < 2)
    throw Exception("fprintf requires at least two arguments, the file handle and theformat string");
  Array tmp(arg[0]);
  int handle = tmp.getContentsAsIntegerScalar();
  if (handle == 1) {
    ArrayVector argCopy(arg);
    argCopy.pop_front();
    return PrintfFunction(nargout,argCopy,eval);
  }
  FilePtr *fptr=(fileHandles.lookupHandle(handle+1));
  Array format(arg[1]);
  if (!format.isString())
    throw Exception("fprintf format argument must be a string");
  ArrayVector argcopy(arg);
  argcopy.pop_front();
  char *op = xprintfFunction(nargout,argcopy);
  char *buff = (char*) malloc(strlen(op)+1);
  convertEscapeSequences(buff,op);
  fprintf(fptr->fp,"%s",buff);
  free(buff);
  free(op);
  return ArrayVector();
}

  
ArrayVector SaveNativeFunction(string filename, rvstring names, Interpreter* eval) {
  File ofile(filename,"wb");
  Serialize output(&ofile);
  output.handshakeServer();
  Context *cntxt = eval->getContext();
  for (int i=0;i<names.size();i++) {
    ArrayReference toWrite;
    char flags;
    if (!(names[i].compare("ans") == 0)) {
      toWrite = cntxt->lookupVariable(names[i]);
      if (!toWrite.valid())
	throw Exception(std::string("unable to find variable ")+
			names[i]+" to save to file "+filename);
      flags = 'n';
      if (cntxt->isVariableGlobal(names[i]))
	flags = 'g';
      if (cntxt->isVariablePersistent(names[i]))	
	flags = 'p';
      output.putString(names[i].c_str());
      output.putByte(flags);
      output.putArray(*toWrite);
    }
  }
  output.putString("__eof");
  return ArrayVector();
}
  
ArrayVector SaveASCIIFunction(string filename, rvstring names, bool tabsMode,
			      bool doubleMode, Interpreter* eval) {
  FILE *fp = fopen(filename.c_str(),"w");
  if (!fp) throw Exception("unable to open file " + filename + " for writing.");
  Context *cntxt = eval->getContext();
  for (int i=0;i<names.size();i++) {
    if (!(names[i] == "ans")) {
      ArrayReference toWrite = cntxt->lookupVariable(names[i]);
      if (!toWrite.valid())
	throw Exception("unable to find variable " + names[i] + 
			" to save to file "+filename);
      if (toWrite->isReferenceType()) {
	eval->warningMessage("variable " + names[i] + " is not numeric - cannot write it to an ASCII file");
	continue;
      }
      if (!toWrite->is2D()) {
	eval->warningMessage("variable " + names[i] + " is not 2D - cannot write it to an ASCII file");
	continue;
      }
      if (toWrite->isComplex()) 
	eval->warningMessage("variable " + names[i] + " is complex valued - only real part will be written to ASCII file");
      Array A(*toWrite);
      A.promoteType(FM_DOUBLE);
      int rows = A.rows();
      int cols = A.columns();
      double *dp = (double*) A.getDataPointer();
      for (int i=0;i<rows;i++) {
	for (int j=0;j<cols;j++) {
	  if (doubleMode)
	    fprintf(fp,"%.15e",dp[j*rows+i]);
	  else
	    fprintf(fp,"%.7e",dp[j*rows+i]);
	  if (tabsMode && (j < (cols-1)))
	    fprintf(fp,"\t");
	  else
	    fprintf(fp," ");
	}
	fprintf(fp,"\n");
      }
    }
  }
  fclose(fp);
  return ArrayVector();
}

//!
//@Module SAVE Save Variables To A File
//@@Section IO
//@@Usage
//Saves a set of variables to a file in a machine independent format.
//There are two formats for the function call.  The first is the explicit
//form, in which a list of variables are provided to write to the file:
//@[
//  save filename a1 a2 ...
//@]
//In the second form,
//@[
//  save filename
//@]
//all variables in the current context are written to the file.  The 
//format of the file is a simple binary encoding (raw) of the data
//with enough information to restore the variables with the @|load|
//command.  The endianness of the machine is encoded in the file, and
//the resulting file should be portable between machines of similar
//types (in particular, machines that support IEEE floating point 
//representation).
//
//You can also specify both the filename as a string, in which case
//you also have to specify the names of the variables to save.  In
//particular
//@[
//   save('filename','a1','a2')
//@]
//will save variables @|a1| and @|a2| to the file.
//
//Starting with version 2.0, FreeMat can also read and write MAT
//files (the file format used by MATLAB) thanks to substantial 
//work by Thomas Beutlich.  Support for MAT files in version 2.1
//has improved over previous versions.  In particular, classes
//should be saved properly, as well as a broader range of sparse
//matrices.  Compression is supported for both reading and writing
//to MAT files.  MAT file support is still in the alpha stages, so 
//please be cautious with using it to store critical 
//data.  The file format is triggered
//by the extension.  To save files with a MAT format, simply
//use a filename with a ".mat" ending.
//
//The @|save| function also supports ASCII output.  This is a very limited
//form of the save command - it can only save numeric arrays that are
//2-dimensional.  This form of the @|save| command is triggered using
//@[
//   save -ascii filename var1 var 2
//@]
//although where @|-ascii| appears on the command line is arbitrary (provided
//it comes after the @|save| command, of course).  Be default, the @|save|
//command uses an 8-digit exponential format notation to save the values to
//the file.  You can specify that you want 16-digits using the
//@[
//   save -ascii -double filename var1 var2
//@]
//form of the command.  Also, by default, @|save| uses spaces as the 
//delimiters between the entries in the matrix.  If you want tabs instead,
//you can use
//@[
//   save -ascii -tabs filename var1 var2
//@]
//(you can also use both the @|-tabs| and @|-double| flags simultaneously).
//
//Finally, you can specify that @|save| should only save variables that
//match a particular regular expression.  Any of the above forms can be
//combined with the @|-regexp| flag:
//@[
//   save filename -regexp pattern1 pattern2
//@]
//in which case variables that match any of the patterns will be saved.
//@@Example
//Here is a simple example of @|save|/@|load|.  First, we save some 
//variables to a file.
//@< 
//D = {1,5,'hello'};
//s = 'test string';
//x = randn(512,1);
//z = zeros(512);
//who
//save loadsave.dat
//@>
//Next, we clear the variables, and then load them back from the file.
//@<
//clear D s x z
//who
//load loadsave.dat
//who
//@>
//@@Tests
//@{ test_save1.m
//% Test the save and load capability with cell arrays (bug 1581481)
//function test_val = test_save1
//   a{1} = 'bert';
//   save tmp.mat a
//   b = a;
//   load tmp.mat
//   test_val = strcomp(a{1},b{1});
//@}
//!
ArrayVector SaveFunction(int nargout, const ArrayVector& arg, Interpreter* eval) {
  ArrayVector argCopy;
  if (arg.size() == 0) return ArrayVector();
  bool asciiMode = false;
  bool tabsMode = false;
  bool doubleMode = false;
  bool matMode = false;
  bool regexpMode = false;
  for (int i=0;i<arg.size();i++) {
    if (arg[i].isString()) {
      if (arg[i].getContentsAsStringUpper() == "-MAT")
	matMode = true;
      else if (arg[i].getContentsAsStringUpper() == "-ASCII")
	asciiMode = true;
      else if (arg[i].getContentsAsStringUpper() == "-REGEXP")
	regexpMode = true;
      else if (arg[i].getContentsAsStringUpper() == "-DOUBLE")
	doubleMode = true;
      else if (arg[i].getContentsAsStringUpper() == "-TABS")
	tabsMode = true;
      else
	argCopy << arg[i];
    } else
      argCopy << arg[i];
  }
  if (argCopy.size() < 1) throw Exception("save requires a filename argument");
  string fname(ArrayToString(argCopy[0]));
  if (!asciiMode && !matMode) {
    int len = fname.size();
    if ((len >= 4) && (fname[len-4] == '.') &&
	((fname[len-3] == 'M') || (fname[len-3] == 'm')) &&
	((fname[len-2] == 'A') || (fname[len-2] == 'a')) &&
	((fname[len-1] == 'T') || (fname[len-1] == 't'))) 
      matMode = true;
    if ((len >= 4) && (fname[len-4] == '.') &&
	((fname[len-3] == 'T') || (fname[len-3] == 't')) &&
	((fname[len-2] == 'X') || (fname[len-2] == 'x')) &&
	((fname[len-1] == 'T') || (fname[len-1] == 't'))) 
      matMode = true;    
  }
  rvstring names;
  for (int i=1;i<argCopy.size();i++) {
    if (!arg[i].isString())
      throw Exception("unexpected non-string argument to save command");
    names << ArrayToString(argCopy[i]);
  }
  Context *cntxt = eval->getContext();
  rvstring toSave;
  if (regexpMode || (names.size() == 0)) {
    stringVector allNames = cntxt->listAllVariables();
    for (int i=0;i<allNames.size();i++)
      if ((names.size() == 0) || contains(names,allNames[i],regexpMode))
	toSave << allNames[i];
  } else 
    toSave = names;
  if (matMode)
    return MatSaveFunction(fname,toSave,eval);
  else if (asciiMode)
    return SaveASCIIFunction(fname,toSave,tabsMode,doubleMode,eval);
  else
    return SaveNativeFunction(fname,toSave,eval);
}

//!
//@Module DLMREAD Read ASCII-delimited File
//@@Section IO
//@@Usage
//Loads a matrix from an ASCII-formatted text file with a delimiter
//between the entries.  This function is similar to the @|load -ascii|
//command, except that it can handle complex data, and it allows you
//to specify the delimiter.  Also, you can read only a subset of the
//data from the file.  The general syntax for the @|dlmread| function
//is
//@[
//    y = dlmread(filename)
//@]
//where @|filename| is a string containing the name of the file to read.
//In this form, FreeMat will guess at the type of the delimiter in the 
//file.  The guess is made by examining the input for common delimiter
//characters, which are @|,;:| or a whitespace (e.g., tab).  The text
//in the file is preprocessed to replace these characters with whitespace
//and the file is then read in using a whitespace for the delimiter.
//
//If you know the delimiter in the file, you can specify it using
//this form of the function:
//@[
//    y = dlmread(filename, delimiter)
//@]
//where @|delimiter| is a string containing the delimiter.  If @|delimiter|
//is the empty string, then the delimiter is guessed from the file.
//
//You can also read only a portion of the file by specifying a start row
//and start column:
//@[
//    y = dlmread(filename, delimiter, startrow, startcol)
//@]
//where @|startrow| and @|startcol| are zero-based.  You can also specify
//the data to read using a range argument:
//@[
//    y = dlmread(filename, delimiter, range)
//@]
//where @|range| is either a vector @|[startrow,startcol,stoprow,stopcol]|
//or is specified in spreadsheet notation as @|B4..ZA5|.
//
//Note that complex numbers can be present in the file if they are encoded
//without whitespaces inside the number, and use either @|i| or @|j| as 
//the indicator.  Note also that when the delimiter is given, each incidence
//of the delimiter counts as a separator.  Multiple separators generate
//zeros in the matrix.
//!
static int ParseNumber(QString tx) {
  int lookahead = 0;
  int len = 0;
  if ((tx[len] == '+') || (tx[len] == '-'))
    len++;
  lookahead = len;
  while (tx[len].isDigit()) len++;
  lookahead = len;
  if (tx[lookahead] == '.') {
    lookahead++;
    len = 0;
    while (tx[len+lookahead].isDigit()) len++;
    lookahead+=len;
  }
  if ((tx[lookahead] == 'E') || (tx[lookahead] == 'e')) {
    lookahead++;
    if ((tx[lookahead] == '+') || (tx[lookahead] == '-')) {
      lookahead++;
    }
    len = 0;
    while (tx[len+lookahead].isDigit()) len++;
    lookahead+=len;
  }
  return lookahead;
}

static void ParseComplexValue(QString tx, double &real, double &imag) {
  int lnum = ParseNumber(tx);
  int rnum = ParseNumber(tx.mid(lnum));
  QString num1 = tx.left(lnum);
  QString num2 = tx.mid(lnum,rnum);
  if (num1.isEmpty() && num2.isEmpty()) {
    real = 0; imag = 1;
    return;
  }
  if (num1 == "+") num1 = "+1";
  if (num2 == "+") num2 = "+1";
  if (num1 == "-") num1 = "-1";
  if (num2 == "-") num2 = "-1";
  if (num2.isEmpty()) {
    real = 0;
    imag = num1.toDouble();
  } else {
    real = num1.toDouble();
    imag = num2.toDouble();
  }
}

int DecodeSpreadsheetColumn(QString tx) {
  tx.toUpper();
  QByteArray txb(tx.toLatin1());
  for (int i=0;i<txb.count();i++) 
    txb[i] = txb[i] - 'A';
  int ret = 0;
  for (int i=0;i<txb.count();i++) 
    ret += (int) txb.at(i)*pow(26.0,txb.count()-1-i);
  return ret;
}

void DecodeSpreadsheetRange(QString tx, int &startrow, int &startcol,
			    int &stoprow, int &stopcol) {
  QString colstart;
  QString rowstart;
  QString colstop;
  QString rowstop;
  while (tx.at(0).isLetter()) {
    colstart += tx.left(1);
    tx = tx.mid(1);
  }
  while (tx.at(0).isDigit()) {
    rowstart += tx.left(1);
    tx = tx.mid(1);
  }
  tx = tx.mid(1);
  tx = tx.mid(1);
  while (tx.at(0).isLetter()) {
    colstop += tx.left(1);
    tx = tx.mid(1);
  }
  while (tx.at(0).isDigit()) {
    rowstop += tx.left(1);
    tx = tx.mid(1);
  }
  startrow = rowstart.toInt()-1;
  stoprow = rowstop.toInt()-1;
  startcol = DecodeSpreadsheetColumn(colstart);
  stopcol = DecodeSpreadsheetColumn(colstop);
}

ArrayVector DlmReadFunction(int nargout, const ArrayVector& arg) {
  if (arg.size() == 0) 
    throw Exception("dlmread expects a filename");
  string filename(ArrayToString(arg[0]));
  QFile ifile(QString::fromStdString(filename));
  if (!ifile.open(QFile::ReadOnly))
    throw Exception("filename " + filename + " could not be opened");
  bool no_delimiter = true;
  string delimiter;
  if (arg.size() >= 2) {
    delimiter = ArrayToString(arg[1]);
    no_delimiter = (delimiter.size() == 0);
  }
  int col_count = 0;
  int row_count = 0;
  QList<QList<double> > data_real;
  QList<QList<double> > data_imag;
  QTextStream str(&ifile);
  while (!str.atEnd()) {
    QString line = str.readLine(0);
    QStringList elements;
    if (no_delimiter) {
      if (line.contains(QRegExp("[,;:]")))
	elements = line.split(QRegExp("[,;:]"));
      else {
	line = line.simplified();
	elements = line.split(' ');
      }
    } else {
      elements = line.split(QString::fromStdString(delimiter)[0]);
    }
    QList<double> row_data_real;
    QList<double> row_data_imag;
    row_count++;
    for (int i=0;i<elements.size();i++) {
      QString element(elements[i]);
      element.replace(" ","");
      if (element.contains('i') || element.contains('I') ||
	  element.contains('j') || element.contains('J')) {
	double real, imag;
	ParseComplexValue(element,real,imag);
	row_data_real << real;
	row_data_imag << imag;
      } else {
	row_data_real << element.toDouble();
	row_data_imag << 0;
      }
    }
    col_count = qMax(col_count,elements.size());
    data_real << row_data_real;
    data_imag << row_data_imag;
  }
  int startrow = 0;
  int startcol = 0;
  int stoprow = row_count-1;
  int stopcol = col_count-1;
  if (arg.size() == 4) {
    startrow = ArrayToInt32(arg[2]);
    startcol = ArrayToInt32(arg[3]);
  } else if (arg.size() == 3) {
    if (arg[2].isVector() && (arg[2].getLength() == 4)) {
      Array range(arg[2]);
      range.promoteType(FM_INT32);
      const int32*dp = (const int32*) range.getDataPointer();
      startrow = dp[0];
      startcol = dp[1];
      stoprow = dp[2];
      stopcol = dp[3];
    } else if (arg[2].isString()) 
      DecodeSpreadsheetRange(QString::fromStdString(ArrayToString(arg[2])),
			     startrow,startcol,stoprow,stopcol);
    else
      throw Exception("Unable to decode the range arguments to the dlmread function");
  }
  startrow = qMax(0,qMin(row_count-1,startrow));
  startcol = qMax(0,qMin(col_count-1,startcol));
  stoprow = qMax(0,qMin(row_count-1,stoprow));
  stopcol = qMax(0,qMin(col_count-1,stopcol));
  int numrows = stoprow-startrow+1;
  int numcols = stopcol-startcol+1;
  bool anyNonzeroImaginary = false;
  for (int i=startrow;i<=stoprow;i++) 
    for (int j=0;j<=qMin(data_real[i].size()-1,stopcol);j++) 
      if (data_imag[i][j] != 0) anyNonzeroImaginary = true;
  Array A;
  if (!anyNonzeroImaginary) {
    A = Array::doubleMatrixConstructor(numrows,numcols);
    double *dp = (double*) A.getReadWriteDataPointer();
    for (int i=startrow;i<=stoprow;i++)
      for (int j=startcol;j<=stopcol;j++)
	if (j <= (data_real[i].size()-1))
	  dp[i-startrow+(j-startcol)*numrows] = data_real[i][j];
  } else {
    A = Array::dcomplexMatrixConstructor(numrows,numcols);
    double *dp = (double*) A.getReadWriteDataPointer();
    for (int i=startrow;i<=stoprow;i++)
      for (int j=startcol;j<=stopcol;j++)
	if (j <= (data_real[i].size()-1)) {
	  dp[2*(i-startrow+(j-startcol)*numrows)] = data_real[i][j];
	  dp[2*(i-startrow+(j-startcol)*numrows)+1] = data_imag[i][j];
	}
  }
  return ArrayVector() << A;
}

ArrayVector LoadASCIIFunction(int nargout, string filename, Interpreter* eval) {
  // Hmmm...
  QFile ifile(QString::fromStdString(filename));
  if (!ifile.open(QFile::ReadOnly))
    throw Exception("filename " + filename + " could not be opened");
  QTextStream str(&ifile);
  int i=0;
  int col_count = 0;
  int row_count = 0;
  QList<double> data;
  bool evenData = true;
  while (!str.atEnd() && evenData) {
    QString line = str.readLine(0);
    line = line.simplified();
    QStringList elements(line.split(' '));
    if (row_count == 0) 
      col_count = elements.size();
    else if (elements.size() != col_count)
      evenData = false;
    if (evenData) {
      row_count++;
      for (i=0;i<elements.size();i++) 
	data << elements[i].toDouble();
    }
  }
  if (!evenData)
    eval->warningMessage("data in ASCII file does not have a uniform number of entries per row");
  // Now construct the matrix
  Array A;
  if ((row_count > 0) && (col_count > 0)) {
    A = Array::doubleMatrixConstructor(row_count,col_count);
    double *dp = (double *) A.getDataPointer();
    for (int r=0;r<row_count;r++) 
      for (int c=0;c<col_count;c++) 
	dp[r+c*row_count] = data.at(r*col_count+c);
  }
  if (nargout == 1)
    return ArrayVector() << A;
  else {
    QFileInfo fi(QString::fromStdString(filename));
    eval->getContext()->insertVariable(fi.baseName().toStdString(),A);
  }
  return ArrayVector();
}

ArrayVector LoadNativeFunction(int nargout, string filename,
			       rvstring names, bool regexpmode, Interpreter* eval) {
  File ofile(filename,"rb");
  Serialize input(&ofile);
  input.handshakeClient();
  string arrayName = input.getString();
  rvstring fieldnames;
  ArrayVector fieldvalues;
  while (arrayName != "__eof") {
    Array toRead;
    char flag;
    flag = input.getByte();
    input.getArray(toRead);
    if ((names.size() == 0) || 
	(contains(names,arrayName,regexpmode) && (nargout == 0))) {
      switch (flag) {
      case 'n':
	break;
      case 'g':
	eval->getContext()->addGlobalVariable(arrayName);
	break;
      case 'p':
	eval->getContext()->addPersistentVariable(arrayName);
	break;
      default:
	throw Exception(std::string("unrecognized variable flag ") + flag + 
			std::string(" on variable ") + arrayName);
      }
      eval->getContext()->insertVariable(arrayName,toRead);
    } else {
      fieldnames << arrayName;
      fieldvalues << toRead;
    }
    arrayName = input.getString();
  }
  if (nargout == 0)
    return ArrayVector();
  else
    return ArrayVector() <<
      Array::structConstructor(fieldnames,fieldvalues);
}

//!
//@Module LOAD Load Variables From A File
//@@Section IO
//@@Usage
//Loads a set of variables from a file in a machine independent format.
//The @|load| function takes one argument:
//@[
//  load filename,
//@]
//or alternately,
//@[
//  load('filename')
//@]
//This command is the companion to @|save|.  It loads the contents of the
//file generated by @|save| back into the current context.  Global and 
//persistent variables are also loaded and flagged appropriately.  By
//default, FreeMat assumes that files that end in a @|.mat| or @|.MAT|
//extension are MATLAB-formatted files.  Also, FreeMat assumes that 
//files that end in @|.txt| or @|.TXT| are ASCII files. 
//For other filenames, FreeMat first tries to open the file as a 
//FreeMat binary format file (as created by the @|save| function).  
//If the file fails to open as a FreeMat binary file, then FreeMat 
//attempts to read it as an ASCII file.  
//
//You can force FreeMat to assume a particular format for the file
//by using alternate forms of the @|load| command.  In particular,
//@[
//  load -ascii filename
//@]
//will load the data in file @|filename| as an ASCII file (space delimited
//numeric text) loaded into a single variable in the current workspace
//with the name @|filename| (without the extension).
//
//For MATLAB-formatted data files, you can use
//@[
//  load -mat filename
//@]
//which forces FreeMat to assume that @|filename| is a MAT-file, regardless
//of the extension on the filename.
//
//You can also specify which variables to load from a file (not from 
//an ASCII file - only single 2-D variables can be successfully saved and
//retrieved from ASCII files) using the additional syntaxes of the @|load|
//command.  In particular, you can specify a set of variables to load by name
//@[
//  load filename Var_1 Var_2 Var_3 ...
//@]
//where @|Var_n| is the name of a variable to load from the file.  
//Alternately, you can use the regular expression syntax
//@[
//  load filename -regexp expr_1 expr_2 expr_3 ...
//@]
//where @|expr_n| is a regular expression (roughly as expected by @|regexp|).
//Note that a simpler regular expression mechanism is used for this syntax
//than the full mechanism used by the @|regexp| command.
//
//Finally, you can use @|load| to create a variable containing the 
//contents of the file, instead of automatically inserting the variables
//into the curent workspace.  For this form of @|load| you must use the
//function syntax, and capture the output:
//@[
//  V = load('arg1','arg2',...)
//@]
//which returns a structure @|V| with one field for each variable
//retrieved from the file.  For ASCII files, @|V| is a double precision
//matrix.
//
//@@Example
//Here is a simple example of @|save|/@|load|.  First, we save some variables to a file.
//@<
//D = {1,5,'hello'};
//s = 'test string';
//x = randn(512,1);
//z = zeros(512);
//who
//save loadsave.dat
//@>
//Next, we clear the variables, and then load them back from the file.
//@<
//clear D s x z
//who
//load loadsave.dat
//who
//@>
//!
ArrayVector LoadFunction(int nargout, const ArrayVector& arg, 
			 Interpreter* eval) {
  // Process the arguments to extract the "-mat", "-ascii" and "-regexp" 
  // flags.
  ArrayVector argCopy;
  if (arg.size() == 0) return ArrayVector();
  bool asciiMode = false;
  bool matMode = false;
  bool regexpMode = false;
  for (int i=0;i<arg.size();i++) {
    if (arg[i].isString()) {
      if (arg[i].getContentsAsStringUpper() == "-MAT")
	matMode = true;
      else if (arg[i].getContentsAsStringUpper() == "-ASCII")
	asciiMode = true;
      else if (arg[i].getContentsAsStringUpper() == "-REGEXP")
	regexpMode = true;
      else
	argCopy << arg[i];
    } else
      argCopy << arg[i];
  }
  if (argCopy.size() < 1)  throw Exception("load requries a filename argument");
  string fname(ArrayToString(argCopy[0]));
  // If one of the filemode flags has not been specified, try to 
  // guess if it is an ASCII file or a MAT file
  if (!matMode && !asciiMode) {
    int len = fname.size();
    if ((len >= 4) && (fname[len-4] == '.') &&
	((fname[len-3] == 'M') || (fname[len-3] == 'm')) &&
	((fname[len-2] == 'A') || (fname[len-2] == 'a')) &&
	((fname[len-1] == 'T') || (fname[len-1] == 't'))) {
      matMode = true;
    } else  if ((len >= 4) && (fname[len-4] == '.') &&
		((fname[len-3] == 'T') || (fname[len-3] == 't')) &&
		((fname[len-2] == 'X') || (fname[len-2] == 'x')) &&
		((fname[len-1] == 'T') || (fname[len-1] == 't'))) {
      asciiMode = true;
    } else {
      // Could be an ASCII file - try to open it native
      try {
	File of(fname,"rb");
	Serialize input(&of);
	input.handshakeClient();
      } catch(Exception& e) {
	asciiMode = true;
      }
    }
  }
  rvstring names;
  for (int i=1;i<argCopy.size();i++) {
    if (!arg[i].isString())
      throw Exception("unexpected non-string argument to load command");
    names << ArrayToString(argCopy[i]);
  }
  // Read the data file using the appropriate handler
  try {
    if (matMode)
      return MatLoadFunction(nargout,fname,names,regexpMode,eval);
    else if (asciiMode)
      return LoadASCIIFunction(nargout,fname,eval);
    else
      return LoadNativeFunction(nargout,fname,names,regexpMode,eval);
  } catch (Exception& e) {
    throw Exception("unable to read data from file " + fname + " - it may be corrupt, or FreeMat may not understand the format.  See help load for more information.  The specific error was: " + e.getMessageCopy());
  }
  return ArrayVector();
}


syntax highlighted by Code2HTML, v. 0.9.1