#include "Runner.h"
#include <algorithm>
#include <cstring>

namespace deeppluck::benchmark
{
juce::var compareAudio (const std::vector<float>& reference, const std::vector<float>& candidate)
{
    require(!reference.empty() && reference.size()%2==0 && reference.size()==candidate.size(),"Audio length/channel mismatch");
    const auto frames=reference.size()/2;
    std::array<double,4> maxError{},diffEnergy{},refPeak{},candidatePeak{},maxWindowRms{},maxRelative{};
    std::array<size_t,4> maxPosition{},worstWindow{};
    for(size_t begin=0;begin<frames;begin+=1024)
    {
        const auto end=std::min(frames,begin+1024);
        std::array<double,4> windowDiff{},windowRef{};
        for(size_t i=begin;i<end;++i)
        {
            const double rl=reference[2*i],rr=reference[2*i+1],cl=candidate[2*i],cr=candidate[2*i+1];
            require(std::isfinite(rl)&&std::isfinite(rr)&&std::isfinite(cl)&&std::isfinite(cr),"Nonfinite raw audio before statistics");
            const std::array r{rl,rr,(rl+rr)*.5,(rl-rr)*.5},c{cl,cr,(cl+cr)*.5,(cl-cr)*.5};
            for(size_t ch=0;ch<4;++ch)
            {
                const auto difference=c[ch]-r[ch], magnitude=std::abs(difference);
                if(magnitude>maxError[ch]) { maxError[ch]=magnitude; maxPosition[ch]=i; }
                refPeak[ch]=std::max(refPeak[ch],std::abs(r[ch])); candidatePeak[ch]=std::max(candidatePeak[ch],std::abs(c[ch]));
                diffEnergy[ch]+=difference*difference; windowDiff[ch]+=difference*difference; windowRef[ch]+=r[ch]*r[ch];
            }
        }
        for(size_t ch=0;ch<4;++ch)
        {
            const auto count=static_cast<double>(end-begin);
            const auto rms=std::sqrt(windowDiff[ch]/count), refRms=std::sqrt(windowRef[ch]/count);
            if(rms>maxWindowRms[ch]) { maxWindowRms[ch]=rms; worstWindow[ch]=begin; }
            require(rms<=1e-7,"1024-frame absolute RMS gate failed");
            if(refRms>=1e-4)
            {
                maxRelative[ch]=std::max(maxRelative[ch],rms/refRms);
                require(rms/refRms<=1e-6,"1024-frame relative RMS gate failed");
            }
        }
    }
    std::array<double,4> diffRms{},peakDifference{};
    for(size_t ch=0;ch<4;++ch)
    {
        diffRms[ch]=std::sqrt(diffEnergy[ch]/static_cast<double>(frames));
        peakDifference[ch]=std::abs(candidatePeak[ch]-refPeak[ch]);
        require(maxError[ch]<=1e-6 && diffRms[ch]<=1e-7 && peakDifference[ch]<=1e-6,"Full-clip numerical gate failed");
    }
    auto out=object(); const auto identical=std::memcmp(reference.data(),candidate.data(),reference.size()*sizeof(float))==0;
    put(out,"bit_identical",identical); put(out,"max_error_L_R_M_S",array(maxError));
    put(out,"rms_difference_L_R_M_S",array(diffRms)); put(out,"max_window_rms_L_R_M_S",array(maxWindowRms));
    put(out,"max_relative_window_error_L_R_M_S",array(maxRelative)); put(out,"peak_difference_L_R_M_S",array(peakDifference));
    juce::Array<juce::var> positions,windows;
    for(const auto p:maxPosition) positions.add(static_cast<juce::int64>(p));
    for(const auto p:worstWindow) windows.add(static_cast<juce::int64>(p));
    put(out,"max_error_frame_L_R_M_S",positions); put(out,"worst_window_begin_L_R_M_S",windows);
    // This milestone has not authorized a particular FP reordering experiment.
    // The numerical maxima above do not silently authorize a non-null result.
    require(identical,"Non-bitidentical output requires an explicitly documented FP-reordering experiment; none is enabled");
    return out;
}
}
