// Detect It Easy: detection rule file
// Author: Kaens <TG@kaens>

meta("audio", "FLAC waveform audio (.FLAC)");

function detect() {
    //ref https://xiph.org/flac/format.html#format_overview
    if (!File.compare("'fLaC'")) return;
    end = 0; p = 4; hk = hks = malformed = "";
    ch = rate = bps = smp = smplen = sz = xa = xna = 0; seekt = -1;
    while (!end && p < File.getSize()) {
        t = File.read_uint8(p); if (p == 5 && (t & 0xFF)) return;
        last = t & 0x80; mdbt = t & 0x7F; mdsz = File.read_uint24(p + 1, _BE);
        p += 4;
        switch (mdbt) {
            case 0: hk = "STREAMINFO";
                minb = File.read_uint16(p, _BE), maxb = File.read_uint16(p + 2, _BE),
                    minf = File.read_uint24(p + 4, _BE), maxf = File.read_uint24(p + 7, _BE);
                if (minb < 16 || maxb > 65535 || minb > maxb) malformed += "!badblksz";
                br = new BitReader(p + 10, _BE);
                rate = br.read(20); ch = 1 + br.read(3); bps = 1 + br.read(5);
                smp = br.read(36); smplen = smp / rate;
                break;
            case 1: hk = "PADDING"; break;
            case 2: hk = "APP"; s = File.read_ansiString(p, 4);
                switch (s) { //from https://xiph.org/flac/id.html on 2024-02-20
                    case "ATCH": hk += ":Flacfile"; break;
                    case "BSOL": hk += ":beSolo"; break;
                    case "BUGS": hk += ":Bugs Player"; break;
                    case "Cues": hk += ":GoldWave"; break;
                    case "Fica": hk += ":CUE Splitter"; break;
                    case "Ftol": hk += ":flac-tools"; break;
                    case "MOTB": hk += ":MOTB MetaCzar"; break;
                    case "MPSE": hk += ":MP3 Stream Editor"; break;
                    case "MuML": hk += ":MusicML"; break;
                    case "RIFF": hk += ":Sound Devices RIFF c.s"; break;
                    case "SFFL": hk += ":Sound Font FLAC"; break;
                    case "SONY": hk += ":Sony Creative Software"; break;
                    case "SQEZ": hk += ":flacsqueeze"; break;
                    case "TtWv": hk += ":TwistedWave"; break;
                    case "UITS": hk += ":UITS Embedding tools"; break;
                    case "aiff": hk += ":FLAC AIFF c.s"; break;
                    case "imag": hk += ":flac-image file"; break;
                    case "peem": hk += ":Parseable Embedded Extensible Metadata"; break;
                    case "qfst": hk += ":QFLAC Studio"; break;
                    case "riff": hk += ":FLAC RIFF c.s (OpenMPT?)"; break;
                    case "tune": hk += ":TagTuner"; break;
                    case "w64 ": hk += ":FLAC Wave64 chunks"; break;
                    case "xbat": hk += ":XBAT"; break;
                    case "xbat": hk += ":xmcd"; break;
                    default: hk += ":<" + s + ">"
                }
                break;
            case 3: hk = "SEEKTABLE"; seekt = p; hk += "#" + Math.floor(mdsz, 18); break;
            case 4: hk = "VORBIS_COMMENT";
                break;
            case 5: hk = "CUESHEET";
                xa = xna = 0; trkn = File.read_uint8(p + 0x18B); u = p + 0x18C;
                for (i = 0; i < trkn; i++) {
                    u += 0x15; if (File.read_uint8(u++) & 0x80) xna++; else xa++;
                    u += 0x0D; u += 12 * File.read_uint8(u++);
                }
                break;
            case 6: hk = "PICTURE"; break;
            case 0x7F: end = 1; hk = "frames"; break;
            default: hk = "unknown" + Hex(mdbt)
        }
        if (hk != "frames") hks = hks.append("[" + hk + "]");
        p += mdsz; if (last) end = 1;
    }
    if (sz < p) sz = p;
    t = p + (smp * bps * ch >> 3); if (sz < t) sz = t; _log("t:" + t)
    if (seekt >= 0) for (i = 0; i < mdsz / 18; i++) {
        smpn = File.read_uint64(seekt + i * 18, _BE);
        seek = p + File.read_uint64(seekt + 8 + i * 18, _BE);
        st = seek + (File.read_uint16(seekt + 16 + i * 18, _BE) * bps * ch >> 3);
        if (sz < st) sz = st
    }
    end = 0;
    if (!smp && File.isDeepScan())
        while (!end && p < File.getSize()) {
            end = 1
            // TODO. Or not to do, this is the question >__> But prolly do; no other way to always find filesize
        }
    if (malformed) sVersion += malformed;
    bDetected = 1;
    if (Binary.isVerbose()) {
        sOption(Math.floor(smplen / 60) + ":" + (Math.floor(smplen) % 60).padStart(2, '0'));
        if (xa > 1 || xna) sOption("x" + xa + "+" + xna); //audio and non-audio tracks
        sOption(hks);
        sOption(rate + "Hz ch:" + ch + " bps:" + bps);//+" sz:"+sz);
        if (minb === maxb) sOption("fixed-size stream");
    }

    return result();
}