/************************************************************************
 ************************************************************************
    FAUST compiler
    Copyright (C) 2022 GRAME, Centre National de Creation Musicale
    ---------------------------------------------------------------------
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 ************************************************************************
 ************************************************************************/

#include <regex>
#include <string>

#include "Text.hh"
#include "exception.hh"
#include "fir_function_builder.hh"
#include "floats.hh"
#include "global.hh"
#include "jsfx_code_container.hh"

using namespace std;

map<string, bool> JSFXInstVisitor::gFunctionSymbolTable;

dsp_factory_base* JSFXCodeContainer::produceFactory()
{
    return new text_dsp_factory_aux(
        fKlassName, "", "",
        ((dynamic_cast<ostringstream*>(fOut)) ? dynamic_cast<ostringstream*>(fOut)->str() : ""),
        "");
}

JSFXCodeContainer::JSFXCodeContainer(const string& name, int numInputs, int numOutputs,
                                     ostream* out)
{
    // Mandatory
    initialize(numInputs, numOutputs);
    fKlassName = name;
    fOut       = out;

    // Allocate one static visitor to be shared by main module and sub containers
    if (!gGlobal->gJSFXVisitor) {
        gGlobal->gJSFXVisitor = new JSFXInstVisitor(out, name);
    }
}

CodeContainer* JSFXCodeContainer::createScalarContainer(const string& name, int sub_container_type)
{
    return new JSFXScalarCodeContainer(name, 0, 1, fOut, sub_container_type);
}

CodeContainer* JSFXCodeContainer::createContainer(const string& name, int numInputs, int numOutputs,
                                                  ostream* dst)
{
    CodeContainer* container;

    if (gGlobal->gOpenCLSwitch) {
        throw faustexception("ERROR : OpenCL not supported for JSFX\n");
    }
    if (gGlobal->gCUDASwitch) {
        throw faustexception("ERROR : CUDA not supported for JSFX\n");
    }

    if (gGlobal->gOpenMPSwitch) {
        throw faustexception("ERROR : OpenMP not supported for JSFX\n");
    } else if (gGlobal->gSchedulerSwitch) {
        throw faustexception("ERROR : Scheduler not supported for JSFX\n");
    } else if (gGlobal->gVectorSwitch) {
        throw faustexception("ERROR : Vector not supported for JSFX\n");
    } else {
        container = new JSFXScalarCodeContainer(name, numInputs, numOutputs, dst, kInt);
    }

    return container;
}

// Used for subcontainers if 'inlining sub containers' model is not used
void JSFXCodeContainer::produceInternal()
{
}

/*
 Given as an example of what a real backend would have to do: add or remove FIR visiting code etc.
*/
void JSFXCodeContainer::produceClass()
{
    // Necessary to set precision and use << fixed << num to write literal float numbers since JSFX
    // doesn't allow 0.5f or exponent 9.58738e-05
    fOut->precision(numeric_limits<double>::digits10 + 1);
    int n = 0;

    *fOut << "/*\n"
          << " * Generated by 'faust " << gGlobal->printCompilationOptions1() << "'\n"
          << " */\n";

    *fOut << "desc: Effect " << fKlassName << "\n";

    produceMetadata(n);

    // buildUserInterface > Sliders buttons and checkboxes only are available on JSFX
    generateUserInterface(gGlobal->gJSFXVisitor);

    // inputs/outputs
    tab(n, *fOut);
    if (fNumInputs > 64 && (gGlobal->gOutputLang != "jsfx-test")) {
        throw(faustexception("ERROR : JSFX format does not support more than 64 inputs\n"));
    }
    if (fNumOutputs > 64 && (gGlobal->gOutputLang != "jsfx-test")) {
        throw(faustexception("ERROR : JSFX format does not support more than 64 outputs\n"));
    }
    for (int i = 0; i < fNumInputs; i++) {
        *fOut << "in_pin:input" << std::to_string(i) << "\n";
    }
    for (int i = 0; i < fNumOutputs; i++) {
        *fOut << "out_pin:output" << std::to_string(i) << "\n";
    }
    tab(n, *fOut);

    // Generate utility functions
    *fOut << "@init" << endl;

    gGlobal->gJSFXVisitor->generateInit();

    *fOut << "\n"
          << "// GLOBAL Functions \n"
          << "\n";
    *fOut << "/*\n"
          << " * Initialise memory allocator \n"
          << " */\n"
          << "function init_memory() instance(index) (\n"
          << " index = 0;\n"
          << ");\n";
    *fOut << "/*\n"
          << " *Allocate memory \n"
          << " */\n"
          << "function alloc_memory(amount) instance(index) local(i) (\n"
          << " i = index; \n"
          << " index += amount; \n"
          << " i;\n"
          << ");\n";

    *fOut << "MEMORY.init_memory();\n\n";

    *fOut << "function zeros(address, amount) (\n"
          << " i = 0;\n"
          << " loop(amount, address[i] = 0; i+=1;);\n"
          << " address;\n"
          << ");\n";

    // Int32 casting functions (JSFX only works with double precision floats)
    *fOut << "function int32(x) ( \n"
             " x >= 2147483648 ? x - 4294967296 : x; \n"
             "); \n"

             "function uint32(x) ( \n"
             " x < 0 ? x + 4294967296 : x; \n"
             "); \n"

             "function add32(x, y) ( \n"
             " (x += y) >= 4294967296 ? x - 4294967296 : x; \n"
             "); \n"

             "function sub32(x, y) ( \n"
             " (x -= y) < 0 ? x + 4294967296 : x; \n"
             "); \n"

             "function mul32(x, y) local(a, b) ( \n"
             " x = uint32(x); \n"
             " y = uint32(y); \n"
             " a = x & 0xFFFF; \n"
             " b = y & 0xFFFF; \n"
             " y = ((((y / 65536)|0) * a + ((x / 65536)|0) * b) & 0xFFFF) * 65536 + a * b; \n"
             " y >= 4294967296 ? y - 4294967296 : y; \n"
             "); \n"

             "function ftoi32(x) ( \n"
             " x <= -1 ? ((min(max(-2147483648, x), -1)|0) + 4294967296;) : (min(max(0, x), "
             "4294967295)|0;); \n"
             ");"

             // Other utility functions
             "function limit(x, min, max) (\n"
             "   (x < min) ? min : (x > max) ? max : x; \n"
             ");\n"

             "function midi_scale(x, min, max, step) (\n"
             "   diff = (max - min);\n"
             "   min + (diff * (x / 127));\n"
             ");\n"
             "function mtof(mid) (\n"
             "   440 * exp(0.0577622645 * (mid - 69));\n"
             ");\n";

    // Maths missing functions (required by Faust, but not in JSFX natively)
    *fOut << "/*\n"
          << " * Mathematical functions \n"
          << " */\n"
          << "function exp2(arg) (pow(2, n)); \n"
          << "function exp10(arg) (pow(10, n)); \n"
          << "function log2(x) (log(x) / log(2)); \n"
          << "function round(N) (0|(N+sign(N)*0.5)); \n"
          << "function rint(x) (round(int32(x))); \n"
          << "function mod(a,b) (a%b); \n"
          << "function remainder(x,y) (\n"
          << " x - (round(x/y)*y);\n"
          << ");\n"
          << "function fmod(x,y) ( \n"
          << " y = abs(y); \n"
          << " res = remainder(abs(x), y); \n"
          << " (res < 0) ? res += y; \n"
          << " (x < 0) ? -res : res; \n"
          << ");\n";

    *fOut << "/* \n"
          << " * MIDI Constants \n"
          << " */ \n"
          << "CC = 0xB0; \n"
          << "NOTE_ON = 0x90; \n"
          << "NOTE_OFF = 0x80; \n"
          << "BEND = 0xE0; \n"
          << "PGM_CHANGE = 0xC0; \n";

    tab(n, *fOut);

    mergeSubContainers();

    // Setting in/outs and global variables
    *fOut << "num_inputs = " << fNumInputs << ";\n"
          << "num_outputs = " << fNumOutputs << ";\n\n";
    *fOut << "nvoices = " << nvoices << ";\n";
    *fOut << "voice_idx = 0; \n";
    gGlobal->gJSFXVisitor->Tab(n);
    JSFXInitFieldsVisitor initializer(fOut, n);
    for (const auto& it : fGlobalDeclarationInstructions->fCode) {
        if (dynamic_cast<DeclareVarInst*>(it)) {
            it->accept(&initializer);
        }
    }

    // If polyphonic & voice stealing : generate the voice stealing array
    if (poly && gGlobal->gJSFXVisitor->mode == JSFXMIDIVoiceMode::voice_steal) {
        tab(n, *fOut);
        *fOut << "voices_arr = MEMORY.alloc_memory(" << nvoices << ");\n";
        for (int i = 0; i < nvoices; ++i) {
            *fOut << "voices_arr[" << i << "] = " << i << ";\n";
        }
        *fOut << "function sort_voices(n) (\n"
                 "   cnt = nvoices-1;\n"
                 "   while(cnt > 0) (\n"
                 "       voices_arr[cnt] = voices_arr[cnt - 1];\n"
                 "       cnt -= 1;\n"
                 "   );\n"
                 "   voices_arr[0] = n;\n"
                 ");\n"
                 "function get_oldest_voice() (\n"
                 "   voices_arr[nvoices-1];\n"
                 ");\n";
    }

    // This function is used to retrieve a voice
    tab(n, *fOut);
    *fOut << "function get_dsp(index) (\n"
             "  dsp.memory + dsp.size * index \n"
             ");\n";

    // Generate class layout : in JSFX, classes do not exist natively.
    // Instead, we use indexes that represent the position of members in memory, relatively to
    // object address. dsp.memory represents the position of first object dsp.size is the total size
    // of object (1 = 64 bits) Other dsp.fields are members position in object
    int total_size = 0;
    tab(n, *fOut);
    *fOut << "// DSP struct memory layout \n";
    *fOut << "dsp.memory = MEMORY.alloc_memory(0);\n";
    string            class_decl;
    StructInstVisitor struct_visitor;
    fDeclarationInstructions->accept(&struct_visitor);
    for (const auto& it : fDeclarationInstructions->fCode) {
        auto desc = struct_visitor.getMemoryDesc(it->getName());
        class_decl += "dsp." + it->getName() + " = " + std::to_string(total_size) + ";\n";
        total_size += desc.fSize;
    }
    for (const auto& it : fComputeBlockInstructions->fCode) {
        string name = it->getName();
        if (strfind(name, "output") || strfind(name, "input")) {
            continue;
        }
        class_decl += "dsp." + it->getName() + " = " + std::to_string(total_size) + ";\n";
        total_size++;
    }

    // These special fields dsp.key_id and dsp.gate are added in polyphonic context.
    // They are used to know whether a noteoff can close a playing note (if same key and gate == 1)
    if (poly) {
        *fOut << "// Two identifiers to know which noteoff goes to which voice \n";
        class_decl += "dsp.key_id = " + std::to_string(total_size) + ";\n";
        total_size++;
        class_decl += "dsp.gate = " + std::to_string(total_size) + ";\n";
        total_size++;
    }

    *fOut << "dsp.size = " << std::to_string(total_size + fNumOutputs) << ";\n";
    *fOut << class_decl;
    for (int i = 0; i < fNumOutputs; i++) {
        *fOut << "dsp.output" << i << " = " << ++total_size - 1 << "; \n";
    }

    // create_instances is used to allocate memory for instances and initialize members to zero
    *fOut << "\n";
    *fOut << "function create_instances() (";
    tab(n + 1, *fOut);
    *fOut << "voice_idx = 0;";
    tab(n + 1, *fOut);
    *fOut << "while(voice_idx < nvoices) (";
    tab(n + 2, *fOut);
    *fOut << "obj = MEMORY.alloc_memory(dsp.size);";
    initializer.fTab += 2;
    generateDeclarations(&initializer);
    tab(n + 2, *fOut);
    *fOut << "voice_idx += 1; \n\t);\n);\n\n";

    // init_instances is used to initialize members to their init (or constant) values
    // Both create_instances and init_instances are placed in the @init section
    // In JSFX, @init is called at first load of plugin, and every time a new context is required by
    // host (Reaper)
    *fOut << "function init_instances() (";
    tab(n + 1, *fOut);
    gGlobal->gJSFXVisitor->generatePolyInit();
    tab(n + 1, *fOut);
    *fOut << "voice_idx = 0; ";
    tab(n + 1, *fOut);
    *fOut << "while(voice_idx < nvoices) ( ";
    tab(n + 2, *fOut);
    *fOut << "obj = dsp.memory + dsp.size * voice_idx;";
    tab(n + 2, *fOut);
    gGlobal->gJSFXVisitor->Tab(n + 2);
    generateClear(gGlobal->gJSFXVisitor);

    inlineSubcontainersFunCalls(fStaticInitInstructions)->accept(gGlobal->gJSFXVisitor);
    tab(n + 2, *fOut);
    inlineSubcontainersFunCalls(fInitInstructions)->accept(gGlobal->gJSFXVisitor);

    *fOut << "voice_idx += 1;";
    tab(n + 1, *fOut);
    *fOut << "); \n);\n";

    tab(n, *fOut);
    *fOut << "create_instances();";
    tab(n, *fOut);
    *fOut << "init_instances();";
    tab(n, *fOut);

    generateCompute(n);
    tab(n, *fOut);
}

void JSFXCodeContainer::produceMetadata(int tabs)
{
    // Here, we retrieve two kind of metadata :
    //  * Description fields are parsed to be translated in JSFX description fields
    //  * MIDI metadata like ["midi:on"] is used to activate MIDI, while ["nvoices:N"] indicates the
    //  polyphonic number of voices
    // Note that ["nvoices:1"] is useful to create a monophonic instrument that can respond to midi
    // key numbers
    for (const auto& i : gGlobal->gMetaDataSet) {
        if (i.first == tree("options")) {
            for (set<Tree>::iterator j = i.second.begin(); j != i.second.end(); j++) {
                stringstream ss;
                ss << **j;
                string s;
                ss >> s;
                if (strfind(s, "[midi:on]")) {
                    midi = true;
                }
                if (s.find("[nvoices:")) {
                    regex r("\\[nvoices:([0-9]+)\\]");
                    for (sregex_iterator i = sregex_iterator(s.begin(), s.end(), r);
                         i != sregex_iterator(); ++i) {
                        poly          = true;
                        midi          = true;
                        std::smatch m = *i;
                        nvoices       = std::stoi(m[1].str());
                        if (nvoices < 1) {
                            throw(faustexception("ERROR : nvoices must be >= to 1"));
                        }
                    }
                    gGlobal->gJSFXVisitor->poly    = poly;
                    gGlobal->gJSFXVisitor->midi    = midi;
                    gGlobal->gJSFXVisitor->nvoices = nvoices;
                }
                *fOut << "desc: " << *(i.first) << " " << **j << "\n";
            }
        } else {
            // But the "author" meta data is accumulated, the upper level becomes the main author
            // and sub-levels become "contributor"
            for (set<Tree>::iterator j = i.second.begin(); j != i.second.end(); j++) {
                if (j == i.second.begin()) {
                    *fOut << "desc: " << *(i.first) << " " << **j << "\n";
                } else {
                    *fOut << "desc: "
                          << "contributor"
                          << " " << **j << "\n";
                }
            }
        }
    }
    tab(tabs, *fOut);
    *fOut << endl;
}

// Scalar
JSFXScalarCodeContainer::JSFXScalarCodeContainer(const string& name, int numInputs, int numOutputs,
                                                 ostream* out, int sub_container_type)
    : JSFXCodeContainer(name, numInputs, numOutputs, out)
{
    fSubContainerType = sub_container_type;
}

// Given as an example of what a real backend would have to implement.
void JSFXScalarCodeContainer::generateCompute(int n)
{
    // The control function is called from @slider section (every time a slider is moved)
    // as well as @block if MIDI is on (each time a MIDI event happens)
    tab(n, *fOut);
    *fOut << "function control() (";
    gGlobal->gJSFXVisitor->Tab(n + 1);
    // Empty functions are not allowed, so generate a dummy line
    tab(n + 1, *fOut);
    *fOut << "voice_idx = 0;";
    tab(n + 1, *fOut);
    *fOut << "while(voice_idx < nvoices) (";
    gGlobal->gJSFXVisitor->Tab(n + 2);
    tab(n + 2, *fOut);
    *fOut << "obj = dsp.memory + dsp.size * voice_idx;";
    tab(n + 2, *fOut);
    gGlobal->gJSFXVisitor->midi_poly_assign_sliders();
    generateComputeBlock(gGlobal->gJSFXVisitor);
    *fOut << "voice_idx += 1;";
    tab(n + 1, *fOut);
    *fOut << ");";
    tab(n, *fOut);
    *fOut << ");";
    tab(n, *fOut);

    // @block section is only used for MIDI events (inputs)
    if (gGlobal->gJSFXVisitor->hasMIDIInstructions()) {
        tab(n, *fOut);
        *fOut << "@block";
        tab(n, *fOut);
        gGlobal->gJSFXVisitor->Tab(n);
        gGlobal->gJSFXVisitor->generateMIDIInstructions();
        tab(n, *fOut);
    }

    // We want to filter if an event occurs to avoid compute control if not needed
    if (midi || poly) {
        *fOut << "(midi_event > 0) ? (control());\n";
    }

    // @slider section is called when a control (slider, button, checkbutton) has changed its value
    // It is also called at startup
    tab(n, *fOut);
    *fOut << "@slider";
    tab(n, *fOut);
    *fOut << "control();";
    tab(n, *fOut);

    // @serialize section is used to prevent init values (in @init section) to override the
    // serialized value It is here just to be non-empty, as described in JSFX documentation
    tab(n, *fOut);
    *fOut << "@serialize";
    tab(n, *fOut);
    *fOut << "(file_avail(0) < 0);";
    tab(n, *fOut);

    // @sample section is the audio loop.
    // Note that the @sample code represents 1 sample computation (it is internally called several
    // times per vector to match vector size) It will process all internal computations for each
    // voice and push the output results in the object output variables
    tab(n, *fOut);
    *fOut << "@sample";
    tab(n, *fOut);
    *fOut << "voice_idx = 0;";
    tab(n, *fOut);
    *fOut << "while(voice_idx < nvoices) (";
    gGlobal->gJSFXVisitor->Tab(n + 1);
    tab(n + 1, *fOut);
    *fOut << "obj = dsp.memory + dsp.size * voice_idx;";
    tab(n + 1, *fOut);
    SimpleForLoopInst* loop = fCurLoop->generateSimpleScalarLoop(fFullCount);
    loop->accept(gGlobal->gJSFXVisitor);
    *fOut << "voice_idx += 1;";
    tab(n, *fOut);
    *fOut << ");";
    // Then, output members are summed in spl0, spl1 (...) which are the actual JSFX outputs
    for (int i = 0; i < fNumOutputs; ++i) {
        tab(n, *fOut);
        *fOut << "spl" << i << " = ";
        for (int v = 0; v < nvoices; ++v) {
            *fOut << "get_dsp(" << v << ")[dsp.output" << i << "]";
            if (v < (nvoices - 1)) {
                *fOut << " + ";
            }
        }
        *fOut << ";";
    }
    tab(n, *fOut);
}
