// -----------------
// ui.js
// -----------------
//
// Automatically (re)generate/destroy sliders/buttons according to Faust DSP module.
// By Edgar Berdahl, July 2014
// Revised by Grame, 2014-2025
//
// Currently addHorizontalBargraph, addVerticalBargraph, declare,
// openTabBox, openHorizontalBox, openVerticalBox, and closeBox are not handled.
//
// Started from autosurface.js by rld, 5.04
//

// Global table
var dsp_ui_table = (typeof dsp_ui_table === 'undefined') ? [] : dsp_ui_table;

var faust = (typeof faust === 'undefined') ? {} : faust;

// global variables and arrays
faust.numwidgets = -1;

// Maxobj variables for scripting
faust.theComments = new Array(128);
faust.theSliders = new Array(128);
faust.theMessages = new Array(128);
faust.thenumberBoxes = new Array(128);

// Function to check if a string starts with a prefix
faust.starts_with = function (str, prefix) {
    return (str.lastIndexOf(prefix, 0) === 0);
}

// Function to get the DSP object by its name of class
faust.get_with_name = function (patcher, name) {
    var obj = patcher.firstobject;
    while (obj) {
        if (faust.starts_with(obj.varname, name) || faust.starts_with(obj.maxclass, name)) {
            return obj;
        }
        obj = obj.nextobject;
    }
    return null;
}

// Function to remove all existing faust UI objects
faust.remove_ui = function () {
    // Remove comments
    for (var i = 0; i < faust.theComments.length; i++) {
        patcher.remove(faust.theComments[i]);
    }
    faust.theComments = [];

    // Remove sliders
    for (var i = 0; i < faust.theSliders.length; i++) {
        patcher.remove(faust.theSliders[i]);
    }
    faust.theSliders = [];

    // Remove messages
    for (var i = 0; i < faust.theMessages.length; i++) {
        patcher.remove(faust.theMessages[i]);
    }
    faust.theMessages = [];

    // Remove number boxes
    for (var i = 0; i < faust.thenumberBoxes.length; i++) {
        patcher.remove(faust.thenumberBoxes[i]);
    }
    faust.thenumberBoxes = [];

    // Reset the counter
    faust.numwidgets = 0;
};

// Main function
faust.ui = function (json, patcher) {

    var widgHeight = 30;
    var hBase = 150;

    // JSON parsing

    // Recursive functions to parse the JSON UI description
    parse_ui = function (ui, target, patcher) {
        var i;
        for (i = 0; i < ui.length; i++) {
            parse_group(ui[i], target, patcher);
        }
    }

    // Parse a group
    parse_group = function (group, target, patcher) {
        if (group.items) {
            parse_items(group.items, target, patcher);
        }
    }

    // Parse items in a group
    parse_items = function (items, target, patcher) {
        var i;
        for (i = 0; i < items.length; i++) {
            parse_item(items[i], target, patcher);
        }
    }

    // Parse a single item
    parse_item = function (item, target, patcher) {
        if (item.type === "vgroup" || item.type === "hgroup" || item.type === "tgroup") {

            parse_items(item.items, target, patcher);

        } else if (item.type === "hbargraph" || item.type === "vbargraph") {

            faust.numwidgets++;
            faust.theComments[faust.numwidgets] = patcher.newdefault(hBase, 20 + widgHeight * faust.numwidgets, "comment");
            faust.theComments[faust.numwidgets].message("set", "bargraph:" + item.label);

            // TODO : create "meter" instead of "multiSlider" 
            faust.theSliders[faust.numwidgets] = patcher.newobject("user", "multiSlider", hBase + 130, 20 + widgHeight * faust.numwidgets, 120, 20, 0., 1., 1, 2936, 15, 0, 0, 2, 0, 0, 0);
            if (parseFloat(item.step) == parseInt(item.step)) {
                faust.theSliders[faust.numwidgets].message('settype', 0);
            } else {
                faust.theSliders[faust.numwidgets].message('settype', 1);
            }
            faust.theSliders[faust.numwidgets].message('contdata', 1);
            faust.theSliders[faust.numwidgets].message('setstyle', 0);
            faust.theSliders[faust.numwidgets].message('setminmax', parseFloat(item.min), parseFloat(item.max));
            faust.theSliders[faust.numwidgets].message(parseFloat(item.init));  // Set initial value

            // Bargraph ScriptingName is set with the complete parameter path, so that faustgen~ can directly address them
            faust.theSliders[faust.numwidgets].message('varname', item.shortname);

            faust.thenumberBoxes[faust.numwidgets] = patcher.newobject("flonum", hBase + 258, 20 + widgHeight * faust.numwidgets, 80, 13);
            faust.thenumberBoxes[faust.numwidgets].message('min', parseFloat(item.min));
            faust.thenumberBoxes[faust.numwidgets].message('max', parseFloat(item.max));

            patcher.hiddenconnect(faust.theSliders[faust.numwidgets], 0, faust.thenumberBoxes[faust.numwidgets], 0);
            patcher.hiddenconnect(faust.theMessages[faust.numwidgets], 0, target, 0);

            // direct connection to faustgen~ does not work...
            //patcher.hiddenconnect(faust.thenumberBoxes[faust.numwidgets], 0, target, 0);

        } else if (item.type === "vslider" || item.type === "hslider") {

            faust.numwidgets++;
            faust.theComments[faust.numwidgets] = patcher.newdefault(hBase, 20 + widgHeight * faust.numwidgets, "comment");
            faust.theComments[faust.numwidgets].message("set", item.label);

            faust.theSliders[faust.numwidgets] = patcher.newobject("user", "multiSlider", hBase + 130, 20 + widgHeight * faust.numwidgets, 120, 20, 0., 1., 1, 2936, 15, 0, 0, 2, 0, 0, 0);
            if (parseFloat(item.step) == parseInt(item.step)) {
                faust.theSliders[faust.numwidgets].message('settype', 0);
            } else {
                faust.theSliders[faust.numwidgets].message('settype', 1);
            }
            faust.theSliders[faust.numwidgets].message('contdata', 1);
            faust.theSliders[faust.numwidgets].message('setstyle', 0);
            faust.theSliders[faust.numwidgets].message('setminmax', parseFloat(item.min), parseFloat(item.max));
            faust.theSliders[faust.numwidgets].message(parseFloat(item.init));  // Set initial value
            faust.theSliders[faust.numwidgets].message('varname', item.shortname);

            faust.thenumberBoxes[faust.numwidgets] = patcher.newobject("flonum", hBase + 258, 20 + widgHeight * faust.numwidgets, 80, 13);
            faust.thenumberBoxes[faust.numwidgets].message('min', parseFloat(item.min));
            faust.thenumberBoxes[faust.numwidgets].message('max', parseFloat(item.max));
            faust.thenumberBoxes[faust.numwidgets].message(parseFloat(item.init));

            patcher.hiddenconnect(faust.theSliders[faust.numwidgets], 0, faust.thenumberBoxes[faust.numwidgets], 0);

            faust.theMessages[faust.numwidgets] = patcher.newobject("message", hBase + 345, 23 + widgHeight * faust.numwidgets, 350, 9);
            faust.theMessages[faust.numwidgets].message("set", item.shortname, "\$1");

            patcher.hiddenconnect(faust.thenumberBoxes[faust.numwidgets], 0, faust.theMessages[faust.numwidgets], 0);
            patcher.hiddenconnect(faust.theMessages[faust.numwidgets], 0, target, 0);

            // direct connection to faustgen~ does not work...
            //patcher.hiddenconnect(faust.thenumberBoxes[faust.numwidgets], 0, target, 0);

        } else if (item.type === "button" || item.type === "checkbox") {

            faust.numwidgets++;
            faust.theComments[faust.numwidgets] = patcher.newdefault(hBase, 20 + widgHeight * faust.numwidgets, "comment");
            faust.theComments[faust.numwidgets].message("set", item.label);

            faust.theSliders[faust.numwidgets] = patcher.newdefault(hBase + 130, 20 + widgHeight * faust.numwidgets, "toggle");  // Faust says always have default of zero--even for check buttons!  Ohhhh well...

            faust.thenumberBoxes[faust.numwidgets] = patcher.newobject("number", hBase + 258, 20 + widgHeight * faust.numwidgets, 80, 13);
            faust.thenumberBoxes[faust.numwidgets].message('min', 0);
            faust.thenumberBoxes[faust.numwidgets].message('max', 1);

            patcher.hiddenconnect(faust.theSliders[faust.numwidgets], 0, faust.thenumberBoxes[faust.numwidgets], 0);

            faust.theMessages[faust.numwidgets] = patcher.newobject("message", hBase + 345, 23 + widgHeight * faust.numwidgets, 350, 9);
            faust.theMessages[faust.numwidgets].message("set", item.shortname, "\$1");

            patcher.hiddenconnect(faust.thenumberBoxes[faust.numwidgets], 0, faust.theMessages[faust.numwidgets], 0);
            patcher.hiddenconnect(faust.theMessages[faust.numwidgets], 0, target, 0);

            // direct connection to faustgen~ does not work...
            //patcher.hiddenconnect(faust.thenumberBoxes[faust.numwidgets], 0, target, 0)

        } else if (item.type === "nentry") {

            faust.numwidgets++;
            faust.theComments[faust.numwidgets] = patcher.newdefault(hBase, 20 + widgHeight * faust.numwidgets, "comment");
            faust.theComments[faust.numwidgets].message("set", item.label);

            if (parseFloat(item.step) == parseInt(item.step)) {
                //post("integer : nentry \n");
                faust.thenumberBoxes[faust.numwidgets] = patcher.newobject("number", hBase + 258, 20 + widgHeight * faust.numwidgets, 80, 13);
            } else {
                //post("float : nentry \n");
                faust.thenumberBoxes[faust.numwidgets] = patcher.newobject("flonum", hBase + 258, 20 + widgHeight * faust.numwidgets, 80, 13);
            }

            faust.thenumberBoxes[faust.numwidgets].message('min', parseFloat(item.min));
            faust.thenumberBoxes[faust.numwidgets].message('max', parseFloat(item.max));
            faust.thenumberBoxes[faust.numwidgets].message(parseFloat(item.init));

            faust.theMessages[faust.numwidgets] = patcher.newobject("message", hBase + 345, 23 + widgHeight * faust.numwidgets, 350, 9);
            faust.theMessages[faust.numwidgets].message("set", item.shortname, "\$1");

            patcher.hiddenconnect(faust.thenumberBoxes[faust.numwidgets], 0, faust.theMessages[faust.numwidgets], 0);
            patcher.hiddenconnect(faust.theMessages[faust.numwidgets], 0, target, 0);

            // direct connection to faustgen~ does not work...
            //patcher.hiddenconnect(faust.thenumberBoxes[faust.numwidgets], 0, target, 0);       
        }
    }

    // Remove old
    faust.remove_ui();

    // Create new
    const parsed_json = JSON.parse(json);

    // Tries to find the compiled object from the "name" field in the JSON
    var dsp_object = patcher.getnamed(parsed_json.name + "~");
    if (dsp_object !== patcher.getnamed("null_object")) {
        parse_ui(parsed_json.ui, dsp_object, patcher);
    } else {
        // Tries to find the compiled object from the "filename" field in the JSON
        dsp_object = patcher.getnamed(parsed_json.filename.slice(0, -4) + "~");
        if (dsp_object !== patcher.getnamed("null_object")) {
            parse_ui(parsed_json.ui, dsp_object, patcher);
        } else {
            // Tries to find the compiled object from the "name" argument (used with faustgen~)
            dsp_object = faust.get_with_name(patcher, "faustgen");
            if (dsp_object !== patcher.getnamed("null_object")) {
                parse_ui(parsed_json.ui, dsp_object, patcher);
            } else {
                post("Error : missing dsp name in the patch\n");
            }
        }
    }
}

// Main entry point
function anything() {
    const args = arrayfromargs(messagename, arguments);
    dsp_ui_table.push(faust.ui(args[1], this.patcher));
}

