/************************************************************************
 ************************************************************************
    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.
 ************************************************************************
 ************************************************************************/

#ifndef _JSFX_INSTRUCTIONS_H
#define _JSFX_INSTRUCTIONS_H

#include <regex>
#include <string>
#include <unordered_map>

#include "struct_manager.hh"
#include "text_instructions.hh"

inline std::string inlineInt32(double fnum)
{
    return std::to_string(int((fnum > 2147483648.) ? (fnum - 4294967296.) : fnum));
}

inline bool strfind(const std::string& str, const std::string& substr)
{
    return (str.find(substr) != str.npos);
}

inline bool exact_match(const std::string& str, const std::string& cmp)
{
    return str == cmp;
}

// Visitor used to initialize array fields into the DSP structure
struct JSFXInitFieldsVisitor : public DispatchVisitor {
    std::ostream* fOut;
    int           fTab;
    // The name of the currently generated array
    std::string fCurArray;

    JSFXInitFieldsVisitor(std::ostream* out, int tab = 0) : fOut(out), fTab(tab) {}

    virtual void visit(DeclareVarInst* inst)
    {
        ArrayTyped* array_type = dynamic_cast<ArrayTyped*>(inst->fType);
        if (array_type) {
            tab(fTab, *fOut);
            if (inst->fValue) {
                fCurArray = inst->getName();
                *fOut << fCurArray << " = MEMORY.alloc_memory(" << array_type->fSize << ");";
                inst->fValue->accept(this);
            } else {
                if (inst->fAddress->isStruct()) {
                    ZeroInitializer(fOut, inst->fType, inst->getName());
                } else {
                    inst->fAddress->accept(this);
                    *fOut << " = ";
                    MemoryAllocInitializer(fOut, inst->fType);
                }
                *fOut << ";";
            }
        }
    }

    virtual void visit(NamedAddress* named)
    {
        if (named->isStruct()) {
            *fOut << "obj[dsp.";
        }
        *fOut << named->fName;
        if (named->isStruct()) {
            *fOut << "]";
        }
    }

    static void MemoryAllocInitializer(std::ostream* fOut, Typed* typed)
    {
        ArrayTyped* array_type = dynamic_cast<ArrayTyped*>(typed);
        faustassert(array_type);
        *fOut << "MEMORY.alloc_memory(" << array_type->fSize << ")";
    }
    static void ZeroInitializer(std::ostream* fOut, Typed* typed, const std::string& name)
    {
        ArrayTyped* array_type = dynamic_cast<ArrayTyped*>(typed);
        faustassert(array_type);
        *fOut << "memset( obj + dsp." << name << ", 0, " << array_type->fSize << ")";
    }

    // Needed for waveforms
    virtual void visit(Int32ArrayNumInst* inst)
    {
        for (size_t i = 0; i < inst->fNumTable.size(); i++) {
            *fOut << fCurArray << "[" << i << "] = " << inlineInt32(inst->fNumTable[i]) << ";\n";
        }
    }

    virtual void visit(FloatArrayNumInst* inst)
    {
        for (size_t i = 0; i < inst->fNumTable.size(); i++) {
            *fOut << fCurArray << "[" << i << "] = " << std::fixed << inst->fNumTable[i] << ";\n";
        }
    }

    virtual void visit(DoubleArrayNumInst* inst)
    {
        for (size_t i = 0; i < inst->fNumTable.size(); i++) {
            *fOut << fCurArray << "[" << i << "] = " << std::fixed << inst->fNumTable[i] << ";\n";
        }
    }
};

struct JSFXMidiInstr {
    JSFXMidiInstr(const std::string& type_, const std::string& vname_, int nbr_, int channel_ = -1)
        : type_name(type_), variable_name(vname_), nbr(nbr_), channel(channel_)
    {
    }

    std::string type_name;
    std::string variable_name;
    int         nbr;
    int         channel = -1;
};

// MIDI mode
enum class JSFXMIDIVoiceMode {
    voice_block = 0,
    voice_steal = 1,
};

// Different scaling algorithm for different MIDI values
enum class JSFXMIDIScaleType { key = 0, freq = 1, gain = 2, veloc = 3, gate = 4, none = 5 };

struct JSFXMidiScale {
    double            init, min, max, step;
    JSFXMIDIScaleType type = JSFXMIDIScaleType::none;

    double limit(double val)
    {
        if (val > max) {
            return max;
        }
        if (val < min) {
            return min;
        }
        return val;
    }

    double scale(int val)
    {
        // val is an int between 0 and 127
        double incr = step * int(val);
        return limit(min + incr);
    }

    static JSFXMidiScale MIDI7bScale(int ini, JSFXMIDIScaleType t)
    {
        JSFXMidiScale s = {double(ini), 0, 127, 1, t};
        double        i = s.limit(s.init);
        s.init          = i;
        return s;
    }
};

/*
    A subclass of TextInstVisitor that implements a lot of generic behaviors.
    Some methods mays have to be redefined in this class, anf the exposed list
    of them is given as an example, to be adapted in the real case.
*/
class JSFXInstVisitor : public TextInstVisitor {
   private:
    /*
     Global functions names table as a static variable in the visitor
     so that each function prototype is generated as most once in the module.
     */
    static std::map<std::string, bool> gFunctionSymbolTable;
    // Polymorphic math functions
    std::map<std::string, std::string> gPolyMathLibTable;

    // Global count for sliders
    size_t slider_count = 0;

    // Skip slider is used for MIDI instructions, that correspond to Sliders in Faust.
    // In JSFX, since the number of sliders is limited to 64, it has been decided not to make it
    // correspond to a slider.
    bool                          skip_slider = false;
    std::map<std::string, double> _slider_init_map;
    std::map<std::string, double> _poly_init_map;

    std::unordered_map<std::string, std::string>   _midi_instructions;
    std::unordered_map<std::string, std::string>   _midi_sliders;
    std::unordered_map<std::string, JSFXMidiScale> _midi_scales;

   public:
    // MIDI polyphonic mode
    bool poly                 = false;
    bool midi                 = false;
    bool next_control_is_midi = false;
    int  nvoices              = 1;
    // If polyphonic, voice_stealing is default
    JSFXMIDIVoiceMode mode = JSFXMIDIVoiceMode::voice_steal;
    using TextInstVisitor::visit;

    JSFXInstVisitor(std::ostream* out, const std::string& struct_name, int tab = 0)
        : TextInstVisitor(out, ".", new JSFXStringTypeManager(xfloat(), "*", struct_name), tab)
    {
        // Mark all math.h functions as generated...
        gFunctionSymbolTable["abs"] = true;

        gFunctionSymbolTable["max_i"] = true;
        gFunctionSymbolTable["min_i"] = true;

        gFunctionSymbolTable["max_f"] = true;
        gFunctionSymbolTable["min_f"] = true;

        gFunctionSymbolTable["max_"] = true;
        gFunctionSymbolTable["min_"] = true;

        gFunctionSymbolTable["max_l"] = true;
        gFunctionSymbolTable["min_l"] = true;

        // Float version
        gFunctionSymbolTable["fabsf"]      = true;
        gFunctionSymbolTable["acosf"]      = true;
        gFunctionSymbolTable["asinf"]      = true;
        gFunctionSymbolTable["atanf"]      = true;
        gFunctionSymbolTable["atan2f"]     = true;
        gFunctionSymbolTable["ceilf"]      = true;
        gFunctionSymbolTable["cosf"]       = true;
        gFunctionSymbolTable["expf"]       = true;
        gFunctionSymbolTable["exp10f"]     = true;
        gFunctionSymbolTable["floorf"]     = true;
        gFunctionSymbolTable["fmodf"]      = true;
        gFunctionSymbolTable["logf"]       = true;
        gFunctionSymbolTable["log10f"]     = true;
        gFunctionSymbolTable["powf"]       = true;
        gFunctionSymbolTable["remainderf"] = true;
        gFunctionSymbolTable["rintf"]      = true;
        gFunctionSymbolTable["roundf"]     = true;
        gFunctionSymbolTable["sinf"]       = true;
        gFunctionSymbolTable["sqrtf"]      = true;
        gFunctionSymbolTable["tanf"]       = true;

        // Hyperbolic
        gFunctionSymbolTable["acoshf"] = false;
        gFunctionSymbolTable["asinhf"] = false;
        gFunctionSymbolTable["atanhf"] = false;
        gFunctionSymbolTable["coshf"]  = false;
        gFunctionSymbolTable["sinhf"]  = false;
        gFunctionSymbolTable["tanhf"]  = false;

        // Double version
        gFunctionSymbolTable["fabs"]      = true;
        gFunctionSymbolTable["acos"]      = true;
        gFunctionSymbolTable["asin"]      = true;
        gFunctionSymbolTable["atan"]      = true;
        gFunctionSymbolTable["atan2"]     = true;
        gFunctionSymbolTable["ceil"]      = true;
        gFunctionSymbolTable["cos"]       = true;
        gFunctionSymbolTable["exp"]       = true;
        gFunctionSymbolTable["exp10"]     = true;
        gFunctionSymbolTable["floor"]     = true;
        gFunctionSymbolTable["fmod"]      = true;
        gFunctionSymbolTable["log"]       = true;
        gFunctionSymbolTable["log10"]     = true;
        gFunctionSymbolTable["pow"]       = true;
        gFunctionSymbolTable["remainder"] = true;
        gFunctionSymbolTable["rint"]      = true;
        gFunctionSymbolTable["round"]     = true;
        gFunctionSymbolTable["sin"]       = true;
        gFunctionSymbolTable["sqrt"]      = true;
        gFunctionSymbolTable["tan"]       = true;

        // Hyperbolic
        gFunctionSymbolTable["acosh"] = false;
        gFunctionSymbolTable["asinh"] = false;
        gFunctionSymbolTable["atanh"] = false;
        gFunctionSymbolTable["coshf"] = false;
        gFunctionSymbolTable["sinh"]  = false;
        gFunctionSymbolTable["tanh"]  = false;

        // Quad version
        gFunctionSymbolTable["fabsl"]      = false;
        gFunctionSymbolTable["acosl"]      = false;
        gFunctionSymbolTable["asinl"]      = false;
        gFunctionSymbolTable["atanl"]      = false;
        gFunctionSymbolTable["atan2l"]     = false;
        gFunctionSymbolTable["ceill"]      = false;
        gFunctionSymbolTable["cosl"]       = false;
        gFunctionSymbolTable["expl"]       = false;
        gFunctionSymbolTable["exp10l"]     = false;
        gFunctionSymbolTable["floorl"]     = false;
        gFunctionSymbolTable["fmodl"]      = false;
        gFunctionSymbolTable["logl"]       = false;
        gFunctionSymbolTable["log10l"]     = false;
        gFunctionSymbolTable["powl"]       = false;
        gFunctionSymbolTable["remainderl"] = false;
        gFunctionSymbolTable["rintl"]      = false;
        gFunctionSymbolTable["roundl"]     = false;
        gFunctionSymbolTable["sinl"]       = false;
        gFunctionSymbolTable["sqrtl"]      = false;
        gFunctionSymbolTable["tanl"]       = false;

        // Hyperbolic
        gFunctionSymbolTable["acoshl"] = false;
        gFunctionSymbolTable["asinhl"] = false;
        gFunctionSymbolTable["atanhl"] = false;
        gFunctionSymbolTable["coshl"]  = false;
        gFunctionSymbolTable["sinhl"]  = false;
        gFunctionSymbolTable["tanhl"]  = false;

        // Polymath mapping int version
        gPolyMathLibTable["abs"]   = "abs";
        gPolyMathLibTable["max_i"] = "max";
        gPolyMathLibTable["min_i"] = "min";

        // Polymath mapping float version
        gPolyMathLibTable["max_f"] = "max";
        gPolyMathLibTable["min_f"] = "min";

        gPolyMathLibTable["fabsf"]      = "abs";
        gPolyMathLibTable["acosf"]      = "acos";
        gPolyMathLibTable["asinf"]      = "asin";
        gPolyMathLibTable["atanf"]      = "atan";
        gPolyMathLibTable["atan2f"]     = "atan";
        gPolyMathLibTable["ceilf"]      = "ceil";
        gPolyMathLibTable["cosf"]       = "cos";
        gPolyMathLibTable["expf"]       = "exp";
        gPolyMathLibTable["exp2f"]      = "exp2";
        gPolyMathLibTable["exp10f"]     = "exp10";
        gPolyMathLibTable["floorf"]     = "floor";
        gPolyMathLibTable["fmodf"]      = "fmod";
        gPolyMathLibTable["logf"]       = "log";
        gPolyMathLibTable["log2f"]      = "log2";
        gPolyMathLibTable["log10f"]     = "log10";
        gPolyMathLibTable["powf"]       = "pow";
        gPolyMathLibTable["remainderf"] = "remainder";
        gPolyMathLibTable["rintf"]      = "rint";
        gPolyMathLibTable["roundf"]     = "round";
        gPolyMathLibTable["sinf"]       = "sin";
        gPolyMathLibTable["sqrtf"]      = "sqrt";
        gPolyMathLibTable["tanf"]       = "tan";

        // Hyperbolic - Not available in JSFX yet
        /*
        gPolyMathLibTable["acoshf"]     = "acosh";
        gPolyMathLibTable["asinhf"]     = "asinh";
        gPolyMathLibTable["atanhf"]     = "atanh";
        gPolyMathLibTable["coshf"]      = "cosh";
        gPolyMathLibTable["sinhf"]      = "sinh";
        gPolyMathLibTable["tanhf"]      = "tanh";

        gPolyMathLibTable["isnanf"]     = "isnan";
        gPolyMathLibTable["isinff"]     = "isinf";
        gPolyMathLibTable["copysignf"]  = "copysign";
        */

        // Polymath mapping double version
        gPolyMathLibTable["max_"] = "max";
        gPolyMathLibTable["min_"] = "min";

        gPolyMathLibTable["fabs"]      = "abs";
        gPolyMathLibTable["acos"]      = "acos";
        gPolyMathLibTable["asin"]      = "asin";
        gPolyMathLibTable["atan"]      = "atan";
        gPolyMathLibTable["atan2"]     = "atan";
        gPolyMathLibTable["ceil"]      = "ceil";
        gPolyMathLibTable["cos"]       = "cos";
        gPolyMathLibTable["exp"]       = "exp";
        gPolyMathLibTable["exp2"]      = "exp2";
        gPolyMathLibTable["exp10"]     = "exp10";
        gPolyMathLibTable["floor"]     = "floor";
        gPolyMathLibTable["fmod"]      = "fmod";
        gPolyMathLibTable["log"]       = "log";
        gPolyMathLibTable["log2"]      = "log2";
        gPolyMathLibTable["log10"]     = "log10";
        gPolyMathLibTable["pow"]       = "pow";
        gPolyMathLibTable["remainder"] = "remainder";
        gPolyMathLibTable["rint"]      = "rint";
        gPolyMathLibTable["round"]     = "round";
        gPolyMathLibTable["sin"]       = "sin";
        gPolyMathLibTable["sqrt"]      = "sqrt";
        gPolyMathLibTable["tan"]       = "tan";

        // Hyperbolic - Not available in JSFX yet
        /*
        gPolyMathLibTable["acosh"]     = "acosh";
        gPolyMathLibTable["asinh"]     = "asinh";
        gPolyMathLibTable["atanh"]     = "atanh";
        gPolyMathLibTable["cosh"]      = "cosh";
        gPolyMathLibTable["sinh"]      = "sinh";
        gPolyMathLibTable["tanh"]      = "tanh";

        gPolyMathLibTable["isnan"]     = "isnan";
        gPolyMathLibTable["isinf"]     = "isinf";
        gPolyMathLibTable["copysign"]  = "copysign";
        */
    }

    virtual ~JSFXInstVisitor() {}

    void generateInit()
    {
        for (auto& it : _slider_init_map) {
            *fOut << it.first << " = " << it.second << ";";
            EndLine(' ');
        }
    };

    void generatePolyInit()
    {
        for (auto& it : _poly_init_map) {
            *fOut << "obj[dsp." << it.first << "] = " << it.second << ";";
            tab(fTab + 1, *fOut);
        }
    }

    // Extract midi parameters (number, channel) from Metadata
    JSFXMidiInstr parseMIDIInstruction(const std::string& fzone, const std::string& value)
    {
        std::stringstream ss;

        /* Storing the whole string into string stream */
        ss << value;

        std::string type;
        ss >> type;
        /* Running loop till the end of the stream */
        std::string         temp;
        int                 found  = 0;
        std::pair<int, int> res    = std::make_pair(0, -1);
        int                 nfound = 0;
        while (!ss.eof()) {
            /* extracting word by word from stream */
            ss >> temp;

            /* Checking the given word is integer or not */
            if (std::stringstream(temp) >> found) {
                if (nfound == 0) {
                    res.first = found;
                    nfound++;
                } else {
                    res.second = found;
                }
            }

            /* To save from space at the end of string */
            temp = "";
        }
        if (type == "pgm") {
            return JSFXMidiInstr(type, fzone, -1, res.first);
        }
        return JSFXMidiInstr(type, fzone, res.first, res.second);
    }

    virtual void visit(AddMetaDeclareInst* inst)
    {
        // Deactivated for now
        if (inst->fKey == "midi") {
            _midi_instructions[inst->fZone] = inst->fValue;
            std::string name                = inst->fValue;
            if (poly && (strfind(name, "gate") || strfind(name, "freq") || strfind(name, "gain"))) {
                skip_slider = true;
            } else {
                next_control_is_midi = true;
            }
            // else
        }
    }

    void midi_poly_assign_sliders()
    {
        // Maybe add a return in non polyphonic or non midi contexts
        for (auto& it : _midi_sliders) {
            *fOut << "obj[dsp." << it.first << "] = " << it.first;
            EndLine();
        }
    }

    void _midi_poly_assign()
    {
        for (const auto& it : _midi_scales) {
            switch (it.second.type) {
                case JSFXMIDIScaleType::freq:
                    tab(fTab + 2, *fOut);
                    *fOut << "obj[dsp." << it.first << "] = limit(mtof(msg2), " << it.second.min
                          << ", " << it.second.max << ");";
                    break;
                case JSFXMIDIScaleType::key:
                    tab(fTab + 2, *fOut);
                    *fOut << "obj[dsp." << it.first << "] = msg2;";
                    break;
                case JSFXMIDIScaleType::gain:
                    tab(fTab + 2, *fOut);
                    *fOut << "obj[dsp." << it.first << "] = ";
                    *fOut << "midi_scale(msg3, " << it.second.min << ", " << it.second.max << ", "
                          << it.second.step << ");";
                    break;
                case JSFXMIDIScaleType::veloc:
                    tab(fTab + 2, *fOut);
                    *fOut << "obj[dsp." << it.first << "] = msg3;";
                    break;
                case JSFXMIDIScaleType::gate:
                    tab(fTab + 2, *fOut);
                    *fOut << "obj[dsp." << it.first << "] = 1;";
                    break;
                case JSFXMIDIScaleType::none:
                    continue;
            }
        }
    }

    void _voices_stealing_impl()
    {
        tab(fTab + 1, *fOut);
        *fOut << "(status == NOTE_ON) ? ( ";
        tab(fTab + 2, *fOut);
        *fOut << "midi_event += 1;";
        tab(fTab + 2, *fOut);
        *fOut << "voice_idx = get_oldest_voice();";
        tab(fTab + 2, *fOut);
        *fOut << "sort_voices(voice_idx);";
        tab(fTab + 2, *fOut);
        *fOut << "obj = get_dsp(voice_idx);";
        tab(fTab + 2, *fOut);
        *fOut << "obj[dsp.key_id] = msg2;";
        tab(fTab + 2, *fOut);
        *fOut << "obj[dsp.gate] = 1;";
        _midi_poly_assign();
        tab(fTab + 1, *fOut);
        *fOut << "); // NOTE ON condition off ";
        tab(fTab + 1, *fOut);
        *fOut << "(status == NOTE_OFF) ? (";
        tab(fTab + 2, *fOut);
        *fOut << "midi_event += 1;";
        tab(fTab + 2, *fOut);
        *fOut << "voice_idx = 0;";
        tab(fTab + 2, *fOut);
        *fOut << "while(voice_idx < nvoices) (";
        tab(fTab + 3, *fOut);
        *fOut << "obj = get_dsp(voice_idx); ";
        tab(fTab + 3, *fOut);
        *fOut << "(obj[dsp.key_id] == msg2 && obj[dsp.gate] > 0) ? (";
        tab(fTab + 4, *fOut);
        *fOut << "obj[dsp.gate] = 0; ";
        for (const auto& it : _midi_scales) {
            if (it.second.type == JSFXMIDIScaleType::gate) {
                tab(fTab + 4, *fOut);
                *fOut << "obj[dsp." << it.first << "] = 0;";
            }
        }
        tab(fTab + 4, *fOut);
        *fOut << "voice_idx = nvoices;";
        tab(fTab + 3, *fOut);
        *fOut << ");";
        tab(fTab + 3, *fOut);
        *fOut << "voice_idx += 1; ";
        tab(fTab + 2, *fOut);
        *fOut << "); // end of while ";
        tab(fTab + 1, *fOut);
        *fOut << "); // end of condition ";
    }

    void _voice_blocking_impl()
    {
        tab(fTab + 1, *fOut);
        *fOut << "(status == NOTE_ON) ? (";
        tab(fTab + 2, *fOut);
        *fOut << "midi_event += 1; ";
        tab(fTab + 2, *fOut);
        *fOut << "voice_idx = 0; ";
        tab(fTab + 2, *fOut);
        *fOut << "while(voice_idx < nvoices) ( ";
        tab(fTab + 3, *fOut);
        *fOut << "obj = get_dsp(voice_idx); ";
        tab(fTab + 3, *fOut);
        *fOut << "(obj[dsp.gate] == 0) ?(";
        *fOut << "obj[dsp.key_id] = msg2;";
        tab(fTab + 4, *fOut);
        *fOut << "obj[dsp.gate] = 1;";
        tab(fTab + 4, *fOut);
        _midi_poly_assign();
        tab(fTab + 4, *fOut);
        *fOut << "voice_idx = nvoices; ";
        tab(fTab + 3, *fOut);
        *fOut << "); ";
        tab(fTab + 3, *fOut);
        *fOut << "voice_idx += 1; ";
        tab(fTab + 2, *fOut);
        *fOut << "); // end of while ";
        tab(fTab + 1, *fOut);
        *fOut << "); // NOTE ON condition off ";
        tab(fTab + 1, *fOut);
        *fOut << "(status == NOTE_OFF) ? (";
        tab(fTab + 2, *fOut);
        *fOut << "midi_event += 1; ";
        tab(fTab + 2, *fOut);
        *fOut << "voice_idx = 0; ";
        tab(fTab + 2, *fOut);
        *fOut << "while(voice_idx < nvoices) (";
        tab(fTab + 3, *fOut);
        *fOut << "obj = get_dsp(voice_idx); ";
        tab(fTab + 3, *fOut);
        *fOut << "(obj[dsp.key_id] == msg2 && obj[dsp.gate] > 0) ? (";
        tab(fTab + 4, *fOut);
        *fOut << "obj[dsp.gate] = 0; ";
        tab(fTab + 4, *fOut);
        for (const auto& it : _midi_scales) {
            if (it.second.type == JSFXMIDIScaleType::gate) {
                tab(fTab + 4, *fOut);
                *fOut << "obj[dsp." << it.first << "] = 0;";
            }
        }
        tab(fTab + 4, *fOut);
        *fOut << "voice_idx = nvoices; ";
        tab(fTab + 3, *fOut);
        *fOut << "); ";
        tab(fTab + 2, *fOut);
        *fOut << "voice_idx += 1; ";
        tab(fTab + 2, *fOut);
        *fOut << "); // end of while ";
        tab(fTab + 1, *fOut);
        *fOut << "); // end of condition ";
    }

    void _midi_mono_instructions()
    {
        std::vector<JSFXMidiInstr> keyons, keyoffs, ccs, pgms;
        for (auto& midi_str : _midi_instructions) {
            JSFXMidiInstr it = parseMIDIInstruction(midi_str.first, midi_str.second);
            if (it.type_name == "key") {
                keyons.push_back(it);
                keyoffs.push_back(it);
            } else if (it.type_name == "keyon") {
                keyons.push_back(it);
            } else if (it.type_name == "keyoff" && !poly) {
                keyoffs.push_back(it);
            } else if (it.type_name == "ctrl" /*&& !poly */) {
                ccs.push_back(it);
            } else if (it.type_name == "pgm") {
                pgms.push_back(it);
            } else if (it.type_name == "pitchwheel") {
                tab(fTab + 1, *fOut);
                *fOut << "(status == BEND) ? (";
                tab(fTab + 2, *fOut);
                *fOut << "midi_event += 1;";
                tab(fTab + 2, *fOut);
                JSFXMidiScale scale = _midi_scales[it.variable_name];
                *fOut << it.variable_name << " = midi_scale( (msg2 << 7) + msg3, " << scale.min
                      << ", " << scale.max << ", " << scale.step << ")";
                tab(fTab + 1, *fOut);
                *fOut << ");";
            }
        }
        if (ccs.size() > 0) {
            tab(fTab + 1, *fOut);
            *fOut << "(status == CC) ? (";
            tab(fTab + 2, *fOut);
            *fOut << "midi_event += 1;";
            for (const auto& cc : ccs) {
                JSFXMidiScale scale = _midi_scales[cc.variable_name];
                tab(fTab + 2, *fOut);
                *fOut << "(msg2 == 0x" << std::hex << cc.nbr;
                if (cc.channel > 0) {
                    *fOut << " && channel == 0x" << std::hex << (cc.channel - 1);
                }
                *fOut << ") ? (" << cc.variable_name << " = midi_scale(msg3, " << scale.min << ", "
                      << scale.max << ", " << scale.step << "));";
            }
            tab(fTab + 1, *fOut);
            *fOut << ");";
        }
        if (pgms.size() > 0) {
            tab(fTab + 1, *fOut);
            *fOut << "(status == PGM_CHANGE) ? (";
            tab(fTab + 2, *fOut);
            *fOut << "midi_event += 1;";
            for (const auto& pgm : pgms) {
                JSFXMidiScale scale = _midi_scales[pgm.variable_name];
                tab(fTab + 2, *fOut);
                if (pgm.channel > 0) {
                    *fOut << "(channel == 0x" << std::hex << (pgm.channel - 1) << ") ? (";
                }
                *fOut << pgm.variable_name << " = midi_scale(msg2, " << scale.min << ", "
                      << scale.max << ", " << scale.step << ")";
                if (pgm.channel > 0) {
                    *fOut << ")";
                }
                *fOut << ";";
            }
            tab(fTab + 1, *fOut);
            *fOut << ");";
        }
        if (keyons.size() > 0) {
            tab(fTab + 1, *fOut);
            *fOut << "(status == NOTE_ON) ? ( ";
            tab(fTab + 2, *fOut);
            *fOut << "midi_event += 1; ";
            for (const auto& k : keyons) {
                JSFXMidiScale scale = _midi_scales[k.variable_name];
                tab(fTab + 2, *fOut);
                *fOut << "(msg2 == 0x" << std::hex << k.nbr;
                if (k.channel > 0) {
                    *fOut << " && channel == 0x" << std::hex << (k.channel - 1);
                }
                *fOut << ") ? (" << k.variable_name << " = midi_scale(0xF0&msg3, " << scale.min
                      << ", " << scale.max << ", " << scale.step << "));";
            }
            tab(fTab + 1, *fOut);
            *fOut << "); ";
        }
        if (keyoffs.size() > 0) {
            tab(fTab + 1, *fOut);
            *fOut << "(status == NOTE_OFF) ? (";
            tab(fTab + 2, *fOut);
            *fOut << "midi_event += 1; ";
            for (const auto& k : keyoffs) {
                JSFXMidiScale scale = _midi_scales[k.variable_name];
                tab(fTab + 2, *fOut);
                *fOut << "(msg2 == 0x" << std::hex << k.nbr;
                if (k.channel > 0) {
                    *fOut << " && channel == 0x" << std::hex << (k.channel - 1);
                }
                *fOut << ") ? (" << k.variable_name << " = midi_scale(0xF0&msg3," << scale.min
                      << ", " << scale.max << ", " << scale.step << " )); ";
            }
            tab(fTab + 1, *fOut);
            *fOut << "); ";
        }
    }

    bool hasMIDIInstructions() { return _midi_instructions.size() > 0 || poly; }

    void generateMIDIInstructions()
    {
        if (_midi_instructions.size() > 0 || poly) {
            *fOut << "midi_event = 0; ";
            tab(fTab, *fOut);
            *fOut << "while (midirecv(mpos, msg1, msg2, msg3)) (";
            tab(fTab + 1, *fOut);
            *fOut << "status = msg1&0xF0;";
            tab(fTab + 1, *fOut);
            *fOut << "channel = msg1&0x0F; ";
        }
        if (_midi_instructions.size() > 0) {
            // MIDI from controls metadata
            _midi_mono_instructions();
        }
        if (poly) {  // Polyphonic MIDI
            switch (mode) {
                case JSFXMIDIVoiceMode::voice_block: {
                    _voice_blocking_impl();
                    break;
                }
                case JSFXMIDIVoiceMode::voice_steal: {
                    _voices_stealing_impl();
                    break;
                }
                default:
                    break;
            }
        }
        if (_midi_instructions.size() > 0 || poly) {
            tab(fTab, *fOut);
            *fOut << ");";
        }
    }

    virtual void visit(OpenboxInst* inst) {}

    virtual void visit(CloseboxInst* inst) {}

    virtual void visit(AddButtonInst* inst)
    {
        if (!skip_slider) {
            std::string name = gGlobal->getFreshID(inst->fLabel);
            if (next_control_is_midi && !poly) {
                _midi_scales[inst->fZone]  = JSFXMidiScale{0, 0, 1, 1};
                _midi_sliders[inst->fZone] = inst->fZone;
            } else if (poly) {
                std::string label = inst->fLabel;
                if (exact_match(label, "gate")) {
                    _midi_scales[inst->fZone] = JSFXMidiScale{0, 0, 1, 1, JSFXMIDIScaleType::gate};
                    _poly_init_map[inst->fZone] = 0;
                    return;
                } else {
                    _midi_scales[inst->fZone]  = JSFXMidiScale{0, 0, 1, 1};
                    _midi_sliders[inst->fZone] = inst->fZone;
                }
            }
            std::string prefix;
            if (inst->fType == AddButtonInst::kDefaultButton) {
                prefix = "button_";
            } else {
                prefix = "checkbox_";
            }
            *fOut << "slider" << ++slider_count << ":" << inst->fZone << "=0<0,1,1>" << prefix
                  << gGlobal->getFreshID(inst->fLabel);
            EndLine(' ');
            // The memory is set to 0 by default
            //_slider_init_map[inst->fZone] = 0;
        } else {
            _midi_scales[inst->fZone] = JSFXMidiScale{0, 0, 1, 1};
        }
        skip_slider          = false;
        next_control_is_midi = false;
        if (slider_count == 256 && (gGlobal->gOutputLang != "jsfx-test")) {
            throw(
                faustexception("ERROR : JSFX format does not support more than 256 controllers\n"));
        }
    }

    virtual void visit(AddSliderInst* inst)
    {
        if (!skip_slider) {
            std::string name = gGlobal->getFreshID(inst->fLabel);
            if (next_control_is_midi && !poly) {
                _midi_scales[inst->fZone]  = JSFXMidiScale{inst->fInit, inst->fMin, inst->fMax,
                                                          inst->fStep, JSFXMIDIScaleType::none};
                _midi_sliders[inst->fZone] = inst->fZone;
            } else if (poly) {
                std::string label = inst->fLabel;
                if (exact_match(label, "gain")) {
                    _midi_scales[inst->fZone]   = JSFXMidiScale{inst->fInit, inst->fMin, inst->fMax,
                                                              inst->fStep, JSFXMIDIScaleType::gain};
                    _poly_init_map[inst->fZone] = inst->fInit;
                    return;
                } else if (exact_match(label, "vel") || exact_match(label, "veloc")) {
                    _midi_scales[inst->fZone] =
                        JSFXMidiScale::MIDI7bScale(inst->fInit, JSFXMIDIScaleType::veloc);
                    _poly_init_map[inst->fZone] = inst->fInit;
                } else if (exact_match(label, "freq")) {
                    _midi_scales[inst->fZone]   = JSFXMidiScale{inst->fInit, inst->fMin, inst->fMax,
                                                              inst->fStep, JSFXMIDIScaleType::freq};
                    _poly_init_map[inst->fZone] = inst->fInit;
                    return;
                } else if (exact_match(label, "key")) {
                    _midi_scales[inst->fZone] =
                        JSFXMidiScale::MIDI7bScale(inst->fInit, JSFXMIDIScaleType::key);
                    _poly_init_map[inst->fZone] = inst->fInit;
                } else {
                    _midi_scales[inst->fZone] =
                        JSFXMidiScale{inst->fInit, inst->fMin, inst->fMax, inst->fStep};
                    _midi_sliders[inst->fZone] = inst->fZone;
                }
            }
            std::string prefix;
            switch (inst->fType) {
                case AddSliderInst::kHorizontal:
                    prefix = "hslider_";
                    break;
                case AddSliderInst::kVertical:
                    prefix = "vslider_";
                    break;
                case AddSliderInst::kNumEntry:
                    prefix = "nentry_";
                    break;
            }
            std::string label = gGlobal->getFreshID(inst->fLabel);
            *fOut << "slider" << ++slider_count << ":" << inst->fZone << "=" << inst->fInit << "<"
                  << inst->fMin << "," << inst->fMax << "," << inst->fStep << ">" << prefix
                  << label;
            EndLine(' ');
            _slider_init_map[inst->fZone] = inst->fInit;
        } else {
            _midi_scales[inst->fZone] =
                JSFXMidiScale{inst->fInit, inst->fMin, inst->fMax, inst->fStep};
        }
        skip_slider          = false;
        next_control_is_midi = false;
        if (slider_count == 256 && (gGlobal->gOutputLang != "jsfx-test")) {
            throw(
                faustexception("ERROR : JSFX format does not support more than 256 controllers\n"));
        }
    }

    virtual void visit(AddBargraphInst* inst) {}

    virtual void visit(AddSoundfileInst* inst)
    {
        throw(faustexception("ERROR : Soundfile is not available in JSFX\n"));
    }

    virtual void visit(Int32NumInst* inst) { *fOut << inlineInt32(inst->fNum); }

    virtual void visit(Int64NumInst* inst) { *fOut << inlineInt32(inst->fNum); }

    virtual void visit(FloatNumInst* inst) { *fOut << std::fixed << inst->fNum; }

    virtual void visit(DoubleNumInst* inst) { *fOut << std::fixed << inst->fNum; }

    virtual void visit(Int32ArrayNumInst* inst)
    {
        char sep = '[';
        for (size_t i = 0; i < inst->fNumTable.size(); i++) {
            *fOut << sep << inlineInt32(inst->fNumTable[i]);
            sep = ',';
        }
        *fOut << ']';
    }

    virtual void visit(FloatArrayNumInst* inst)
    {
        char sep = '[';
        for (size_t i = 0; i < inst->fNumTable.size(); i++) {
            *fOut << sep << std::fixed << inst->fNumTable[i];
            sep = ',';
        }
        *fOut << ']';
    }

    virtual void visit(DoubleArrayNumInst* inst)
    {
        char sep = '[';
        for (size_t i = 0; i < inst->fNumTable.size(); i++) {
            *fOut << sep << std::fixed << inst->fNumTable[i];
            sep = ',';
        }
        *fOut << ']';
    }

    virtual void visit(BinopInst* inst)
    {
        Typed::VarType type1 = TypingVisitor::getType(inst->fInst1);
        Typed::VarType type2 = TypingVisitor::getType(inst->fInst2);

        // Div not implemented yet for int32
        if (isInt32Type(type1) && (isInt32Type(type2) || isInt64Type(type2))) {
            static std::map<int, std::string> iop = {
                {kAdd, "int32(add32("}, {kSub, "int32(sub32("}, {kMul, "int32(mul32("}};
            if (iop.find(inst->fOpcode) != iop.end()) {
                *fOut << iop[inst->fOpcode];
                inst->fInst1->accept(this);
                *fOut << ", ";
                inst->fInst2->accept(this);
                *fOut << "))";
                return;
            }
        }

        if (inst->fOpcode == kXOR) {
            *fOut << "(";
            inst->fInst1->accept(this);
            *fOut << " ~ ";
            inst->fInst2->accept(this);
            *fOut << ")";
        } else {
            // Operator precedence is not like C/C++, so for simplicity, we keep the fully
            // parenthesized version
            *fOut << "(";
            inst->fInst1->accept(this);
            *fOut << " ";
            *fOut << gBinOpTable[inst->fOpcode]->fName;
            *fOut << " ";
            inst->fInst2->accept(this);
            *fOut << ")";
        }
    }

    virtual void visit(DeclareVarInst* inst)
    {
        std::string name = inst->getName();
        if (strfind(name, "output") || strfind(name, "input")) {
            return;
        }
        if (inst->fAddress->isStaticStruct()) {
            *fOut << fTypeManager->generateType(inst->fType, inst->getName());
        } else {
            bool is_block = startWith(name, "iSlow") || startWith(name, "fSlow");
            if (inst->getAccess() & Address::kStack && is_block) {
                *fOut << "obj[dsp.";
            }
            *fOut << fTypeManager->generateType(inst->fType, inst->getName());
            if (inst->getAccess() & Address::kStack && is_block) {
                *fOut << "]";
            }
            if (inst->fValue) {
                *fOut << " = ";
                inst->fValue->accept(this);
            }
        }
        EndLine();
    }

    virtual void visit(DropInst* inst)
    {
        if (inst->fResult) {
            inst->fResult->accept(this);
            EndLine(' ');
        }
    }

    virtual void visit(DeclareFunInst* inst)
    {
        // Already generated
        if (gFunctionSymbolTable.find(inst->fName) != gFunctionSymbolTable.end()) {
            return;
        } else {
            gFunctionSymbolTable[inst->fName] = true;
        }

        *fOut << "function " << inst->fName;
        generateFunDefArgs(inst);
        *fOut << " ";
        generateFunDefBody(inst);
    }

    virtual void generateFunDefArgs(DeclareFunInst* inst)
    {
        if (inst->fType->fArgsTypes.size() == 0) {
            *fOut << "()";
            return;
        }
        *fOut << "(";
        size_t size = inst->fType->fArgsTypes.size(), i = 0;
        for (const auto& it : inst->fType->fArgsTypes) {
            *fOut << fTypeManager->generateType(it);
            if (i++ < size - 1) {
                *fOut << ", ";
            }
        }
        *fOut << ")";
    }

    virtual void generateFunDefBody(DeclareFunInst* inst)
    {
        if (inst->fCode->fCode.size() == 0) {
            *fOut << "()" << std::endl;  // Pure prototype
        } else {
            // Function body
            *fOut << "(" << std::endl;
            fTab++;
            tab(fTab, *fOut);
            inst->fCode->accept(this);
            fTab--;
            back(1, *fOut);
            *fOut << ")";
            EndLine();
            tab(fTab, *fOut);
        }
    }

    static int get_num_output(std::string& s)
    {
        std::regex r("output([0-9]+)");
        for (std::sregex_iterator i = std::sregex_iterator(s.begin(), s.end(), r);
             i != std::sregex_iterator(); ++i) {
            std::smatch m = *i;
            return std::stoi(m[1]);
        }
        return -1;
    }

    // The following methods is used to generate code that declares/defines/accesses structures.
    // The structure correspond to a "DSP" class representing the Faust code.
    // This class can be instanciated in MIDI polyphonic context.
    // The reason of the many conditionals in this method is that JSFX code structure is not object
    // oriented by default. Moreover, some variables like fSlow iSlow had to be put in the class
    // (altough it is not the case in C++ backend for example). In C++, these variables are declared
    // and defined in the compute method of DSP class, just above the audio loop. In JSFX, scopes
    // are handled differently (the @block section is called once for each instances, and the
    // @sample section too) so these fields had to be moved as class members.
    virtual void visit(NamedAddress* named)
    {
        // iSlow and fSlow need to be treated as class members
        const bool is_slow = startWith(named->fName, "iSlow") || startWith(named->fName, "fSlow");
        if ((named->isStruct() && !isControl(named->fName)) || (named->isStack() && is_slow)) {
            *fOut << "obj[dsp.";
        }

        std::string name = named->fName;
        // Replace name with corresponding JSFX names or JSFX object members
        if (strfind(named->fName, "output")) {
            name = "obj[dsp.output" + std::to_string(get_num_output(name)) + "]";
        } else if (strfind(named->fName, "input")) {
            name.replace(0, 5, "spl");
        } else if (strfind(named->fName, "sample_rate")) {
            name = "srate";
            // Control values in polyphonic context are treated as object members
        } else if (poly && isControl(named->fName)) {
            name = "obj[dsp." + name + "]";
        }
        *fOut << name;
        // Open brackets for table that are not already opened
        if (isTable(named->fName) && !named->isStruct()) {
            *fOut << "[";
        }
        // Close brackets if opened
        if (((named->isStruct() && !isControl(named->fName) && !is_slow &&
              !strfind(named->fName, "Rec") && !strfind(named->fName, "Vec")) ||
             (named->isStack() && is_slow)) &&
            !isTable(named->fName)) {
            *fOut << "]";
        }
    }

    /*
    Indexed address can be values in an array or fields in a struct type
    */
    virtual void visit(IndexedAddress* indexed)
    {
        indexed->fAddress->accept(this);
        DeclareStructTypeInst* struct_type = isStructType(indexed->getName());
        if (struct_type) {
            Int32NumInst* field_index = static_cast<Int32NumInst*>(indexed->getIndex());
            *fOut << "." << struct_type->fType->getName(field_index->fNum);
        } else {
            std::string name = indexed->fAddress->getName();
            if (strfind(name, "output") || strfind(name, "input")) {
                return;
            }
            Int32NumInst* field_index = dynamic_cast<Int32NumInst*>(indexed->getIndex());
            // In JSFX, arrays do not really exist. Instead, we access a location relative to an
            // initial position in the preallocated DSP memory. Thus, writing a[10] means accessing
            // the value at a+10. The syntax a[5][5] is thus not allowed, so accessing a position
            // inside a member that is an array may be done with : a[array_pos + index_in_array]
            if (!isTable(indexed->getName())) {
                if (indexed->isStruct()) {
                    *fOut << " + ";
                } else {
                    *fOut << "[";
                }
            } else {
                if (indexed->isStruct()) {
                    *fOut << " + ";
                }
            }
            if (field_index) {
                *fOut << (field_index->fNum) << "]";
            } else {
                indexed->getIndex()->accept(this);
                *fOut << "]";
            }
        }
    }

    virtual void visit(::CastInst* inst)
    {
        if (isIntType(inst->fType->getType())) {
            *fOut << "int32(ftoi32(";
            inst->fInst->accept(this);
            *fOut << "))";
        } else {
            *fOut << "(";
            inst->fInst->accept(this);
            *fOut << ")";
        }
    }

    virtual void visit(BitcastInst* inst) { faustassert(false); }

    virtual void visitCond(ValueInst* cond)
    {
        *fOut << "(";
        cond->accept(this);
        *fOut << " != 0)";
    }

    // Generate standard funcall (not 'method' like funcall...)
    virtual void visit(FunCallInst* inst)
    {
        std::string name = (gPolyMathLibTable.find(inst->fName) != gPolyMathLibTable.end())
                               ? gPolyMathLibTable[inst->fName]
                               : inst->fName;
        // Function that mutate their arguments use the '!' syntax
        *fOut << name << "(";
        // Compile parameters
        generateFunCallArgs(inst->fArgs.begin(), inst->fArgs.end(), inst->fArgs.size());
        *fOut << ")";
    }

    virtual void visit(IfInst* inst)
    {
        *fOut << "(";
        visitCond(inst->fCond);
        *fOut << ") ? (\n";
        inst->fThen->accept(this);
        *fOut << ")";
        if (inst->fElse->fCode.size() > 0) {
            *fOut << " : (\n";
            inst->fElse->accept(this);
            *fOut << ")";
        }
        EndLine();
    }

    virtual void visit(ForLoopInst* inst)
    {
        if (inst->fCode->size() == 0) {
            return;
        }

        tab(fTab, *fOut);
        inst->fInit->accept(this);
        tab(fTab, *fOut);
        *fOut << "while";
        inst->fEnd->accept(this);
        tab(fTab, *fOut);
        *fOut << "(";
        fTab++;
        tab(fTab, *fOut);
        inst->fCode->accept(this);
        inst->fIncrement->accept(this);
        fTab--;
        back(1, *fOut);
        tab(fTab, *fOut);
        *fOut << ");";
    }

    // @sample DSP Loop
    virtual void visit(SimpleForLoopInst* inst) { inst->fCode->accept(this); }

    static void cleanup() { gFunctionSymbolTable.clear(); }
};

#endif
