/*
 * Copyright (c) 2002-2006 Samit Basu
 *
 * 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 "Interpreter.hpp"
#include "Malloc.hpp"
#include "PathSearch.hpp"
#include "IEEEFP.hpp"
#include "Sparse.hpp"
#include "helpwidget.hpp"
#include <algorithm>
#include "Editor.hpp"
#include "PathTool.hpp"
#include <QtCore>
#include <QtGui>
#include "Module.hpp"
#include "MemPtr.hpp"

static std::string helppath;
  
void Tokenize(const std::string& str, std::vector<std::string>& tokens,
	      const std::string& delimiters = " \n") {
  // Skip delimiters at beginning.
  std::string::size_type lastPos = str.find_first_not_of(delimiters, 0);
  // Find first "non-delimiter".
  std::string::size_type pos     = str.find_first_of(delimiters, lastPos);
    
  while (std::string::npos != pos || std::string::npos != lastPos)
    {
      // Found a token, add it to the vector.
      tokens.push_back(str.substr(lastPos, pos - lastPos));
      // Skip delimiters.  Note the "not_of"
      lastPos = str.find_first_not_of(delimiters, pos);
      // Find next "non-delimiter"
      pos = str.find_first_of(delimiters, lastPos);
    }
}

//!
//@Module JITCONTROL Control the Just In Time Compiler
//@@Section FREEMAT
//@@Usage
//The @|jitcontrol| functionality in FreeMat allows you to control
//the use of the Just In Time (JIT) compiler.  
//!
ArrayVector JITControlFunction(int nargout, const ArrayVector& arg, Interpreter* eval) {
  if (arg.size() < 1) {
    if (eval->JITControl())
      return singleArrayVector(Array::stringConstructor("on"));
    else
      return singleArrayVector(Array::stringConstructor("off"));
  } else {
    if (!arg[0].isString())
      throw Exception("jitcontrol function takes only a single, string argument");
    string txt = arg[0].getContentsAsStringUpper();
    if (txt == "ON")
      eval->JITControl(true);
    else if (txt == "OFF")
      eval->JITControl(false);
    else
      throw Exception("jitcontrol function argument needs to be 'on/off'");
  }
  return ArrayVector();
}

//!
//@Module DBAUTO Control Dbauto Functionality
//@@Section DEBUG
//@@Usage
//The dbauto functionality in FreeMat allows you to debug your
//FreeMat programs.  When @|dbauto| is @|on|, then any error
//that occurs while the program is running causes FreeMat to 
//stop execution at that point and return you to the command line
//(just as if you had placed a @|keyboard| command there).  You can
//then examine variables, modify them, and resume execution using
//@|return|.  Alternately, you can exit out of all running routines
//via a @|retall| statement.  Note that errors that occur inside of
//@|try|/@|catch| blocks do not (by design) cause auto breakpoints.  The
//@|dbauto| function toggles the dbauto state of FreeMat.  The
//syntax for its use is
//@[
//   dbauto(state)
//@]
//where @|state| is either
//@[
//   dbauto('on')
//@]
//to activate dbauto, or
//@[
//   dbauto('off')
//@]
//to deactivate dbauto.  Alternately, you can use FreeMat's string-syntax
//equivalence and enter
//@[
//   dbauto on
//@]
//or 
//@[
//   dbauto off
//@]
//to turn dbauto on or off (respectively).  Entering @|dbauto| with no arguments
//returns the current state (either 'on' or 'off').
//!
ArrayVector DbAutoFunction(int nargout, const ArrayVector& arg, Interpreter* eval) {
  if (arg.size() < 1) {
    if (eval->AutoStop()) 
      return singleArrayVector(Array::stringConstructor("on"));
    else 
      return singleArrayVector(Array::stringConstructor("off"));
  } else {
    if (!arg[0].isString())
      throw Exception("dbauto function takes only a single, string argument");
    string txt = arg[0].getContentsAsStringUpper();
    if (txt == "ON")
      eval->AutoStop(true);
    else if (txt == "OFF")
      eval->AutoStop(false);
    else
      throw Exception("dbauto function argument needs to be 'on/off'");
  }
  return ArrayVector();
}

bool inBundleMode() {
  QDir dir(QApplication::applicationDirPath());
  dir.cdUp();
  return (dir.dirName() == "Contents");
}

//!
//@Module HELPWIN Online Help Window
//@@Section FREEMAT
//@@Usage
//Brings up the online help window with the FreeMat manual.  The
//@|helpwin| function takes no arguments:
//@[
//  helpwin
//@]
//!
ArrayVector HelpWinFunction(int nargout, const ArrayVector& arg, Interpreter* eval) {
  QDir dir;
  if (inBundleMode()) {
    dir = QDir(QString(qApp->applicationDirPath() + "/../Resources/help/html"));
  } else {
    QSettings settings("FreeMat","FreeMat");
    dir = QDir(QString(settings.value("root", RESOURCEDIR).toString())+"/help/html");
  }
  HelpWindow *m_helpwin = new HelpWindow(dir.canonicalPath());
  m_helpwin->show();
  return ArrayVector();
}


ArrayVector EndFunction(int nargout, const ArrayVector& arg) {
  if (arg.size() != 3)
    throw Exception("End function requires 3 arguments, the array, the end index, and the number of subindexes");
  Dimensions t(arg[0].dimensions());
  int enddim(ArrayToInt32(arg[1]));
  int totalndxs(ArrayToInt32(arg[2]));
  if (totalndxs == 1)
    return singleArrayVector(Array::int32Constructor(t.getElementCount()));
  return singleArrayVector(Array::int32Constructor(t.get(enddim-1)));
}

//!
//@Module PATHTOOL Open Path Setting Tool
//@@Section FREEMAT
//@@Usage
//Brings up the pathtool dialog.  The @|pathtool| function takes
//no arguments:
//@[
//  pathtool
//@]
//!
ArrayVector PathToolFunction(int nargout, const ArrayVector& arg, Interpreter* eval) {
  PathTool p;
  p.exec();
  QSettings settings("FreeMat","FreeMat");
  QStringList userPath = settings.value("interpreter/path").toStringList();
  eval->setUserPath(userPath);
  eval->rescanPath();
  return ArrayVector();
}

//convert the supplied string to a decimal integer
static double hex2dec_helper (string t) {
  return (double) strtoll(t.c_str(),NULL,16);
}

//!
//@Module HEX2DEC Convert Hexadecimal Numbers To Decimal
//@@Section ELEMENTARY
//@@Usage
//Converts a hexadecimal number (encoded as a string matrix) into integers.
//The syntax for its use is
//@[
//   y = hex2dec(x)
//@]
//where @|x| is a character matrix where each row represents an integer
//in hexadecimal form.  The output is of type @|FM_DOUBLE|.
//@@Examples
//@<
//hex2dec('3ff')
//@>
//Or for a more complex example
//@<
//hex2dec(['0ff';'2de';'123'])
//@>
//!
ArrayVector Hex2DecFunction(int nargout, const ArrayVector& arg) {
  if (arg.size() == 0)
    throw Exception("hex2dec requires an argument");
  Array x(arg[0]);
  if (x.dataClass() != FM_STRING)
    throw Exception("hex2dec argument must be a string");
  if (x.isVector()) {
    string str(ArrayToString(x));
    return ArrayVector() << Array::doubleConstructor(hex2dec_helper(str));
  } else {
    // Create a buffer to hold the string
    int numrows = x.rows();
    int numcolumns = x.columns();
    MemBlock<uint8> p(numcolumns+1);
    uint8 *b = &p;
    // How many strings to convert?
    const uint8* dp = (const uint8*) x.getDataPointer();
    double *qp = (double*) Array::allocateArray(FM_DOUBLE,numrows);
    for (int i=0;i<numrows;i++) {
      for (int j=0;j<numcolumns;j++)
	b[j] = dp[i+j*numrows];
      qp[i] = hex2dec_helper((char*)b);
    }
    return ArrayVector() << Array(FM_DOUBLE,Dimensions(numrows,1),qp);
  }
  return ArrayVector();
}

//!
//@Module DEC2HEX Convert Decimal Number to Hexadecimal
//@@Section ELEMENTARY
//@@Usage
//Converts an integer value into its hexadecimal representation.  The syntax
//for its use is
//@[
//   y = dec2hex(x)
//@]
//where @|x| is an integer (and is promoted to a 64-bit integer if it is not).
//The returned value @|y| is a string containing the hexadecimal representation
//of that integer.  If you require a minimum length for the hexadecimal
//representation, you can specify an optional second argument
//@[
//   y = dec2hex(x,n)
//@]
//where @|n| indicates the minimum number of digits in the representation.
//@@Example
//Here are some simple examples:
//@<
//dec2hex(1023)
//@>
//@<
//dec2hex(58128493)
//@>
//@@Tests
//@$"y=dec2hex(1023)","'3ff'","exact"
//@$"y=dec2hex(16,4)","'0010'","exact"
//!
ArrayVector Dec2HexFunction(int nargout, const ArrayVector& arg) {
  char buffer[1000];
  char fbuffer[1000];
  if (arg.size() < 1)
    throw Exception("dec2hex requires at least one argument");
  Array x(arg[0]);
  if (!x.isScalar())
    throw Exception("dec2hex requires first argument to be a scalar");
  x.promoteType(FM_INT64);
  const int64 *dp = (const int64*) x.getDataPointer();
  int64 xval = dp[0];
  sprintf(buffer,"%llx",xval);
  string retString(buffer);
  if (arg.size() == 1) {
    return ArrayVector() << Array::stringConstructor(retString);
  } else {
    int n = ArrayToInt32(arg[1]);
    if ((n < 1) || (n > 32))
      throw Exception("illegal number of digits requested in dec2hex function");
    while (retString.size() < n)
      retString = '0' + retString;
    return ArrayVector() << Array::stringConstructor(retString);
  }
  return ArrayVector();
}


//!
//@Module NUM2HEX Convert Numbers to IEEE Hex Strings
//@@Section ELEMENTARY
//@@Usage
//Converts single and double precision arrays to IEEE hex strings.  The
//syntax for its use is
//@[
//   y = num2hex(x)
//@]
//where @|x| is either a @|float| or @|double| array.  The output @|y| is
//a @|n-by-p| character array, where @|n| is the number of elements in @|x|,
//and @|p| is 16 for @|double| arrays, and 8 for @|single| arrays.
//@@Example
//Some interesting numbers
//@<
//num2hex([1 0 0.1 -pi inf nan])
//@>
//The same in single precision
//@<
//num2hex(float([1 0 0.1 -pi inf nan]))
//@>
//@@Tests
//@$"y=num2hex(-pi)","'c00921fb54442d18'","exact"
//@$"y=num2hex(.1f)","'3dcccccd'","exact"
//!
ArrayVector Num2HexFunction(int nargout, const ArrayVector& arg) {
  if (arg.size() != 1)
    throw Exception("num2hex fucntion requires a single argument");
  Array x(arg[0]);
  if ((x.dataClass() != FM_FLOAT) && (x.dataClass() != FM_DOUBLE))
    throw Exception("num2hex only works on float and double arrays");
  int len(x.getLength());
  if (x.dataClass() == FM_FLOAT) {
    uint8* dp = (uint8*) Array::allocateArray(FM_STRING,len*8);
    const float *xp = (const float *) x.getDataPointer();
    for (int i=0;i<len;i++) {
      char buffer[10];
      ToHexString(xp[i],buffer);
      for (int j=0;j<8;j++)
	dp[i+len*j] = buffer[j];
    }
    return ArrayVector() << Array(FM_STRING,Dimensions(len,8),dp,false);
  } else {
    uint8* dp = (uint8*) Array::allocateArray(FM_STRING,len*16);
    const double *xp = (const double *) x.getDataPointer();
    for (int i=0;i<len;i++) {
      char buffer[18];
      ToHexString(xp[i],buffer);
      for (int j=0;j<16;j++)
	dp[i+len*j] = buffer[j];
    }
    return ArrayVector() << Array(FM_STRING,Dimensions(len,16),dp,false);
  }
  return ArrayVector();
}

//!
//@Module HELP Help
//@@Section FREEMAT
//@@Usage
//Displays help on a function available in FreeMat.  The help function
//takes one argument:
//@[
//  help topic
//@]
//where @|topic| is the topic to look for help on.  For scripts, the 
//result of running @|help| is the contents of the comments at the top
//of the file.  If FreeMat finds no comments, then it simply displays
//the function declaration.
//!
ArrayVector HelpFunction(int nargout, const ArrayVector& arg, Interpreter* eval)
{
  PathSearcher psearch(eval->getTotalPath());

  if (arg.size() != 1)
    throw Exception("help function requires a single argument (the function or script name)");
  Array singleArg(arg[0]);
  string fname = singleArg.getContentsAsString();
  bool isFun;
  FuncPtr val;
  isFun = eval->getContext()->lookupFunction(fname,val);
  if (isFun && (val->type() == FM_M_FUNCTION)) {
    MFunctionDef *mptr;
    mptr = (MFunctionDef *) val;
    mptr->updateCode(eval);
    for (int i=0;i<mptr->helpText.size();i++)
      eval->outputMessage(mptr->helpText[i]);
    return ArrayVector();
  } else {
    // Check for a mdc file with the given name
    std::string mdcname;
    mdcname = fname + ".mdc";
    try {
      mdcname = psearch.ResolvePath(mdcname);
    } catch (Exception& e) {
      throw Exception("no help available on " + fname);
    }
    FILE *fp;
    fp = fopen(mdcname.c_str(),"r");
    if (fp) {
      //Found it... relay to the output
      std::vector<std::string> helplines;
      std::string workingline;
      char buffer[4096];
      while (!feof(fp)) {
	fgets(buffer,sizeof(buffer),fp);
	//is this line only a $ sign?
	helplines.push_back(buffer);
      }
      // Write the lines out...
      // Get the output width (in characters)
      int outputWidth = eval->getTerminalWidth() - 20;
      for (int p=0;p<helplines.size();p++) {
	std::vector<std::string> tokens;
	// Tokenize the help line
	Tokenize(helplines[p],tokens);
	// Output words..
	int outlen = 0;
	int tokencount = 0;
	eval->outputMessage("\n          ");
	while ((tokens.size() > 0) && (tokencount < tokens.size())) {
	  // Can the next token be output without wrapping?
	  int tsize;
	  tsize = tokens[tokencount].size();
	  if ((outlen == 0) || ((outlen + tsize) < outputWidth)) {
	    // Yes... send it and move on
	    eval->outputMessage(tokens[tokencount]);
	    eval->outputMessage(" ");
	    outlen += tokens[tokencount++].size()+1;
	  } else {
	    eval->outputMessage("\n          ");
	    outlen = 0;
	  }
	}
      }
      fclose(fp);
    }
    return ArrayVector();
  }
  throw Exception("no help for that topic");
}

//!
//@Module CLEAR Clear or Delete a Variable
//@@Section INSPECTION
//@@Usage
//Clears a set of variables from the current context, or alternately, 
//delete all variables defined in the current context.  There are
//several formats for the function call.  The first is the explicit form
//in which a list of variables are provided:
//@[
//   clear a1 a2 ...
//@]
//The variables can be persistent or global, and they will be deleted.
//The second form
//@[
//   clear 'all'
//@]
//clears all variables and libraries from the current context.  Alternately, you can
//use the form:
//@[
//   clear 'libs'
//@]
//which will unload any libraries or DLLs that have been @|import|ed. 
//Optionally, you can specify that persistent variables should be cleared via:
//@[
//   clear 'persistent'
//@]
//and similarly for global variables:
//@[
//   clear 'global'
//@]
//You can use
//@[
//   clear 'classes'
//@]
//to clear all definitions of user-defined classes.
//With no arguments, @|clear| defaults to clearing @|'all'|.
//@@Example
//Here is a simple example of using @|clear| to delete a variable.  First, we create a variable called @|a|:
//@<
//a = 53
//@>
//Next, we clear @|a| using the @|clear| function, and verify that it is deleted.
//@<1
//clear a
//a
//@>
//!

void ClearVariable(Interpreter* eval, string name) {
  eval->getContext()->deleteVariable(name);
}

void ClearAllFunction(Interpreter* eval) {
  ClearLibs(eval);
  stringVector names = eval->getContext()->listAllVariables();
  for (int i=0;i<names.size();i++)
    ClearVariable(eval,names[i]);
}

void ClearPersistent(Interpreter* eval) {
  stringVector names = eval->getContext()->listGlobalVariables();
  for (int i=0;i<names.size();i++) {
    if ((names[i].size() >= 1) && (names[i][0] == '_'))
      eval->getContext()->deleteGlobalVariable(names[i]);
  }
  eval->getContext()->clearPersistentVariableList();
}

void ClearGlobal(Interpreter* eval) {
  stringVector names = eval->getContext()->listGlobalVariables();
  for (int i=0;i<names.size();i++) {
    if ((names[i].size() >= 1) && (names[i][0] != '_')) {
      eval->getContext()->deleteGlobalVariable(names[i]);
    }
  }
  eval->getContext()->clearGlobalVariableList();
}

ArrayVector ClearFunction(int nargout, const ArrayVector& arg, Interpreter* eval) {
  stringVector names;
  if (arg.size() == 0) 
    names.push_back("all");
  else
    for (int i=0;i<arg.size();i++) 
      names.push_back(ArrayToString(arg[i]));
  for (int i=0;i<names.size();i++) {
    if (names[i] == "all")
      ClearAllFunction(eval);
    else if (names[i] == "libs")
      ClearLibs(eval);
    else if (names[i] == "persistent")
      ClearPersistent(eval);
    else if (names[i] == "global")
      ClearGlobal(eval);
    else if (names[i] == "classes")
      eval->clearUserClasses();
    else 
      ClearVariable(eval,names[i]);
  }
  return ArrayVector();
}

//!
//@Module WHO Describe Currently Defined Variables
//@@Section INSPECTION
//@@Usage
//Reports information on either all variables in the current context
//or on a specified set of variables.  For each variable, the @|who|
//function indicates the size and type of the variable as well as 
//if it is a global or persistent.  There are two formats for the 
//function call.  The first is the explicit form, in which a list
//of variables are provided:
//@[
//  who a1 a2 ...
//@]
//In the second form
//@[
//  who
//@]
//the @|who| function lists all variables defined in the current 
//context (as well as global and persistent variables). Note that
//there are two alternate forms for calling the @|who| function:
//@[
//  who 'a1' 'a2' ...
//@]
//and
//@[
//  who('a1','a2',...)
//@]
//@@Example
//Here is an example of the general use of @|who|, which lists all of the variables defined.
//@<
//c = [1,2,3];
//f = 'hello';
//p = randn(1,256);
//who
//@>
//In the second case, we examine only a specific variable:
//@<
//who c
//who('c')
//@>
//!
ArrayVector WhoFunction(int nargout, const ArrayVector& arg, Interpreter* eval) {
  int i;
  stringVector names;
  char buffer[1000];
  if (arg.size() == 0) {
    names = eval->getContext()->listAllVariables();
  } else {
    for (i=0;i<arg.size();i++) {
      Array varName(arg[i]);
      names.push_back(varName.getContentsAsString());
    }
  }
  std::sort(names.begin(),names.end());
  sprintf(buffer,"  Variable Name      Type   Flags             Size\n");
  eval->outputMessage(buffer);
  for (i=0;i<names.size();i++) {
    Array lookup;
    ArrayReference ptr;
    sprintf(buffer,"% 15s",names[i].c_str());
    eval->outputMessage(buffer);
    ptr = eval->getContext()->lookupVariable(names[i]);
    if (!ptr.valid())
      eval->outputMessage("   <undefined>");
    else {
      lookup = *ptr;
      Class t = lookup.dataClass();
      switch(t) {
      case FM_CELL_ARRAY:
	sprintf(buffer,"% 10s","cell");
	break;
      case FM_STRUCT_ARRAY:
	if (lookup.isUserClass())
	  sprintf(buffer,"% 10s",lookup.className().back().c_str());
	else
	  sprintf(buffer,"% 10s","struct");
	break;
      case FM_LOGICAL:
	sprintf(buffer,"% 10s","logical");
	break;
      case FM_UINT8:
	sprintf(buffer,"% 10s","uint8");
	break;
      case FM_INT8:
	sprintf(buffer,"% 10s","int8");
	break;
      case FM_UINT16:
	sprintf(buffer,"% 10s","uint16");
	break;
      case FM_INT16:
	sprintf(buffer,"% 10s","int16");
	break;
      case FM_UINT32:
	sprintf(buffer,"% 10s","uint32");
	break;
      case FM_INT32:
	sprintf(buffer,"% 10s","int32");
	break;
      case FM_UINT64:
	sprintf(buffer,"% 10s","uint64");
	break;
      case FM_INT64:
	sprintf(buffer,"% 10s","int64");
	break;
      case FM_FLOAT:
	sprintf(buffer,"% 10s","float");
	break;
      case FM_DOUBLE:
	sprintf(buffer,"% 10s","double");
	break;
      case FM_COMPLEX:
	sprintf(buffer,"% 10s","complex");
	break;
      case FM_DCOMPLEX:
	sprintf(buffer,"% 10s","dcomplex");
	break;
      case FM_STRING:
	sprintf(buffer,"% 10s","string");
	break;
      case FM_FUNCPTR_ARRAY:
	sprintf(buffer,"% 10s","func ptr");
	break;
      }
      eval->outputMessage(buffer);
      if (lookup.sparse())
	eval->outputMessage("   sparse");
      else
	eval->outputMessage("         ");	  
      if (eval->getContext()->isVariableGlobal(names[i])) {
	sprintf(buffer,"  global ");
	eval->outputMessage(buffer);
      } else if (eval->getContext()->isVariablePersistent(names[i])) {
	sprintf(buffer," persist ");
	eval->outputMessage(buffer);
      } else {
	sprintf(buffer,"         ");
	eval->outputMessage(buffer);
      }
      eval->outputMessage("  ");
      lookup.dimensions().printMe(eval);
    }
    eval->outputMessage("\n");
  }
  return ArrayVector();
}

//!
//@Module FIELDNAMES Fieldnames of a Structure
//@@Section INSPECTION
//@@Usage
//Returns a cell array containing the names of the fields in
//a structure array.  The syntax for its use is
//@[
//   x = fieldnames(y)
//@]
//where @|y| is a structure array of object array.  The result
//is a cell array, with one entry per field in @|y|.
//@@Example
//We define a simple structure array:
//@<
//y.foo = 3; y.goo = 'hello';
//x = fieldnames(y)
//@>
//!
ArrayVector FieldNamesFunction(int nargout, const ArrayVector& arg) {
  ArrayVector retval;
  if (arg.size() < 1)
    throw Exception("fieldnames function requires at least one argument");
  Array a(arg[0]);
  if (a.dataClass() != FM_STRUCT_ARRAY) {
    Array ret(Array::emptyConstructor());
    ret.promoteType(FM_CELL_ARRAY);
    return singleArrayVector(ret);
  }
  rvstring names(a.fieldNames());
  ArrayMatrix m;
  for (int i=0;i<names.size();i++)
    m.push_back(singleArrayVector(Array::stringConstructor(names.at(i))));
  return singleArrayVector(Array::cellConstructor(m));
}

//!
//@Module SIZE Size of a Variable
//@@Section INSPECTION
//@@Usage
//Returns the size of a variable.  There are two syntaxes for its
//use.  The first syntax returns the size of the array as a vector
//of integers, one integer for each dimension
//@[
//  [d1,d2,...,dn] = size(x)
//@]
//The other format returns the size of @|x| along a particular
//dimension:
//@[
//  d = size(x,n)
//@]
//where @|n| is the dimension along which to return the size.
//@@Example
//@<
//a = randn(23,12,5);
//size(a)
//@>
//Here is an example of the second form of @|size|.
//@<
//size(a,2)
//@>
//@@Tests
//@{ test_size1.m
//% Check the size function with an n-dim argument & one output
//function test_val = test_size1
//a = [];
//a(3,7,2) = 1.0;
//c = size(a);
//test_val = test(c(1) == 3) & test(c(2) == 7) & test(c(3) == 2);
//@}
//@{ test_size2.m
//% Check the size function with an n-dim argument & multiple outputs
//function test_val = test_size2
//a = [1,2,3;4,5,6];
//[c1,c2,c3] = size(a);
//test_val = test(c1 == 2) & test(c2 == 3) & test(c3 == 1);
//@}
//@{ test_size3.m
//% Check the size function with two arguments and one output
//function test_val = test_size3
//a = [1,2,3;4,5,6];
//n = size(a,2);
//test_val = test(n == 3);
//@}
//@{ test_size4.m
//% Check the size function with two arguments and two outputs
//function test_val = test_size4
//a = [1,2,3;4,5,6];
//test_val = 0;
//[c,d] = size(a,2);
//test_val = (c == 3);
//@}
//!
ArrayVector SizeFunction(int nargout, const ArrayVector& arg) {
  ArrayVector retval;
  if (arg.size() < 1)
    throw Exception("size function requires either one or two arguments");
  Dimensions sze;
  sze = arg[0].dimensions();
  if (arg.size() == 1) {
    if (nargout > 1) {
      ArrayVector retval;
      for (int i=0;i<nargout;i++)
	retval.push_back(Array::uint32Constructor(sze.get(i)));
      return retval;
    } else {
      uint32 *dims = (uint32 *) Malloc(sizeof(uint32)*sze.getLength());
      for (int i=0;i<sze.getLength();i++)
	dims[i] = sze.get(i);
      Array ret = Array(FM_UINT32,Dimensions(1,sze.getLength()),dims);
      retval.push_back(ret);
      return retval;
    } 
  }
  if (arg.size() == 2) {
    Array tmp(arg[1]);
    int dimval = tmp.getContentsAsIntegerScalar();
    if (dimval<1)
      throw Exception("illegal value for dimension argument in call to size function");
    retval.push_back(Array::uint32Constructor(sze.get(dimval-1)));
    return retval;
  }
}

//   ArrayVector LengthFunction(int nargout, const ArrayVector& arg) {
//     Array A(Array::int32Constructor(arg[0].dimensions().getMax()));
//     ArrayVector retval;
//     retval.push_back(A);
//     return retval;
//   }

int ExistBuiltinFunction(string fname, Interpreter* eval) {    
  bool isDefed;
  FuncPtr d;
  isDefed = eval->getContext()->lookupFunction(fname,d);
  if (isDefed && ((d->type() == FM_BUILT_IN_FUNCTION) ||
		  (d->type() == FM_SPECIAL_FUNCTION)))
    return 5;
  else
    return 0;
  return 0;
}

int ExistDirFunction(string fname, Interpreter* eval) {
  // Check for extra termination
  int flen = fname.size();
  if ((fname[flen-1] == '/') ||
      (fname[flen-1] == '\\'))
    fname[flen-1] = 0;
  QFileInfo filestat(QString::fromStdString(fname));
  if (!filestat.exists()) return 0;
  if (filestat.isDir()) return 7;
  return 0;
}

int ExistFileFunction(string fname, Interpreter* eval) {
  PathSearcher src(eval->getPath());
  try {
    src.ResolvePath(fname);
    return 2;
  } catch (Exception &e) {
  }
  bool isDefed;
  FuncPtr d;
  isDefed = eval->getContext()->lookupFunction(fname,d);
  if (isDefed && (d->type() == FM_M_FUNCTION))
    return 2;
  return 0;
}

int ExistVariableFunction(string fname, Interpreter* eval) {
  bool isDefed = (eval->getContext()->lookupVariable(fname).valid());
  if (isDefed)
    return 1;
  else
    return 0;
}

int ExistAllFunction(string fname, Interpreter* eval) {
  int ret;
  ret = ExistVariableFunction(fname,eval);
  if (ret) return ret;
  ret = ExistFileFunction(fname,eval);
  if (ret) return ret;
  ret = ExistDirFunction(fname,eval);
  if (ret) return ret;
  ret = ExistBuiltinFunction(fname,eval);
  if (ret) return ret;
  return 0;
}

//!
//@Module ISSET Test If Variable Set
//@@Section INSPECTION
//@@Usage
//Tests for the existence and non-emptiness of a variable.
//the general syntax for its use is
//@[
//   y = isset('name')
//@]
//where @|name| is the name of the variable to test.  This
//is functionally equivalent to 
//@[
//   y = exist('name','var') & ~isempty(name)
//@]
//It returns a @|logical| 1 if the variable is defined 
//in the current workspace, and is not empty, and returns
//a 0 otherwise.
//@@Example
//Some simple examples of using @|isset|
//@<
//who
//isset('a')
//a = [];
//isset('a')
//a = 2;
//isset('a')
//@>
//!
ArrayVector IsSetFunction(int nargout, const ArrayVector& arg, Interpreter* eval) {
  if (arg.size() < 1)
    throw Exception("isset function takes at least one argument - the name of the variable to check for");
  Array tmp(arg[0]);
  string fname = tmp.getContentsAsString();
  bool isDefed;
  ArrayReference d = eval->getContext()->lookupVariable(fname);
  isDefed = (d.valid());
  if (isDefed && !d->isEmpty())
    return singleArrayVector(Array::logicalConstructor(1));
  else
    return singleArrayVector(Array::logicalConstructor(0));
}
    
  
//!
//@Module EXIST Test for Existence
//@@Section INSPECTION
//@@Usage
//Tests for the existence of a variable, function, directory or
//file.  The general syntax for its use is
//@[
//  y = exist(item,kind)
//@]
//where @|item| is a string containing the name of the item
//to look for, and @|kind| is a string indicating the type 
//of the search.  The @|kind| must be one of 
//\begin{itemize}
//\item @|'builtin'| checks for built-in functions
//\item @|'dir'| checks for directories
//\item @|'file'| checks for files
//\item @|'var'| checks for variables
//\item @|'all'| checks all possibilities (same as leaving out @|kind|)
//\end{itemize}
//You can also leave the @|kind| specification out, in which case
//the calling syntax is
//@[
//  y = exist(item)
//@]
//The return code is one of the following:
//\begin{itemize}
//\item 0 - if @|item| does not exist
//\item 1 - if @|item| is a variable in the workspace
//\item 2 - if @|item| is an M file on the search path, a full pathname
// to a file, or an ordinary file on your search path
//\item 5 - if @|item| is a built-in FreeMat function
//\item 7 - if @|item| is a directory
//\end{itemize}
//Note: previous to version @|1.10|, @|exist| used a different notion
//of existence for variables: a variable was said to exist if it 
//was defined and non-empty.  This test is now performed by @|isset|.
//@@Example
//Some examples of the @|exist| function.  Note that generally @|exist|
//is used in functions to test for keywords.  For example,
//@[
//  function y = testfunc(a, b, c)
//  if (~exist('c'))
//    % c was not defined, so establish a default
//    c = 13;
//  end
//  y = a + b + c;
//@]
//An example of @|exist| in action.
//@<
//a = randn(3,5,2)
//b = []
//who
//exist('a')
//exist('b')
//exist('c')
//@>
//@@Tests
//@{ test_exist1.m
//function x = test_exist1
//x = test_exist1_assist;
//x = test_exist1_assist;
//
//function y = test_exist1_assist
//persistent x
//if (exist('x'))
//  y = 1;
//else
//  y = 0;
//  x = 1;
//end
//@}
//@{ test_exist2.m
//function x = test_exist2
//persistent y
//x = 1;
//if (exist('y'))
//  x = 0;
//end
//@}
//!
ArrayVector ExistFunction(int nargout, const ArrayVector& arg, Interpreter* eval) {
  if (arg.size() < 1)
    throw Exception("exist function takes at least one argument - the name of the object to check for");
  Array tmp(arg[0]);
  string fname = tmp.getContentsAsString();
  string stype;
  if (arg.size() > 1) {
    Array tmp2(arg[1]);
    stype = tmp2.getContentsAsString();
  } else {
    stype = "all";
  }
  int retval;
  if (stype=="all")
    retval = ExistAllFunction(fname,eval);
  else if (stype=="builtin")
    retval = ExistBuiltinFunction(fname,eval);
  else if (stype=="dir")
    retval = ExistDirFunction(fname,eval);
  else if (stype=="file")
    retval = ExistFileFunction(fname,eval);
  else if (stype=="var")
    retval = ExistVariableFunction(fname,eval);
  else throw Exception("Unrecognized search type for function 'exist'");
  return singleArrayVector(Array::int32Constructor(retval));
}

//!
//@Module NNZ Number of Nonzeros
//@@Section SPARSE
//@@Usage
//Returns the number of nonzero elements in a matrix.
//The general format for its use is
//@[
//   y = nnz(x)
//@]
//This function returns the number of nonzero elements
//in a matrix or array.  This function works for both
//sparse and non-sparse arrays.  For 
//@@Example
//@<
//a = [1,0,0,5;0,3,2,0]
//nnz(a)
//A = sparse(a)
//nnz(A)
//@>
//!
ArrayVector NNZFunction(int nargout, const ArrayVector& arg) {
  if (arg.size() != 1)
    throw Exception("nnz function takes one argument - the array");
  Array tmp(arg[0]);
  return singleArrayVector(Array::int32Constructor(tmp.nnz()));
}

//!
//@Module ISSPARSE Test for Sparse Matrix
//@@Section INSPECTION
//@@Usage
//Test a matrix to see if it is sparse or not.  The general
//format for its use is 
//@[
//   y = issparse(x)
//@]
//This function returns true if @|x| is encoded as a sparse
//matrix, and false otherwise.
//@@Example
//Here is an example of using @|issparse|:
//@<
//a = [1,0,0,5;0,3,2,0]
//issparse(a)
//A = sparse(a)
//issparse(A)
//@>
//!
ArrayVector IsSparseFunction(int nargout, const ArrayVector& arg) {
  if (arg.size() != 1)
    throw Exception("issparse function takes one argument - the array to test");
  Array tmp(arg[0]);
  return singleArrayVector(Array::logicalConstructor(tmp.sparse()));
}

//!
//@Module ISNAN Test for Not-a-Numbers
//@@Section INSPECTION
//@@Usage
//Returns true for entries of an array that are NaN's (i.e.,
//Not-a-Numbers).  The usage is
//@[
//   y = isnan(x)
//@]
//The result is a logical array of the same size as @|x|,
//which is true if @|x| is not-a-number, and false otherwise.
//Note that for @|complex| or @|dcomplex| data types that
//the result is true if either the real or imaginary parts
//are NaNs.
//@@Example
//Suppose we have an array of floats with one element that
//is @|nan|:
//@<
//a = [1.2 3.4 nan 5]
//isnan(a)
//@>
//!
ArrayVector IsNaNFunction(int nargout, const ArrayVector& arg) {
  if (arg.size() != 1)
    throw Exception("isnan function takes one argument - the array to test");
  Array tmp(arg[0]);
  if (tmp.isReferenceType())
    throw Exception("isnan is not defined for reference types");
  ArrayVector retval;
  int len(tmp.getLength());
  logical *op = (logical *) Malloc(len*sizeof(logical));
  switch (tmp.dataClass()) {
  case FM_STRING:
  case FM_LOGICAL:
  case FM_UINT8:
  case FM_INT8:
  case FM_UINT16:
  case FM_INT16:
  case FM_UINT32:
  case FM_INT32:
  case FM_UINT64:
  case FM_INT64:
    break;
  case FM_FLOAT: {
    const float *dp = (const float *)tmp.getDataPointer();
    for (int i=0;i<len;i++)
      op[i] = IsNaN(dp[i]) ? 1 : 0;
    break;
  }
  case FM_DOUBLE: {
    const double *dp = (const double *)tmp.getDataPointer();
    for (int i=0;i<len;i++)
      op[i] = IsNaN(dp[i]) ? 1 : 0;
    break;
  }
  case FM_COMPLEX: {
    const float *dp = (const float *)tmp.getDataPointer();
    for (int i=0;i<len;i++)
      op[i] = (IsNaN(dp[2*i]) || IsNaN(dp[2*i+1])) ? 1 : 0;
    break;
  }
  case FM_DCOMPLEX: {
    const double *dp = (const double *)tmp.getDataPointer();
    for (int i=0;i<len;i++)
      op[i] = (IsNaN(dp[2*i]) || IsNaN(dp[2*i+1])) ? 1 : 0;
    break;
  }
  }
  retval.push_back(Array(FM_LOGICAL,tmp.dimensions(),op));
  return(retval);
}

//!
//@Module ISINF Test for infinities
//@@Section INSPECTION
//@@Usage
//Returns true for entries of an array that are infs (i.e.,
//infinities).  The usage is
//@[
//   y = isinf(x)
//@]
//The result is a logical array of the same size as @|x|,
//which is true if @|x| is not-a-number, and false otherwise.
//Note that for @|complex| or @|dcomplex| data types that
//the result is true if either the real or imaginary parts
//are infinite.
//@@Example
//Suppose we have an array of floats with one element that
//is @|inf|:
//@<
//a = [1.2 3.4 inf 5]
//isinf(a)
//b = 3./[2 5 0 3 1]
//@>
//!
ArrayVector IsInfFunction(int nargout, const ArrayVector& arg) {
  if (arg.size() != 1)
    throw Exception("isinf function takes one argument - the array to test");
  Array tmp(arg[0]);
  if (tmp.isReferenceType())
    throw Exception("isinf is not defined for reference types");
  ArrayVector retval;
  int len(tmp.getLength());
  logical *op = (logical *) Malloc(len*sizeof(logical));
  switch (tmp.dataClass()) {
  case FM_STRING:
  case FM_LOGICAL:
  case FM_UINT8:
  case FM_INT8:
  case FM_UINT16:
  case FM_INT16:
  case FM_UINT32:
  case FM_INT32:
  case FM_UINT64:
  case FM_INT64:
    break;
  case FM_FLOAT: {
    const float *dp = (const float *)tmp.getDataPointer();
    for (int i=0;i<len;i++)
      op[i] = IsInfinite(dp[i]) ? 1 : 0;
    break;
  }
  case FM_DOUBLE: {
    const double *dp = (const double *)tmp.getDataPointer();
    for (int i=0;i<len;i++)
      op[i] = IsInfinite(dp[i]) ? 1 : 0;
    break;
  }
  case FM_COMPLEX: {
    const float *dp = (const float *)tmp.getDataPointer();
    for (int i=0;i<len;i++)
      op[i] = (IsInfinite(dp[2*i]) || IsInfinite(dp[2*i+1])) ? 1 : 0;
    break;
  }
  case FM_DCOMPLEX: {
    const double *dp = (const double *)tmp.getDataPointer();
    for (int i=0;i<len;i++)
      op[i] = (IsInfinite(dp[2*i]) || IsInfinite(dp[2*i+1])) ? 1 : 0;
    break;
  }
  }
  retval.push_back(Array(FM_LOGICAL,tmp.dimensions(),op));
  return(retval);
}

//!
//@Module WHERE Get Information on Program Stack
//@@Section INSPECTION
//@@Usage
//Returns information on the current stack.  The usage is
//@[
//   where
//@]
//The result is a kind of stack trace that indicates the state
//of the current call stack, and where you are relative to the
//stack.
//@@Example
//Suppose we have the following chain of functions.
//@{ chain1.m
//function chain1
//  a = 32;
//  b = a + 5;
//  chain2(b)
//@}
//@{ chain2.m
//function chain2(d)
//  d = d + 5;
//  chain3
//@}
//@{ chain3.m
//function chain3
//  g = 54;
//  f = g + 1;
//  keyboard
//@}
//The execution of the @|where| command shows the stack trace.
//@<
//chain1
//where
//@>
//!
ArrayVector WhereFunction(int nargout, const ArrayVector& arg, Interpreter* eval) {
  //    eval->stackTrace(false);
  eval->stackTrace(true);
  return ArrayVector();
}

//!
//@Module WHICH Get Information on Function
//@@Section INSPECTION
//@@Usage
//Returns information on a function (if defined).  The usage is
//@[
//   which(fname)
//@]
//where @|fname| is a @|string| argument that contains the name of the 
//function.  For functions and scripts defined
//via @|.m| files, the @|which| command returns the location of the source
//file:
//@[
//   y = which(fname)
//@]
//will return the filename for the @|.m| file corresponding to the given
//function, and an empty string otherwise.
//@@Example
//First, we apply the @|which| command to a built in function.
//@<
//which fft
//@>
//Next, we apply it to a function defined via a @|.m| file.
//@<
//which fliplr
//@>
//!
ArrayVector WhichFunction(int nargout, const ArrayVector& arg, Interpreter* eval) {
  if (arg.size() != 1)
    throw Exception("which function takes one string argument (the name of the function to look up)");
  string fname = arg[0].getContentsAsString();
  bool isFun;
  FuncPtr val;
  isFun = eval->lookupFunction(fname,val);
  char buffer[1000];
  Array ret(Array::emptyConstructor());
  if (isFun) {
    if (val->type() == FM_M_FUNCTION) {
      MFunctionDef *mptr;
      mptr = (MFunctionDef *) val;
      mptr->updateCode(eval);
      if (mptr->pcodeFunction) {
	if (mptr->scriptFlag) {
	  if (nargout == 0) {
	    sprintf(buffer,"Function %s, P-code script\n",fname.c_str());
	    eval->outputMessage(buffer);
	  }
	} else {
	  if (nargout == 0) {
	    sprintf(buffer,"Function %s, P-code function\n",fname.c_str());
	    eval->outputMessage(buffer);
	  }
	}
      } else {
	if (mptr->scriptFlag) {
	  if (nargout == 0) {
	    sprintf(buffer,"Function %s, M-File script in file '%s'\n",fname.c_str(),
		    mptr->fileName.c_str());
	    eval->outputMessage(buffer);
	  } else 
	    ret = Array::stringConstructor(mptr->fileName);
	} else {
	  if (nargout == 0) {
	    sprintf(buffer,"Function %s, M-File function in file '%s'\n",
		    fname.c_str(),mptr->fileName.c_str());
	    eval->outputMessage(buffer);
	  } else
	    ret = Array::stringConstructor(mptr->fileName);
	}
      }
    } else if ((val->type() == FM_BUILT_IN_FUNCTION) || (val->type() == FM_SPECIAL_FUNCTION) ) {
      if (nargout == 0) {
	sprintf(buffer,"Function %s is a built in function\n",fname.c_str());
	eval->outputMessage(buffer);
      }
    } else {
      if (nargout == 0) {
	sprintf(buffer,"Function %s is an imported function\n",fname.c_str());
	eval->outputMessage(buffer);
      }
    }
  } else {
    if (nargout == 0) {
      sprintf(buffer,"Function %s is unknown!\n",fname.c_str());
      eval->outputMessage(buffer);
    }
  }
  if (nargout > 0)
    return singleArrayVector(ret);
  else
    return ArrayVector();
}

ArrayVector SingleFindModeFull(Array x) {
  x.promoteType(FM_LOGICAL);
  const logical *dp;
  dp = (const logical*) x.getDataPointer();
  int len;
  len = x.getLength();
  // Count the number of non-zero entries
  int nonZero;
  nonZero = 0;
  int i;
  for (i=0;i<len;i++)
    if (dp[i]) nonZero++;
  // Setup the output array
  uint32 *op;
  op = (uint32*) Malloc(nonZero*sizeof(uint32));
  int ndx;
  ndx = 0;
  for (i=0;i<len;i++)
    if (dp[i])
      op[ndx++] = i + 1;
  Dimensions retDim(2);
  if (x.isRowVector()) {
    retDim.set(0,1);
    retDim.set(1,nonZero);
  } else {
    retDim.set(0,nonZero);
    retDim.set(1,1);
  }
  return singleArrayVector(Array(FM_UINT32,retDim,op));
}
  
ArrayVector RCFindModeFull(Array x) {
  x.promoteType(FM_LOGICAL);
  const logical *dp;
  dp = (const logical*) x.getDataPointer();
  int len;
  len = x.getLength();
  // Count the number of non-zero entries
  int nonZero;
  nonZero = 0;
  int i;
  for (i=0;i<len;i++)
    if (dp[i]) nonZero++;
  // Setup the output array
  uint32 *op_row;
  op_row = (uint32*) Malloc(nonZero*sizeof(uint32));
  uint32 *op_col;
  op_col = (uint32*) Malloc(nonZero*sizeof(uint32));
  int ndx;
  int rows = x.getDimensionLength(0);
  int cols = x.getDimensionLength(1);
  ndx = 0;
  for (i=0;i<len;i++)
    if (dp[i]) {
      op_row[ndx] = (i % rows) + 1;
      op_col[ndx++] = (i / rows) + 1;
    }
  Dimensions retDim(2);
  if (x.isRowVector()) {
    retDim.set(0,1);
    retDim.set(1,nonZero);
  } else {
    retDim.set(0,nonZero);
    retDim.set(1,1);
  }
  ArrayVector retval;
  retval.push_back(Array(FM_UINT32,retDim,op_row));
  retval.push_back(Array(FM_UINT32,retDim,op_col));
  return retval;
}

template <class T>
ArrayVector RCVFindModeFullReal(Array x) {
  const T* dp;
  dp = (const T*) x.getDataPointer();
  int len;
  len = x.getLength();
  // Count the number of non-zero entries
  int nonZero;
  nonZero = 0;
  int i;
  for (i=0;i<len;i++)
    if (dp[i]) nonZero++;
  // Setup the output array
  uint32 *op_row;
  op_row = (uint32*) Malloc(nonZero*sizeof(uint32));
  uint32 *op_col;
  op_col = (uint32*) Malloc(nonZero*sizeof(uint32));
  T* op_val;
  op_val = (T*) Malloc(nonZero*sizeof(T));
  int ndx;
  int rows = x.getDimensionLength(0);
  int cols = x.getDimensionLength(1);
  ndx = 0;
  for (i=0;i<len;i++)
    if (dp[i]) {
      op_row[ndx] = (i % rows) + 1;
      op_col[ndx] = (i / rows) + 1;
      op_val[ndx++] = dp[i];
    }
  Dimensions retDim(2);
  if (x.isRowVector()) {
    retDim.set(0,1);
    retDim.set(1,nonZero);
  } else {
    retDim.set(0,nonZero);
    retDim.set(1,1);
  }
  ArrayVector retval;
  retval.push_back(Array(FM_UINT32,retDim,op_row));
  retval.push_back(Array(FM_UINT32,retDim,op_col));
  retval.push_back(Array(x.dataClass(),retDim,op_val));
  return retval;
}

template <class T>
ArrayVector RCVFindModeFullComplex(Array x) {
  const T* dp;
  dp = (const T*) x.getDataPointer();
  int len;
  len = x.getLength();
  // Count the number of non-zero entries
  int nonZero;
  nonZero = 0;
  int i;
  for (i=0;i<len;i++)
    if (dp[2*i] || dp[2*i+1]) nonZero++;
  // Setup the output array
  uint32 *op_row;
  op_row = (uint32*) Malloc(nonZero*sizeof(uint32));
  uint32 *op_col;
  op_col = (uint32*) Malloc(nonZero*sizeof(uint32));
  T* op_val;
  op_val = (T*) Malloc(2*nonZero*sizeof(T));
  int ndx;
  int rows = x.getDimensionLength(0);
  int cols = x.getDimensionLength(1);
  ndx = 0;
  for (i=0;i<len;i++)
    if (dp[2*i] || dp[2*i+1]) {
      op_row[ndx] = (i % rows) + 1;
      op_col[ndx] = (i / rows) + 1;
      op_val[2*ndx] = dp[2*i];
      op_val[2*ndx+1] = dp[2*i+1];
      ndx++;
    }
  Dimensions retDim(2);
  if (x.isRowVector()) {
    retDim.set(0,1);
    retDim.set(1,nonZero);
  } else {
    retDim.set(0,nonZero);
    retDim.set(1,1);
  }
  ArrayVector retval;
  retval.push_back(Array(FM_UINT32,retDim,op_row));
  retval.push_back(Array(FM_UINT32,retDim,op_col));
  retval.push_back(Array(x.dataClass(),retDim,op_val));
  return retval;
}

ArrayVector RCVFindModeFull(Array x) {
  switch (x.dataClass()) {
  case FM_LOGICAL:
    return RCVFindModeFullReal<logical>(x);
  case FM_UINT8:
    return RCVFindModeFullReal<uint8>(x);
  case FM_INT8:
    return RCVFindModeFullReal<int8>(x);
  case FM_UINT16:
    return RCVFindModeFullReal<uint16>(x);
  case FM_INT16:
    return RCVFindModeFullReal<int16>(x);
  case FM_UINT32:
    return RCVFindModeFullReal<uint32>(x);
  case FM_INT32:
    return RCVFindModeFullReal<int32>(x);
  case FM_UINT64:
    return RCVFindModeFullReal<uint64>(x);
  case FM_INT64:
    return RCVFindModeFullReal<int64>(x);
  case FM_FLOAT:
    return RCVFindModeFullReal<float>(x);
  case FM_DOUBLE:
    return RCVFindModeFullReal<double>(x);
  case FM_COMPLEX:
    return RCVFindModeFullComplex<float>(x);
  case FM_DCOMPLEX:
    return RCVFindModeFullComplex<double>(x);
  case FM_STRING:
    return RCVFindModeFullReal<char>(x);
  }
}

ArrayVector FindModeSparse(Array x, int nargout) {
  // Convert the sparse matrix to RCV mode
  void *dp;
  uint32 *rows;
  uint32 *cols;
  int nnz;
  dp = SparseToIJV(x.dataClass(), x.getDimensionLength(0),
		   x.getDimensionLength(1), x.getSparseDataPointer(),
		   rows, cols, nnz);
  Dimensions retDim(2);
  if (x.isRowVector()) {
    retDim.set(0,1);
    retDim.set(1,nnz);
  } else {
    retDim.set(0,nnz);
    retDim.set(1,1);
  }
  ArrayVector retval;
  // Decide how to combine the arrays depending on nargout
  if (nargout == 3) {
    retval.push_back(Array(FM_UINT32,retDim,rows));
    retval.push_back(Array(FM_UINT32,retDim,cols));
    retval.push_back(Array(x.dataClass(),retDim,dp));
  } else if (nargout == 2) {
    retval.push_back(Array(FM_UINT32,retDim,rows));
    retval.push_back(Array(FM_UINT32,retDim,cols));
    Free(dp);
  } else {
    int numrows;
    numrows = x.getDimensionLength(0);
    for (int i=0;i<nnz;i++)
      rows[i] = rows[i] + (cols[i]-1)*numrows;
    Free(cols);
    Free(dp);
    retval.push_back(Array(FM_UINT32,retDim,rows));      
  }
  return retval;
}
  
//!
//@Module FIND Find Non-zero Elements of An Array
//@@Section ARRAY
//@@Usage
//Returns a vector that contains the indicies of all non-zero elements 
//in an array.  The usage is
//@[
//   y = find(x)
//@]
//The indices returned are generalized column indices, meaning that if 
//the array @|x| is of size @|[d1,d2,...,dn]|, and the
//element @|x(i1,i2,...,in)| is nonzero, then @|y|
//will contain the integer
//\[
//   i_1 + (i_2-1) d_1 + (i_3-1) d_1 d_2 + \dots
//\]
//The second syntax for the @|find| command is
//@[
//   [r,c] = find(x)
//@]
//which returns the row and column index of the nonzero entries of @|x|.
//The third syntax for the @|find| command also returns the values
//@[
//   [r,c,v] = find(x).
//@]
//Note that if the argument is a row vector, then the returned vectors
//are also row vectors. This form is particularly useful for converting 
//sparse matrices into IJV form.
//
//The @|find| command also supports some additional arguments.  Each of the
//above forms can be combined with an integer indicating how many results
//to return:
//@[
//   y = find(x,k)
//@]
//where @|k| is the maximum number of results to return.  This form will return
//the first @|k| results.  You can also specify an optional flag indicating 
//whether to take the first or last @|k| values:
//@[
//   y = find(x,k,'first')
//   y = find(x,k,'last')
//@]
//in the case of the @|'last'| argument, the last @|k| values are returned.
//@@Example
//Some simple examples of its usage, and some common uses of @|find| in FreeMat programs.
//@<
//a = [1,2,5,2,4];
//find(a==2)
//@>
//Here is an example of using find to replace elements of @|A| that are @|0| with the number @|5|.
//@<
//A = [1,0,3;0,2,1;3,0,0]
//n = find(A==0)
//A(n) = 5
//@>
//Incidentally, a better way to achieve the same concept is:
//@<
//A = [1,0,3;0,2,1;3,0,0]
//A(A==0) = 5
//@>
//Now, we can also return the indices as row and column indices using the two argument
//form of @|find|:
//@<
//A = [1,0,3;0,2,1;3,0,0]
//[r,c] = find(A)
//@>
//Or the three argument form of @|find|, which returns the value also:
//@<
//[r,c,v] = find(A)
//@>
//@@Tests
//@{ test_sparse20.m
//function x = test_sparse20
//a = [0,0,3,1,3;1,0,0,0,2;4,3,0,2,0];
//[i,j,v] = find(a);
//B = sparse(i,j,v);
//x = testeq(a,B);
//@}
//@{ test_sparse21.m
//function x = test_sparse21
//ai = [0,2,6,0,1;3,0,3,0,2;0,0,3,0,2];
//ar = [1,2,0,0,4;3,2,0,0,5;0,0,3,0,2];
//a = complex(ar+i*ai);
//[i,j,v] = find(a);
//B = sparse(i,j,v);
//x = testeq(a,B);
//@}
//@{ test_sparse68.m
//% Test sparse to IJV conversion
//function x = test_sparse68
//[yi1,zi1] = sparse_test_mat('int32',300,400);
//[yf1,zf1] = sparse_test_mat('float',300,400);
//[yd1,zd1] = sparse_test_mat('double',300,400);
//[yc1,zc1] = sparse_test_mat('complex',300,400);
//[yz1,zz1] = sparse_test_mat('dcomplex',300,400);
//[iyi1,jyi1,vyi1] = find(yi1);
//[izi1,jzi1,vzi1] = find(zi1);
//[iyf1,jyf1,vyf1] = find(yf1);
//[izf1,jzf1,vzf1] = find(zf1);
//[iyd1,jyd1,vyd1] = find(yd1);
//[izd1,jzd1,vzd1] = find(zd1);
//[iyc1,jyc1,vyc1] = find(yc1);
//[izc1,jzc1,vzc1] = find(zc1);
//[iyz1,jyz1,vyz1] = find(yz1);
//[izz1,jzz1,vzz1] = find(zz1);
//x = testeq(iyi1,izi1) & testeq(jyi1,jzi1) & testeq(vyi1,vzi1);
//x = x & testeq(iyf1,izf1) & testeq(jyf1,jzf1) & testeq(vyf1,vzf1);
//x = x & testeq(iyd1,izd1) & testeq(jyd1,jzd1) & testeq(vyd1,vzd1);
//x = x & testeq(iyc1,izc1) & testeq(jyc1,jzc1) & testeq(vyc1,vzc1);
//x = x & testeq(iyz1,izz1) & testeq(jyz1,jzz1) & testeq(vyz1,vzz1);
//@}
//@{ test_sparse70.m
//% Test sparse to IJV to sparse conversion
//function x = test_sparse70
//[yi1,zi1] = sparse_test_mat('int32',300,400);
//[yf1,zf1] = sparse_test_mat('float',300,400);
//[yd1,zd1] = sparse_test_mat('double',300,400);
//[yc1,zc1] = sparse_test_mat('complex',300,400);
//[yz1,zz1] = sparse_test_mat('dcomplex',300,400);
//[iyi1,jyi1,vyi1] = find(yi1);
//[iyf1,jyf1,vyf1] = find(yf1);
//[iyd1,jyd1,vyd1] = find(yd1);
//[iyc1,jyc1,vyc1] = find(yc1);
//[iyz1,jyz1,vyz1] = find(yz1);
//x = testeq(sparse(iyi1,jyi1,vyi1),zi1);
//x = x & testeq(sparse(iyf1,jyf1,vyf1),zf1);
//x = x & testeq(sparse(iyd1,jyd1,vyd1),zd1);
//x = x & testeq(sparse(iyc1,jyc1,vyc1),zc1);
//x = x & testeq(sparse(iyz1,jyz1,vyz1),zz1);
//@}
//!  

ArrayVector FindTrim(ArrayVector a, int cnt, bool first_flag, Interpreter* m_eval) {
  if (cnt < 0) return a;
  if (a.size() == 0) return a;
  int N = a[0].getLength();
  if (cnt > N) return a;
  ArrayVector ret;
  Array ndx;
  bool vertflag = !(a[0].isRowVector());
  if (first_flag)
    ndx = Array::int32RangeConstructor(1,1,cnt,vertflag);
  else
    ndx = Array::int32RangeConstructor((N-cnt)+1,1,N,vertflag);
  for (int i=0;i<a.size();i++) 
    ret.push_back(a[i].getVectorSubset(ndx,m_eval));
  return ret;
}

ArrayVector FindFunction(int nargout, const ArrayVector& arg, Interpreter* m_eval) {
  // Detect the Find mode...
  if (arg.size() < 1)
    throw Exception("find function takes at least one argument");
  Array tmp(arg[0]);
  int k = -1;
  bool first_flag = true;
  if (arg.size() > 1)
    k  = ArrayToInt32(arg[1]);
  if (arg.size() == 3) {
    string flag = ArrayToString(arg[2]);
    if (flag=="first")
      first_flag = true;
    else if (flag=="last")
      first_flag = false;
    else
      throw Exception("third option to find must be either 'first' or 'last'");
  }
  if (tmp.isReferenceType())
    throw Exception("find does not work on reference types (cell-arrays or structure arrays)");
  if ((nargout <= 1) && !tmp.sparse())
    return FindTrim(SingleFindModeFull(tmp),k,first_flag,m_eval);
  if ((nargout == 2) && !tmp.sparse())
    return FindTrim(RCFindModeFull(tmp),k,first_flag,m_eval);
  if ((nargout == 3) && !tmp.sparse())
    return FindTrim(RCVFindModeFull(tmp),k,first_flag,m_eval);
  if (nargout > 3)
    throw Exception("Do not understand syntax of find call (too many output arguments).");
  return FindTrim(FindModeSparse(tmp,nargout),k,first_flag,m_eval);
}

//!
//@Module MFILENAME Name of Current Function
//@@Section FreeMat
//@@Usage
//Returns a string describing the name of the current function.  For M-files
//this string will be the complete filename of the function.  This is true even
//for subfunctions.  The syntax for its use is
//@[
//    y = mfilename
//@]
//!

static std::string fname_only(std::string name) {
  int ndx;
  ndx = name.rfind("/");
  if (ndx>=0)
    name.erase(0,ndx+1);
  ndx = name.rfind(".");
  if (ndx>=0)
    name.erase(ndx,name.size());
  return name;
}

ArrayVector MFilenameFunction(int nargout, const ArrayVector& arg, Interpreter* eval) {
  return singleArrayVector(Array::stringConstructor(fname_only(eval->getMFileName())));
}

//!
//@Module COMPUTER Computer System FreeMat is Running On
//@@Section FreeMat
//@@Usage
//Returns a string describing the name of the system FreeMat is running on.
//The exact value of this string is subject to change, although the @|'MAC'|
//and @|'PCWIN'| values are probably fixed.
//@[
//  str = computer
//@]
//Currently, the following return values are defined
//\begin{itemize}
//  \item @|'PCWIN'| - MS Windows
//  \item @|'MAC'| - Mac OS X
//  \item @|'UNIX'| - All others
//\end{itemize}
//!
ArrayVector ComputerFunction(int nargout, const ArrayVector& arg) {
#ifdef WIN32
  return singleArrayVector(Array::stringConstructor("PCWIN"));
#elif defined(__APPLE__)
  return singleArrayVector(Array::stringConstructor("MAC"));
#else
  return singleArrayVector(Array::stringConstructor("UNIX"));
#endif
}



syntax highlighted by Code2HTML, v. 0.9.1