#include "core/instrument/engine/filter/filter_morph.h"

#include <cmath>

namespace reasampler::instrument::engine::filter {
namespace {

constexpr double kPi = 3.14159265358979323846;

struct Pair {
    double a, b;
};

// Equal-power crossfade, EXACT at both ends by construction rather than by rounding: cos and sin
// of the leg's quarter turn are only 1e-17 from 0/1 at the endpoints, and the endpoints have to
// be pure taps, not a pure tap plus a -324 dB neighbour.
Pair equalPower(double t) {
    if (!(t > 0.0)) return {1.0, 0.0};
    if (t >= 1.0) return {0.0, 1.0};
    const double theta = 0.5 * kPi * t;
    return {std::cos(theta), std::sin(theta)};
}

}  // namespace

MorphWeights morphWeights(float norm, MorphLaw law) {
    const double n = norm < 0.0 ? 0.0 : (norm > 1.0 ? 1.0 : static_cast<double>(norm));

    MorphWeights w;
    if (law == MorphLaw::HighNotchLow) {
        // ONE crossfade across the whole sweep rather than two legs, so HP and LP carry weight
        // together everywhere between the endpoints and are equal at the centre.
        const Pair p = equalPower(n);
        w.hp = static_cast<float>(p.a);
        w.bp = 0.0f;
        w.lp = static_cast<float>(p.b);
        return w;
    }

    if (n <= 0.5) {
        const Pair p = equalPower(2.0 * n);  // HP -> BP
        w.hp = static_cast<float>(p.a);
        w.bp = static_cast<float>(p.b);
        w.lp = 0.0f;
    } else {
        const Pair p = equalPower(2.0 * n - 1.0);  // BP -> LP
        w.hp = 0.0f;
        w.bp = static_cast<float>(p.a);
        w.lp = static_cast<float>(p.b);
    }
    return w;
}

MorphMix morphMix(const MorphWeights& w, float k) {
    MorphMix m;
    m.m0 = w.hp;
    m.m1 = w.bp - w.hp * k;
    m.m2 = w.lp - w.hp;
    return m;
}

MorphMix bypassMix() { return MorphMix{1.0f, 0.0f, 0.0f}; }

}  // namespace reasampler::instrument::engine::filter
