/************************************************************************
 IMPORTANT NOTE : this file contains two clearly delimited sections :
 the ARCHITECTURE section (in two parts) and the USER section. Each section
 is governed by its own copyright and license. Please check individually
 each section for license and copyright information.
 *************************************************************************/

/*******************BEGIN ARCHITECTURE SECTION (part 1/2)****************/

/************************************************************************
 FAUST Architecture File
 Copyright (C) 2003-2019 GRAME, Centre National de Creation Musicale
 ---------------------------------------------------------------------
 This Architecture section is free software; you can redistribute it
 and/or modify it under the terms of the GNU General Public License
 as published by the Free Software Foundation; either version 3 of
 the License, or (at your option) any later version.
 
 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, see <http://www.gnu.org/licenses/>.
 
 EXCEPTION : As a special exception, you may create a larger work
 that contains this FAUST architecture section and distribute
 that work under terms of your choice, so long as this FAUST
 architecture section is not modified.
 
 ************************************************************************
 ************************************************************************/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <limits.h>
#include <math.h>
#include <errno.h>
#include <time.h>
#include <ctype.h>

#include <vector>
#include <stack>
#include <string>
#include <map>
#include <iostream>

// g++ -O3 -lm -lsndfile  myfx.cpp

using namespace std;

#include "gui/GUI.h"
#include "audio/dsp.h"
#include "misc.h"

//-------------------------------------------------------------------
// Generic min and max using c++ inline
//-------------------------------------------------------------------

inline int 		max (unsigned int a, unsigned int b) { return (a>b) ? a : b; }
inline int 		max (int a, int b) 			{ return (a>b) ? a : b; }

inline long 	max (long a, long b) 		{ return (a>b) ? a : b; }
inline long 	max (int a, long b) 		{ return (a>b) ? a : b; }
inline long 	max (long a, int b) 		{ return (a>b) ? a : b; }

inline float 	max (float a, float b) 		{ return (a>b) ? a : b; }
inline float 	max (int a, float b) 		{ return (a>b) ? a : b; }
inline float 	max (float a, int b) 		{ return (a>b) ? a : b; }
inline float 	max (long a, float b) 		{ return (a>b) ? a : b; }
inline float 	max (float a, long b) 		{ return (a>b) ? a : b; }

inline double 	max (double a, double b) 	{ return (a>b) ? a : b; }
inline double 	max (int a, double b) 		{ return (a>b) ? a : b; }
inline double 	max (double a, int b) 		{ return (a>b) ? a : b; }
inline double 	max (long a, double b) 		{ return (a>b) ? a : b; }
inline double 	max (double a, long b) 		{ return (a>b) ? a : b; }
inline double 	max (float a, double b) 	{ return (a>b) ? a : b; }
inline double 	max (double a, float b) 	{ return (a>b) ? a : b; }

inline int 		min (int a, int b) 			{ return (a<b) ? a : b; }

inline long 	min (long a, long b) 		{ return (a<b) ? a : b; }
inline long 	min (int a, long b) 		{ return (a<b) ? a : b; }
inline long 	min (long a, int b) 		{ return (a<b) ? a : b; }

inline float 	min (float a, float b) 		{ return (a<b) ? a : b; }
inline float 	min (int a, float b) 		{ return (a<b) ? a : b; }
inline float 	min (float a, int b) 		{ return (a<b) ? a : b; }
inline float 	min (long a, float b) 		{ return (a<b) ? a : b; }
inline float 	min (float a, long b) 		{ return (a<b) ? a : b; }

inline double 	min (double a, double b) 	{ return (a<b) ? a : b; }
inline double 	min (int a, double b) 		{ return (a<b) ? a : b; }
inline double 	min (double a, int b) 		{ return (a<b) ? a : b; }
inline double 	min (long a, double b) 		{ return (a<b) ? a : b; }
inline double 	min (double a, long b) 		{ return (a<b) ? a : b; }
inline double 	min (float a, double b) 	{ return (a<b) ? a : b; }
inline double 	min (double a, float b) 	{ return (a<b) ? a : b; }

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

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

struct param {
    float* fZone; float fMin; float fMax;
    param(float* z, float init, float a, float b) : fZone(z), fMin(a), fMax(b) { *z = init; }
};

class CMDUI : public UI
{
    int                  fArgc;
    char**               fArgv;
    vector<char*>        fFiles;
    stack<string>        fPrefix;
    map<string, param>   fKeyParam;
    
    void openAnyBox(const char* label)
    {
        string prefix;
        
        if (label && label[0]) {
            prefix = fPrefix.top() + "-" + label;
        } else {
            prefix = fPrefix.top();
        }
        fPrefix.push(prefix);
    }
    
    string simplify(const string& src)
    {
        int       i = 0;
        int       level = 0;
        string    dst;
        
        while (src[i] ) {
            
            switch (level) {
                    
                case 0 :
                case 1 :
                case 2 :
                    // Skip the begin of the label "--foo-"
                    // until 3 '-' have been read
                    if (src[i] == '-') { level++; }
                    break;
                    
                case 3 :
                    // copy the content, but skip non alphnum
                    // and content in parenthesis
                    switch (src[i]) {
                        case '(' :
                        case '[' :
                            level++;
                            break;
                            
                        case '-' :
                            dst += '-';
                            break;
                            
                        default :
                            if (isalnum(src[i])) {
                                dst+= tolower(src[i]);
                            }
                    }
                    break;
                    
                default :
                    // here we are inside parenthesis and
                    // we skip the content until we are back to
                    // level 3
                    switch (src[i]) {
                            
                        case '(' :
                        case '[' :
                            level++;
                            break;
                            
                        case ')' :
                        case ']' :
                            level--;
                            break;
                            
                        default :
                            break;
                    }
            }
            i++;
        }
        return dst;
    }
    
public:
    
    CMDUI(int argc, char *argv[]) : UI(), fArgc(argc), fArgv(argv) { fPrefix.push("-"); }
    virtual ~CMDUI() {}
    
    void addOption(const char* label, float* zone, float init, float min, float max)
    {
        string fullname = "-" + simplify(fPrefix.top() + "-" + label);
        fKeyParam.insert(make_pair(fullname, param(zone, init, min, max)));
    }
    
    virtual void addButton(const char* label, float* zone)
    {
        addOption(label,zone,0,0,1);
    }
    
    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, const 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)             {}
    
    // -- soundfiles
    
    virtual void addSoundfile(const char* label, const char* filename, Soundfile** sf_zone) {}
    
    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 show() {}
    virtual void run()     {}
    
    void printhelp()
    {
        map<string, param>::iterator i;
        cout << fArgc << "\n";
        cout << fArgv[0] << " option list : ";
        for (i = fKeyParam.begin(); i != fKeyParam.end(); i++) {
            cout << "[ " << i->first << " " << i->second.fMin << ".." << i->second.fMax <<" ] ";
        }
        cout << " infile outfile\n";
    }
    
    void process_command()
    {
        map<string, param>::iterator p;
        for (int i = 1; i < fArgc; i++) {
            if (fArgv[i][0] == '-') {
                if (    (strcmp(fArgv[i], "-help") == 0)
                    || (strcmp(fArgv[i], "-h") == 0)
                    || (strcmp(fArgv[i], "--help") == 0) )     {
                    printhelp();
                    exit(1);
                }
                p = fKeyParam.find(fArgv[i]);
                if (p == fKeyParam.end()) {
                    cout << fArgv[0] << " : unrecognized option " << fArgv[i] << "\n";
                    printhelp();
                    exit(1);
                }
                char*    end;
                *(p->second.fZone) = float(strtod(fArgv[i+1], &end));
                i++;
            } else {
                fFiles.push_back(fArgv[i]);
            }
        }
    }
    
    int     files()            { return fFiles.size(); }
    char*     file (int n)    { return fFiles[n]; }
    
};

class channels
{
    
protected:
    
    int     fNumFrames;
    int     fNumChannels;
    int*    fRates;
    float*  fBuffers[256];
    
public:
    
    channels(int nframes, int nchannels, int* rates)
    {
        fNumFrames       = nframes;
        fNumChannels     = nchannels;
        fRates = rates;
        
        // allocate audio  channels
        for (int chan = 0; chan < fNumChannels; chan++) {
            fBuffers[chan] = (float*)calloc(fNumFrames * fRates[chan], sizeof(float));
        }
    }
    
    ~channels()
    {
        // free separate input channels
        for (int i = 0; i < fNumChannels; i++) {
            //    free(fBuffers[i]);
        }
    }
    
    float** buffers() { return fBuffers; }
    
};

class input_channels : public channels
{
public:
    
    input_channels(int nframes, int nchannels, int* rates)
    :channels(nframes, nchannels, rates)
    {}
    
    void dirac()
    {
        cout << "dirac: chan " << fNumChannels << " frames: " << fNumFrames << endl;
        for (int chan = 0; chan < fNumChannels; chan++) {
            memset(fBuffers[chan], 0, fNumFrames * fRates[chan] * sizeof(float));
            fBuffers[0][0] = 1.f;
        }
    }
    
    void step()
    {
        cout << "step: chan " << fNumChannels << " frames: " << fNumFrames << endl;
        for (int chan = 0; chan < fNumChannels; chan++) {
            for (int frame = 0; frame < fNumFrames * fRates[chan]; frame++) {
                fBuffers[chan][frame] = 1.0;
            }
        }
    }
    
    void ramp()
    {
        cout << "ramp: chan " << fNumChannels << " frames: " << fNumFrames << endl;
        for (int chan = 0; chan < fNumChannels; chan++) {
            for (int frame = 0; frame < fNumFrames * fRates[chan]; frame++) {
                fBuffers[chan][frame] = (float(frame)/float(fNumFrames * fRates[chan]));
            }
        }
    }
    
};

/******************************************************************************
 *******************************************************************************
 
 VECTOR INTRINSICS
 
 *******************************************************************************
 *******************************************************************************/

<<includeIntrinsic>>

/********************END ARCHITECTURE SECTION (part 1/2)****************/

/**************************BEGIN USER SECTION **************************/

<<includeclass>>

/***************************END USER SECTION ***************************/

/*******************BEGIN ARCHITECTURE SECTION (part 2/2)***************/

mydsp DSP;

#define kFrames 128

int main(int argc, char* argv[])
{
	float fnbsamples;
    float signal_test_type;
    FILE* mr_input = NULL;

	CMDUI* interface = new CMDUI(argc, argv);
	DSP.buildUserInterface(interface);
	interface->addOption("-n", &fnbsamples, 16, 0.0, 100000000.0);
    interface->addOption("-s", &signal_test_type, 1.0, 0.0, 2.0);

    if (DSP.getNumInputs() > 0) {
        mr_input = fopen("mr-plot.in", "r");
        if (!mr_input) {
            fprintf(stderr, "no input file found\n");
        }
    }

	// init signal processor and the user interface values
	DSP.init(44100);

	// modify the UI values according to the command line options
	interface->process_command();

    int nins = DSP.getNumInputs();
    int nins_rate[nins];
    int max_nins_rate = 0;
    for (int chan = 0; chan < nins; chan++) {
        nins_rate[chan] = DSP.getInputRate(chan);
        max_nins_rate = std::max<int>(max_nins_rate, nins_rate[chan]);
    }
	input_channels chan_in(kFrames, nins, nins_rate);

	int nouts = DSP.getNumOutputs();
    int nouts_rate[nins];
    int max_nouts_rate = 0;
    for (int chan = 0; chan < nouts; chan++) {
        nouts_rate[chan] = DSP.getOutputRate(chan);
        max_nouts_rate = std::max<int>(max_nouts_rate, nouts_rate[chan]);
    }
	channels chan_out(kFrames, nouts, nouts_rate);

    switch(int(signal_test_type)) {

        case 0:
            chan_in.dirac();
            break;
        case 1:
            chan_in.step();
            break;
        case 2:
            chan_in.ramp();
            break;
    }

	int nbsamples = int(fnbsamples);
  	while (nbsamples > kFrames) {
        // Read input
        for (int i = 0; i < kFrames; i++) {
            for (int r = 0; r < max_nins_rate; r++) {
                for (int chan = 0; chan < nins; chan++) {
                    /*
                    float sample;
                    if (mr_input) {
                        fscanf(mr_input, "%8f\t", &sample);
                    } else {
                        sample = float(i);
                    }
                    if (r < nins_rate[chan]) {
                        chan_in.buffers()[chan][i * nins_rate[chan] + r] = sample;
                    } else {
                        // Nothing
                    }
                    */
                }
            }
		}
        // Compute
		DSP.compute(kFrames, chan_in.buffers(), chan_out.buffers());
        // Write output
		for (int i = 0; i < kFrames; i++) {
            cout << "--- frame " << i << " ---" << endl;
            for (int r = 0; r < max_nouts_rate; r++) {
                for (int chan = 0; chan < nouts; chan++) {
                    if (r < nouts_rate[chan]) {
                        printf("%8f\t", chan_out.buffers()[chan][i * nouts_rate[chan] + r]);
                    } else {
                        printf("\t");
                    }
                }
                printf("\n");
            }
		}
		nbsamples -= kFrames;
	}

    // Remaining frames

    // Read input
    for (int i = 0; i < kFrames; i++) {
        for (int r = 0; r < max_nins_rate; r++) {
            for (int chan = 0; chan < nins; chan++) {
                /*
                float sample;
                if (mr_input) {
                    fscanf(mr_input, "%8f\t", &sample);
                } else {
                    sample = float(i);
                }
                if (r < nins_rate[chan]) {
                    chan_in.buffers()[chan][i * nins_rate[chan] + r] = sample;
                } else {
                    // Nothing
                }
                */
            }
        }
    }
    // Compute
    DSP.compute(kFrames, chan_in.buffers(), chan_out.buffers());
    // Write output
    for (int i = 0; i < kFrames; i++) {
        cout << "--- frame " << i << " ---" << endl;
        for (int r = 0; r < max_nouts_rate; r++) {
            for (int chan = 0; chan < nouts; chan++) {
                if (r < nouts_rate[chan]) {
                    printf("%8f\t", chan_out.buffers()[chan][i * nouts_rate[chan] + r]);
                } else {
                    printf("\t");
                }
            }
            printf("\n");
        }
    }

	return 0;
}

/********************END ARCHITECTURE SECTION (part 2/2)****************/

