// curve_tessellate.cpp — see curve_tessellate.h. Pure geometry; no host types.

#include "core/instrument/ui/curve_tessellate.h"

#include <algorithm>
#include <cstdlib>

#include "core/util/curve_law.h"

namespace reasampler::instrument::ui {

using StrokePoint = reasampler::ui::StrokePoint;

namespace {

StrokePoint pt(double x, double y) {
    return StrokePoint{static_cast<float>(x), static_cast<float>(y)};
}

// The samples strictly BETWEEN two nodes. The endpoints are the caller's, so a shared node is
// emitted once and the polyline carries no zero-length joint.
void appendInterior(int x0, int y0, int x1, int y1, double exponent,
                    std::vector<StrokePoint>& out) {
    const int span = std::abs(x1 - x0);
    if (span < 2 || y1 == y0 || exponent == util::kCurveNeutral) return;
    const double dx = static_cast<double>(x1 - x0);
    const double dy = static_cast<double>(y1 - y0);
    for (int i = 1; i < span; ++i) {
        // phi is exact at every column, so x lands on the integer column and the last interior
        // sample is one column short of the end node.
        const double phi = static_cast<double>(i) / static_cast<double>(span);
        out.push_back(pt(static_cast<double>(x0) + dx * phi,
                         static_cast<double>(y0) + dy * util::curveMap(phi, exponent)));
    }
}

}  // namespace

double segmentCurve(const StageEnvelope& env, EnvNode endNode) {
    switch (endNode) {
        case EnvNode::AttackEnd:  return env.attackCurve;
        case EnvNode::DecayEnd:   return env.decayCurve;
        case EnvNode::ReleaseEnd: return env.releaseCurve;
        default:                  return util::kCurveNeutral;
    }
}

void buildEnvelopeTrace(const std::vector<EnvVertex>& poly, const StageEnvelope& env, int xLo,
                        int xHi, std::vector<StrokePoint>& out) {
    out.clear();
    bool started = false;
    int px = 0;
    int py = 0;
    for (const EnvVertex& v : poly) {
        if (v.knot) continue;
        const int x = std::max(xLo, std::min(xHi, v.x));
        if (started) appendInterior(px, py, x, v.y, segmentCurve(env, v.node), out);
        out.push_back(pt(x, v.y));
        started = true;
        px = x;
        py = v.y;
    }
}

}  // namespace reasampler::instrument::ui
