#include "distortion.h"

using Xybrid::Effects::Distortion;
using namespace Xybrid::Data;

#include "util/strings.h"

#include "nodelib/basics.h"
using namespace Xybrid::NodeLib;

#include "data/audioframe.h"
#include "data/porttypes.h"

#include "config/pluginregistry.h"
using namespace Xybrid::Config;

#include "audio/audioengine.h"
using namespace Xybrid::Audio;

#include "ui/patchboard/nodeobject.h"
#include "ui/gadgets/layoutgadget.h"
#include "ui/gadgets/togglegadget.h"
#include "ui/gadgets/knobgadget.h"
using namespace Xybrid::UI;

#include <cmath>

#include <QCborMap>

// clazy:excludeall=non-pod-global-static
RegisterPlugin(Distortion, {
    i->id = "fx:distortion";
    i->displayName = "Distortion";
    i->category = "Effect";
})

Distortion::Distortion() = default;

void Distortion::init() {
    addPort(Port::Input, Port::Audio, 0);
    addPort(Port::Output, Port::Audio, 0);
}

void Distortion::reset() {
    //
}

namespace {
    inline double sxp(double v, double e) {
        double s = v < 0 ? -1.0 : 1.0;
        return std::pow(std::abs(v), e) * s;
    }
    inline AudioFrame sxp(AudioFrame v, double e) { return {sxp(v.l, e), sxp(v.r, e)}; }
}

void Distortion::process() {
    auto in = std::static_pointer_cast<AudioPort>(port(Port::Input, Port::Audio, 0));
    auto out = std::static_pointer_cast<AudioPort>(port(Port::Output, Port::Audio, 0));
    in->pull();
    out->pull();

    auto ts = audioEngine->curTickSize();

    auto d = drive.start(), s = shape.start(), m = mix.start(), o = output.start();

    for (size_t f = 0; f < ts; f++) {
        AudioFrame inp = (*in)[f];
        auto g = inp.gainBalance(d.next());
        auto pv = s.next();
        auto exp = pv > 0.0 ? 1.0 / (1.0 + pv) : -pv + 1.0;


        (*out)[f] = AudioFrame::lerp(inp, sxp(g.clamp(), exp), m.next()).gainBalance(o.next());
    }
}

void Distortion::saveData(QCborMap& m) const {
    drive.save(m);
    shape.save(m);
    mix.save(m);
    output.save(m);
}

void Distortion::loadData(const QCborMap& m) {
    drive.load(m);
    shape.load(m);
    mix.load(m);
    output.load(m);
}

void Distortion::onGadgetCreated() {
    if (!obj) return;
    auto l = new LayoutGadget(obj);

    KnobGadget::autoGain(l, drive);
    (new KnobGadget(l))->setRange(0, 0, 0.1)->bind(shape)->setTextFunc(KnobGadget::textOffset);
    KnobGadget::autoPercent(l, mix);
    KnobGadget::autoGain(l, output);
}
