// wav_trim — pure implementation. See wav_trim.h. NO REAPER / SWELL / vendor.

#include "wav_trim.h"

#include <cstring>  // std::memcpy, std::memcmp

namespace reasampler {

namespace {

// Little-endian readers. Bounds are checked by the caller before each read; these
// assume `off + N <= bytes.size()`. memcpy avoids alignment/aliasing UB.
std::uint16_t readU16LE(const std::vector<std::uint8_t>& b, std::size_t off) {
    return static_cast<std::uint16_t>(b[off] | (b[off + 1] << 8));
}
std::uint32_t readU32LE(const std::vector<std::uint8_t>& b, std::size_t off) {
    return static_cast<std::uint32_t>(b[off]) |
           (static_cast<std::uint32_t>(b[off + 1]) << 8) |
           (static_cast<std::uint32_t>(b[off + 2]) << 16) |
           (static_cast<std::uint32_t>(b[off + 3]) << 24);
}

bool tagEquals(const std::vector<std::uint8_t>& b, std::size_t off, const char* tag) {
    return off + 4 <= b.size() && std::memcmp(b.data() + off, tag, 4) == 0;
}

// WAVE format tags we accept as 32-bit float (see wav_trim.h FORMAT ASSUMPTION).
constexpr std::uint16_t kWaveFormatIeeeFloat  = 0x0003;
constexpr std::uint16_t kWaveFormatExtensible = 0xFFFE;

}  // namespace

WavLayout parseWavLayout(const std::vector<std::uint8_t>& bytes) {
    WavLayout out;

    // Minimum viable RIFF/WAVE: "RIFF"(4) size(4) "WAVE"(4) = 12 bytes.
    if (bytes.size() < 12) return out;
    if (!tagEquals(bytes, 0, "RIFF")) return out;
    if (!tagEquals(bytes, 8, "WAVE")) return out;

    bool haveFmt = false;
    std::uint16_t fmtTag = 0, channels = 0, bitsPerSample = 0;
    std::uint32_t sampleRate = 0;
    std::uint16_t extensibleSubFormatTag = 0;  // set only when fmtTag == kWaveFormatExtensible

    // Walk the sub-chunks after "WAVE" (offset 12). Each is: id(4) size(4) body(size),
    // body padded to an even byte count (RIFF word alignment). Stop cleanly if a
    // header would run past the buffer — a malformed/truncated file is "invalid",
    // never an OOB read.
    std::size_t pos = 12;
    while (pos + 8 <= bytes.size()) {
        const std::size_t bodyOffset = pos + 8;
        const std::uint32_t bodySize = readU32LE(bytes, pos + 4);

        if (tagEquals(bytes, pos, "fmt ")) {
            // fmt body: at least 16 bytes (PCM/float common fields).
            if (bodyOffset + 16 > bytes.size() || bodySize < 16) return out;
            fmtTag        = readU16LE(bytes, bodyOffset + 0);
            channels      = readU16LE(bytes, bodyOffset + 2);
            sampleRate    = readU32LE(bytes, bodyOffset + 4);
            bitsPerSample = readU16LE(bytes, bodyOffset + 14);
            // For WAVE_FORMAT_EXTENSIBLE (0xFFFE), read the SubFormat GUID's leading
            // 2-byte tag at body offset 24 to distinguish float (0x0003) from PCM
            // integer (0x0001) and all other sub-formats. Body must be >= 40 bytes to
            // reach GUID offset 24 + 16 bytes of GUID, and the full GUID must fit in
            // the buffer; otherwise we leave extensibleSubFormatTag at 0 (rejected).
            if (fmtTag == kWaveFormatExtensible) {
                if (bodySize >= 40 && bodyOffset + 40 <= bytes.size()) {
                    extensibleSubFormatTag = readU16LE(bytes, bodyOffset + 24);
                }
            }
            haveFmt = true;
        } else if (tagEquals(bytes, pos, "data")) {
            // The data chunk: PCM starts at bodyOffset, declared length bodySize.
            // Reject if it runs past the buffer (truncated / lying header).
            if (bodyOffset + bodySize > bytes.size()) return out;
            if (!haveFmt) return out;  // data before fmt — not a WAV we parse

            // Plain IEEE-float tag (0x0003): accept as-is.
            // Extensible tag (0xFFFE): accept only when the SubFormat tag read from
            // the GUID at body offset 24 is also 0x0003 (IEEE float). SubFormat tag
            // 0x0001 (PCM integer) or anything else with bitsPerSample==32 is NOT
            // float and must be rejected to prevent mis-decoding as float.
            const bool floatTag = (fmtTag == kWaveFormatIeeeFloat) ||
                                  (fmtTag == kWaveFormatExtensible &&
                                   extensibleSubFormatTag == kWaveFormatIeeeFloat);
            if (!floatTag || bitsPerSample != 32 || channels == 0) return out;

            out.valid               = true;
            out.channelCount        = channels;
            out.sampleRate          = sampleRate;
            out.dataByteOffset      = bodyOffset;
            out.dataByteLength      = bodySize;
            out.riffSizeFieldOffset = 4;
            out.dataSizeFieldOffset = pos + 4;  // the `data` size field (LE uint32)
            return out;
        }

        // Advance past this chunk's body, honoring RIFF even-byte padding. Guard the
        // additions against size_t overflow (a hostile bodySize near SIZE_MAX).
        std::size_t advance = bodySize;
        if (advance & 1u) ++advance;  // pad byte
        if (advance > bytes.size() - bodyOffset) break;  // would overrun -> stop
        pos = bodyOffset + advance;
    }

    return out;  // no data chunk found -> invalid
}

std::vector<AudioSample> extractFloatFrames(const std::vector<std::uint8_t>& bytes,
                                            const WavLayout& layout,
                                            std::size_t startFrame,
                                            std::size_t frameCount) {
    std::vector<AudioSample> out;
    if (!layout.valid) return out;

    const std::size_t bytesPerFrame =
        static_cast<std::size_t>(layout.channelCount) * 4u;
    const std::size_t totalFrames = layout.frameCount();
    if (startFrame >= totalFrames) return out;

    // Clamp the requested span to the frames that actually exist.
    const std::size_t avail = totalFrames - startFrame;
    const std::size_t frames = (frameCount < avail) ? frameCount : avail;
    if (frames == 0) return out;

    const std::size_t firstByte =
        layout.dataByteOffset + startFrame * bytesPerFrame;
    out.resize(frames * layout.channelCount);
    // memcpy each float (LE on target hosts — see header's byte-order note).
    for (std::size_t i = 0; i < out.size(); ++i) {
        float f = 0.0f;
        std::memcpy(&f, bytes.data() + firstByte + i * 4u, 4u);
        out[i] = f;
    }
    return out;
}

WavTruncatePlan planWavTruncate(const WavLayout& layout, std::size_t keptFrames) {
    WavTruncatePlan plan;
    if (!layout.valid) return plan;

    const std::size_t totalFrames = layout.frameCount();
    if (keptFrames > totalFrames) return plan;  // never grow

    const std::size_t bytesPerFrame =
        static_cast<std::size_t>(layout.channelCount) * 4u;
    const std::size_t keptDataBytes = keptFrames * bytesPerFrame;

    plan.valid               = true;
    plan.newFileByteLength   = layout.dataByteOffset + keptDataBytes;
    plan.dataSizeFieldOffset = layout.dataSizeFieldOffset;
    plan.newDataSize         = static_cast<std::uint32_t>(keptDataBytes);
    plan.riffSizeFieldOffset = layout.riffSizeFieldOffset;
    // RIFF size counts everything after the 8-byte "RIFF"+size prefix.
    plan.newRiffSize         = static_cast<std::uint32_t>(plan.newFileByteLength - 8);
    return plan;
}

} // namespace reasampler
