#include "components.hpp"
#include "filter/ripples.hpp"
#include "plugin.hpp"

#include <cstdint>
#include <cstring>

struct TwinPeaks : Module {
    enum ParamIds {
        FREQ_A_PARAM,
        FREQ_B_PARAM,
        RES_PARAM,
        RES_CV_PARAM,
        TRACK_A_PARAM,
        TRACK_B_PARAM,
        FM_GLOBAL_A_PARAM,
        FM_GLOBAL_B_PARAM,
        FM_CV_A_PARAM,
        FM_CV_B_PARAM,

        XFM_B_PARAM,
        TYPE_SWITCH,
        CURVE_B_PARAM,
        CURVE_B_CV_PARAM,
        NUM_PARAMS
    };
    enum InputIds {
        RES_INPUT,
        FREQ_A_INPUT,
        FREQ_B_INPUT,
        FM_CV_A_INPUT,
        FM_CV_B_INPUT,
        IN_INPUT,
        CURVE_B_INPUT,
        NUM_INPUTS
    };
    enum OutputIds {
        OUT_OUTPUT,
        NUM_OUTPUTS
    };
    enum LightIds {
        NUM_LIGHTS
    };

    ripples::RipplesEngine enginesA[16];
    ripples::RipplesEngine enginesB[16];

    // Rack builds plugins with unsafe floating-point optimizations, so use the
    // IEEE-754 representation instead of std::isfinite().
    static bool isFinite(float value) {
        std::uint32_t bits;
        std::memcpy(&bits, &value, sizeof(bits));
        return (bits & 0x7f800000u) != 0x7f800000u;
    }

    static float finiteOrZero(float value) {
        return isFinite(value) ? value : 0.f;
    }

    TwinPeaks() {
        config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);

        configParam(FREQ_A_PARAM, std::log2(ripples::kFreqKnobMin), std::log2(ripples::kFreqKnobMax), std::log2(ripples::kFreqKnobMin), "Frequency A", " Hz", 2.f);
        configParam(FREQ_B_PARAM, std::log2(ripples::kFreqKnobMin), std::log2(ripples::kFreqKnobMax), std::log2(ripples::kFreqKnobMax), "Frequency B", " Hz", 2.f);

        configParam(FM_GLOBAL_A_PARAM, -1.f, 1.f, 1.f, "Frequency A mod", "%", 0, 100);
        configParam(RES_PARAM, 0.f, 1.f, 0.73f, "Resonance", "%", 0, 100);
        configParam(CURVE_B_PARAM, 0.f, 1.f, 1.f, "Low Pass<>Twin Peak", "%", 0, 100);
        configParam(FM_GLOBAL_B_PARAM, -1.f, 1.f, 1.f, "Frequency B mod", "%", 0, 100);

        configParam(FM_CV_A_PARAM, -1.f, 1.f, 0.f, "Frequency A mod CV", "%", 0, 100);
        configParam(RES_CV_PARAM, -1.f, 1.f, 0.f, "Resonance mod CV", "%", 0, 100);
        configParam(CURVE_B_CV_PARAM, -1.f, 1.f, 0.f, "LP<>TWP mod CV", "%", 0, 100);
        configParam(FM_CV_B_PARAM, -1.f, 1.f, 0.f, "Frequency B mod CV", "%", 0, 100);

        configParam(TRACK_A_PARAM, -1.f, 1.f, 0.f, "Track A", "%", 0, 100);
        configParam(XFM_B_PARAM, -1.f, 1.f, 0.f, "B->A FM", "%", 0, 100);
        configSwitch(TYPE_SWITCH, -1.f, 1.f, 0.f, "Filter type", {"12dB", "18dB", "24dB"});
        configParam(TRACK_B_PARAM, -1.f, 1.f, 0.f, "Track B", "%", 0, 100);

        configInput(RES_INPUT, "Resonance");
        configInput(CURVE_B_INPUT, "LP<>TWP");
        configInput(FREQ_A_INPUT, "Frequency A");
        configInput(FREQ_B_INPUT, "Frequency B");
        configInput(FM_CV_A_INPUT, "FM A");
        configInput(FM_CV_B_INPUT, "FM B");
        configInput(IN_INPUT, "Audio");

        configOutput(OUT_OUTPUT, "Audio");
        configBypass(IN_INPUT, OUT_OUTPUT);
    }

    void onReset() override {
        onSampleRateChange();
    }

    void onSampleRateChange() override {
        // TODO In Rack v2, replace with args.sampleRate
        for (int c = 0; c < 16; c++) {
            enginesA[c].setSampleRate(APP->engine->getSampleRate());
            enginesB[c].setSampleRate(APP->engine->getSampleRate());
        }
    }

    void process(const ProcessArgs& args) override {
        int channels = std::max(inputs[IN_INPUT].getChannels(), 1);

        // Filter A Frame
        ripples::RipplesEngine::Frame frameA{};
        frameA.res_knob = finiteOrZero(params[RES_PARAM].getValue());
        frameA.freq_knob = finiteOrZero(rescale(params[FREQ_A_PARAM].getValue(), std::log2(ripples::kFreqKnobMin), std::log2(ripples::kFreqKnobMax), 0.f, 1.f));
        frameA.fm_knob = finiteOrZero(params[FM_CV_A_PARAM].getValue());
        frameA.fm_global_knob = finiteOrZero(params[FM_GLOBAL_A_PARAM].getValue());
        frameA.track_knob = finiteOrZero(params[TRACK_A_PARAM].getValue());
        frameA.xfm_knob = finiteOrZero(params[XFM_B_PARAM].getValue());
        frameA.mode = (int)clamp(finiteOrZero(params[TYPE_SWITCH].getValue()), -1.f, 1.f) + 2;

        // Filter B Frame
        ripples::RipplesEngine::Frame frameB{};
        frameB.res_knob = frameA.res_knob;
        frameB.freq_knob = finiteOrZero(rescale(params[FREQ_B_PARAM].getValue(), std::log2(ripples::kFreqKnobMin), std::log2(ripples::kFreqKnobMax), 0.f, 1.f));
        frameB.fm_knob = finiteOrZero(params[FM_CV_B_PARAM].getValue());
        frameB.fm_global_knob = finiteOrZero(params[FM_GLOBAL_B_PARAM].getValue());
        frameB.track_knob = finiteOrZero(params[TRACK_B_PARAM].getValue());
        frameB.xfm_knob = 0.f;
        frameB.mode = frameA.mode;
        frameB.b_output = 0.f;

        float curve = finiteOrZero(
            finiteOrZero(params[CURVE_B_PARAM].getValue()) +
                finiteOrZero(params[CURVE_B_CV_PARAM].getValue()) *
                    finiteOrZero(inputs[CURVE_B_INPUT].getVoltage()) * 0.1f);
        curve = clamp(curve, 0.f, 1.f);

        for (int c = 0; c < channels; c++) {
            float res_cv = finiteOrZero(
                finiteOrZero(inputs[RES_INPUT].getPolyVoltage(c)) *
                finiteOrZero(params[RES_CV_PARAM].getValue()));
            float input = finiteOrZero(inputs[IN_INPUT].getVoltage(c));
            frameB.res_cv = res_cv;
            frameB.freq_cv = finiteOrZero(inputs[FREQ_B_INPUT].getPolyVoltage(c));
            frameB.fm_cv = finiteOrZero((inputs[FM_CV_B_INPUT].isConnected()) ? inputs[FM_CV_B_INPUT].getPolyVoltage(c) : inputs[FM_CV_A_INPUT].getPolyVoltage(c));
            frameB.input = input;

            enginesB[c].process(frameB);
            if (!isFinite(frameB.output)) {
                enginesB[c].setSampleRate(args.sampleRate);
                frameB.output = 0.f;
            }

            frameA.res_cv = res_cv;
            frameA.freq_cv = finiteOrZero(inputs[FREQ_A_INPUT].getPolyVoltage(c));
            frameA.fm_cv = finiteOrZero(inputs[FM_CV_A_INPUT].getPolyVoltage(c));
            frameA.input = input;
            frameA.b_output = frameB.output;
            enginesA[c].process(frameA);

            if (!isFinite(frameA.output)) {
                enginesA[c].setSampleRate(args.sampleRate);
                frameA.output = 0.f;
            }

            float output = frameA.output - curve * frameB.output;
            outputs[OUT_OUTPUT].setVoltage(clamp(finiteOrZero(output), -12.f, 12.f), c);
        }

        outputs[OUT_OUTPUT].setChannels(channels);
    }
};

struct TwinPeaksWidget : ModuleWidget {
    TwinPeaksWidget(TwinPeaks* module) {
        setModule(module);
        setPanel(createPanel(asset::plugin(pluginInstance, "res/TwinPeaks.svg"), asset::plugin(pluginInstance, "res/TwinPeaks-dark.svg")));

        addChild(createWidget<ScrewGrey>(Vec(0, 0)));
        addChild(createWidget<ScrewGrey>(Vec(0, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));

        addParam(createParamCentered<RoundHugeBlackKnob>(Vec(45, 78.88), module, TwinPeaks::FREQ_A_PARAM));
        addParam(createParamCentered<RoundHugeBlackKnob>(Vec(135, 78.88), module, TwinPeaks::FREQ_B_PARAM));

        addParam(createParamCentered<RoundBlackKnob>(Vec(22.5, 153.38), module, TwinPeaks::FM_GLOBAL_A_PARAM));
        addParam(createParamCentered<RoundBlackKnob>(Vec(67.5, 153.38), module, TwinPeaks::RES_PARAM));
        addParam(createParamCentered<RoundBlackKnob>(Vec(112.5, 153.38), module, TwinPeaks::CURVE_B_PARAM));
        addParam(createParamCentered<RoundBlackKnob>(Vec(157.5, 153.38), module, TwinPeaks::FM_GLOBAL_B_PARAM));

        addParam(createParamCentered<RoundBlackKnob>(Vec(22.5, 203.79), module, TwinPeaks::TRACK_A_PARAM));
        addParam(createParamCentered<RoundBlackKnob>(Vec(67.5, 203.79), module, TwinPeaks::XFM_B_PARAM));
        addParam(createParamCentered<CKSSThree>(Vec(103.02, 208.09), module, TwinPeaks::TYPE_SWITCH));
        addParam(createParamCentered<RoundBlackKnob>(Vec(157.5, 203.79), module, TwinPeaks::TRACK_B_PARAM));

        addParam(createParamCentered<Trimpot>(Vec(22.5, 252.5), module, TwinPeaks::FM_CV_A_PARAM));
        addParam(createParamCentered<Trimpot>(Vec(67.5, 252.5), module, TwinPeaks::RES_CV_PARAM));
        addParam(createParamCentered<Trimpot>(Vec(112.5, 252.5), module, TwinPeaks::CURVE_B_CV_PARAM));
        addParam(createParamCentered<Trimpot>(Vec(157.5, 252.5), module, TwinPeaks::FM_CV_B_PARAM));

        addInput(createInputCentered<ThemedPJ301MPort>(Vec(22.5, 280.01), module, TwinPeaks::FM_CV_A_INPUT));
        addInput(createInputCentered<ThemedPJ301MPort>(Vec(67.5, 280.01), module, TwinPeaks::RES_INPUT));
        addInput(createInputCentered<ThemedPJ301MPort>(Vec(112.5, 280.01), module, TwinPeaks::CURVE_B_INPUT));
        addInput(createInputCentered<ThemedPJ301MPort>(Vec(157.5, 280.01), module, TwinPeaks::FM_CV_B_INPUT));

        addInput(createInputCentered<ThemedPJ301MPort>(Vec(22.5, 329.25), module, TwinPeaks::IN_INPUT));
        addInput(createInputCentered<ThemedPJ301MPort>(Vec(67.5, 329.25), module, TwinPeaks::FREQ_A_INPUT));
        addInput(createInputCentered<ThemedPJ301MPort>(Vec(112.5, 329.25), module, TwinPeaks::FREQ_B_INPUT));
        addOutput(createOutputCentered<ThemedPJ301MPort>(Vec(157.5, 329.25), module, TwinPeaks::OUT_OUTPUT));
    }
};

Model* modelTwinPeaks = createModel<TwinPeaks, TwinPeaksWidget>("TwinPeaks");
