/* octave.cpp
   Copyright (C) 2009 by Bjoern Anton Erlach. */

// OCTAVE architecture file for faust.
//
// 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 <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <limits.h>
#include <math.h>
#include <errno.h>
#include <time.h>
#include <vector>
#include <string>
#include <map>
#include <iostream>
#include <oct.h>

using namespace std;

// TODO: find out what to do with this Meta thing
struct Meta : map<const char*, const char*>
{
    void declare (const char* key, const char* value) { (*this)[key]=value; }
};

// abs is now predefined
//template<typename T> T abs (T a)			{ return (a<T(0)) ? -a : a; }

inline int		lsr (int x, int n)			{ return int(((unsigned int)x) >> n); }

/******************************************************************************
*******************************************************************************

							       VECTOR INTRINSICS

*******************************************************************************
*******************************************************************************/

//inline void *aligned_calloc(size_t nmemb, size_t size) { return (void*)((unsigned)(calloc((nmemb*size)+15,sizeof(char)))+15 & 0xfffffff0); }
//inline void *aligned_calloc(size_t nmemb, size_t size) { return (void*)((size_t)(calloc((nmemb*size)+15,sizeof(char)))+15 & ~15); }

<<includeIntrinsic>>

/******************************************************************************
*******************************************************************************

			ABSTRACT USER INTERFACE

*******************************************************************************
*******************************************************************************/

class UI
{
	bool	fStopped;
public:

	UI() : fStopped(false) {}
	virtual ~UI() {}

	// -- active widgets

	virtual void addButton(const char* label, float* zone) = 0;
	virtual void addToggleButton(const char* label, float* zone) = 0;
	virtual void addCheckButton(const char* label, float* zone) = 0;
	virtual void addVerticalSlider(const char* label, float* zone, float init, float min, float max, float step) = 0;
	virtual void addHorizontalSlider(const char* label, float* zone, float init, float min, float max, float step) = 0;
	virtual void addNumEntry(const char* label, float* zone, float init, float min, float max, float step) = 0;

	// -- passive widgets

	virtual void addNumDisplay(const char* label, float* zone, int precision) = 0;
	virtual void addTextDisplay(const char* label, float* zone, char* names[], float min, float max) = 0;
	virtual void addHorizontalBargraph(const char* label, float* zone, float min, float max) = 0;
	virtual void addVerticalBargraph(const char* label, float* zone, float min, float max) = 0;

	// -- frames and labels

	virtual void openFrameBox(const char* label) = 0;
	virtual void openTabBox(const char* label) = 0;
	virtual void openHorizontalBox(const char* label) = 0;
	virtual void openVerticalBox(const char* label) = 0;
	virtual void closeBox() = 0;

	virtual void show() = 0;
	virtual void run() = 0;

	void stop()		{ fStopped = true; }
	bool stopped() 	{ return fStopped; }

    virtual void declare(float* zone, const char* key, const char* value) {}
};

struct param {
	string fName; float *fVals; float* fZone; float fMin; float fMax;
	param(string name, float* z, float init, float a, float b) : fName(name), fVals(NULL), fZone(z), fMin(a), fMax(b) { *z = init; }
};


class FNUI : public UI
{
	vector<param>	fParam;
	int numOptions;

public:
	FNUI() : UI() { numOptions=0; }
	virtual ~FNUI() {}


	void addOption(const char* label, float* zone, float init, float min, float max)
	{
	  string fullname = label;
	  fParam.push_back(param(fullname, zone, init, min, max));
	  numOptions++;
	}

	virtual vector<param> getOpts () { return fParam; }

	virtual void addButton(const char* label, float* zone)
	{
		addOption(label,zone,0,0,1);
	}

	virtual int getNumOptions() { return numOptions; }

	virtual void addToggleButton(const char* label, float* zone)
	{
		addOption(label,zone,0,0,1);
	}

	virtual void addCheckButton(const char* label, float* zone)
	{
		addOption(label,zone,0,0,1);
	}

	virtual void addVerticalSlider(const char* label, float* zone, float init, float min, float max, float step)
	{
		addOption(label,zone,init,min,max);
	}

	virtual void addHorizontalSlider(const char* label, float* zone, float init, float min, float max, float step)
	{
		addOption(label,zone,init,min,max);
	}

	virtual void addNumEntry(const char* label, float* zone, float init, float min, float max, float step)
	{
		addOption(label,zone,init,min,max);
	}

	// -- passive widgets

	virtual void addNumDisplay(const char* label, float* zone, int precision) {}
	virtual void addTextDisplay(const char* label, float* zone, char* names[], float min, float max) {}
	virtual void addHorizontalBargraph(const char* label, float* zone, float min, float max) {}
	virtual void addVerticalBargraph(const char* label, float* zone, float min, float max) {}


	virtual void openFrameBox(const char* label)		{ }
	virtual void openTabBox(const char* label)              { }
	virtual void openHorizontalBox(const char* label)	{ }
	virtual void openVerticalBox(const char* label)		{ }

	//virtual void openFrameBox(const char* label)		{ openAnyBox(label); }
	//virtual void openTabBox(const char* label)              { openAnyBox(label); }
	//virtual void openHorizontalBox(const char* label)	{ openAnyBox(label); }
	//virtual void openVerticalBox(const char* label)		{ openAnyBox(label); }

	//virtual void closeBox() { fPrefix.pop(); }
	virtual void closeBox() { }
	virtual void run() 	{}
	virtual void show() 	{}

};

/******************************************************************************
*******************************************************************************

			    FAUST DSP

*******************************************************************************
*******************************************************************************/

//----------------------------------------------------------------
//  abstract definition of a signal processor
//----------------------------------------------------------------

class dsp {
 protected:
	int fSamplingFreq;
 public:
	dsp() {}
	virtual ~dsp() {}

	virtual int getNumInputs() 						= 0;
	virtual int getNumOutputs() 					= 0;
	virtual void buildUserInterface(UI* interface) 	= 0;
	virtual void init(int samplingRate) 			= 0;
 	virtual void compute(int len, float** inputs, float** outputs) 	= 0;
};

//----------------------------------------------------------------------------
//  FAUST generated signal processor
//----------------------------------------------------------------------------

<<includeclass>>

//----------------------------------------------------------------------------
//  Octave interface
//----------------------------------------------------------------------------

// Prefered way to allocate memory
#define ALLOC(x) alloca(x)
#define FREE(x) ((void)0)
// if ALLOCA is not available use MALLOC
//#define ALLOC(x) malloc(x)
//#define FREE(x) free(x)

#define QUOTEME(x) #x

#define DEFAULT_SAMPLERATE 44100
#define DEFAULT_BLOCKSIZE 64

// linear interpolation for vector valued control inputs
void
interpolate_ctrlin (float *vals, NDArray in, int n)
{
  int nin = in.length();
  double ratio = (double)(n-1)/(double)(nin-1);
  int irat = (int) ratio;
  double frat = ratio - (double) irat;
  double rest = 0;
  int i = 0;
  float x;

  for (int j=0; j<(nin-1); j++) {
	  float del;
	  int seglength = irat;
	  rest += frat;
	  if (rest >= 1.0) {
		  seglength ++;
		  rest -= 1.0;
	  }
	  del = (in(j+1) - in(j)) / (float) seglength;
	  x = in(j);
	  for (int k=0; k<seglength; k++) {
		  vals[i++] = x;
		  x += del;
	  }
  }
  for (; i<n; i++) {
	  vals[i] = in(nin-1);
  }
}

DEFUN_DLD (FAUST_FUNC_NAME, args, nargout,
	   "type " QUOTEME(FAUST_FUNC_NAME) "() to see the arguments.\n")
{
  int nargin = args.length();
  int numIn;
  int numOut;
  int numOpts;
  int maxInputLength = 0;
  mydsp DSP;
  int ngivenctrls;
  float **finputs;
  float **foutputs;
  float **controlinputs;
  int ctrllength;
  int ctrlargoff;
  int allscalarctrls = 1;
  // TODO: float **controloutputs;
  int i;
  vector<param> opts;
  int bsize;
  int srate;
  octave_value_list retval;
  octave_value tmp;

  FNUI* interface = new FNUI();
  DSP.buildUserInterface(interface);

  // check if global variable FAUST_BLOCKSIZE is set.
  tmp = get_global_value ("FAUST_BLOCKSIZE", true);
  if (tmp.is_defined ())
	  bsize = (int) tmp.scalar_value();
  else {
	  bsize = DEFAULT_BLOCKSIZE;
  }

  // check if global variable FAUST_SAMPLERATE is set.
  tmp = get_global_value ("FAUST_SAMPLERATE", true);
  if (tmp.is_defined ())
	  srate = (int) tmp.scalar_value();
  else {
	  srate = DEFAULT_SAMPLERATE;
  }

  DSP.init(srate);
  opts = interface->getOpts();

  numIn = DSP.getNumInputs();
  numOut = DSP.getNumOutputs();
  numOpts = interface->getNumOptions();

  // print a usage message in case the function is called with too few arguments
  if (nargin < numIn || nargin == 0) {
	  if (numOut>1) {
		  octave_stdout << "[out1";
		  for (i=2; i<=numOut; i++)
			  octave_stdout << ",out" << i;
		  octave_stdout << "] = " << QUOTEME(FAUST_FUNC_NAME) << "(";
	  } else {
		  octave_stdout << "out = " << QUOTEME(FAUST_FUNC_NAME) << "(";
	  }
	  if (numIn == 0)
		  octave_stdout << "numsamps";
	  else
		  octave_stdout << "in1";
	  for (i=2; i<=numIn; i++)
		  octave_stdout << ", in" << i;
	  for (i=0; i<numOpts; i++)
		  octave_stdout << ", " << opts[i].fName;
	  octave_stdout << ")\n";
	  delete interface;
	  return retval;
 }

  // If we have inputs we use the length of the longest input vector
  // as length of the output to be produced.
  // If we don't have inputs, the first argument specifies the number of
  // samples to be produced.
  if (numIn == 0) {
	  maxInputLength = args(0).scalar_value();
	  ctrlargoff = 1;
  } else {
	  ctrlargoff = numIn;
	  for (i=0; i<numIn; i++) {
		  octave_idx_type nr = args(i).matrix_value().rows();
		  octave_idx_type nc = args(i).matrix_value().columns();
		  if (nr == 1) {
			  if (nc > maxInputLength)
				  maxInputLength = nc;
		  } else if (nc == 1) {
			  if (nr > maxInputLength)
				  maxInputLength = nr;
		  } else {
			  maxInputLength = nc;
			  octave_stdout << "Argument " << i << " has wrong dimensions " << nr << "x" << nc << "\n";
		  }
	  }
  }


  ctrllength = (maxInputLength+bsize-1)/bsize;

  // check for arguments that should serve as control inputs
  for (i=ctrlargoff; i<nargin; i++) {
	  if ((i-ctrlargoff) < numOpts) {
		  NDArray v = args(i).array_value();
		  if (v.length() > 1) {
			  allscalarctrls = 0;
			  opts[i-ctrlargoff].fVals = (float*) ALLOC(sizeof(float)*ctrllength);
			  interpolate_ctrlin(opts[i-ctrlargoff].fVals, v, ctrllength);
			  *opts[i-ctrlargoff].fZone = (float) v(0);
		  } else {
			  *opts[i-ctrlargoff].fZone = (float) args(i).scalar_value();
		  }
	  }
  }

  for (i=0; i<numOpts; i++) {
	  octave_stdout << "Parameter " << opts[i].fName << ": " << *opts[i].fZone << "\n";
  }

  finputs = (float**) ALLOC(sizeof(float*) * numIn);
  foutputs = (float**) ALLOC(sizeof(float*) * numOut);

  // Copy the matrix and convert to floats - This is a real slowdown!
  for (i=0; i<numIn; i++) {
	  Matrix m = args(i).matrix_value();
	  float *p;
	  finputs[i] = (float*) ALLOC(maxInputLength * sizeof(float));
	  memset(finputs[i], 0, sizeof(float)*maxInputLength);
	  p = finputs[i];
	  if (m.rows() > m.columns()) {
		  for (int j=0; j<m.rows(); j++) {
			  *p++ = (float) m(j,0);
		  }
	  } else {
		  for (int j=0; j<m.columns(); j++) {
			  *p++ = (float) m(0,j);
		  }
	  }
  }

  // allocate output vectors
  for (i=0; i<numOut; i++) {
	  foutputs[i] = (float*) ALLOC(maxInputLength * sizeof(float));
	  memset(foutputs[i], 0, sizeof(float)*maxInputLength);
  }

  if (allscalarctrls) {
	  DSP.compute(maxInputLength, finputs, foutputs);
  } else {
	  int nleft = maxInputLength;
	  int k = 0;
	  float **fins;
	  float **fouts;
	  fins = (float**) ALLOC(sizeof(float*) * numIn);
	  fouts = (float**) ALLOC(sizeof(float*) * numOut);
	  memcpy(fins, finputs, sizeof(float*)*numIn);
	  memcpy(fouts, foutputs, sizeof(float*)*numOut);
	  while (nleft > 0) {
		  int n = min(bsize, nleft);
		  for (i=0; i<numOpts; i++) {
			  if (opts[i].fVals) {
				  *opts[i].fZone = opts[i].fVals[k];
			  }
		  }
		  DSP.compute(n, fins, fouts);
		  nleft -= n;
		  k++;
		  for (i=0; i<numIn; i++)
			  fins[i] += n;
		  for (i=0; i<numOut; i++)
			  fouts[i] += n;
	  }
	  FREE(fins);
	  FREE(fouts);
  }

  // copy the output from the float arrays (and free all tmp memory if malloc is used)
  for (i=0; i<numOut; i++) {
	  Matrix output = Matrix(1, maxInputLength);
	  for (int j=0; j<maxInputLength; j++)
		  output(0, j) = (double) foutputs[i][j];
	  FREE(foutputs[i]);
	  retval(i) = output;
  }
  for (i=0; i<numOpts; i++) {
	  if (opts[i].fVals) {
		  FREE(opts[i].fVals);
	  }
  }
  for (i=0; i<numIn; i++) {
	  FREE(finputs[i]);
  }
  FREE(foutputs);
  FREE(finputs);

  delete interface;
  return retval;
}
