//! Render PolyMeter grooves through pm-synth to 16-bit mono 48 kHz WAVs so we can audition the
//! ported 808/909 voices on a host (no hardware). Output files are written to the current dir.
use pm_synth::{default_kit, Player, Synth, SPIKE_BARS, SPIKE_PROGRAM, SR};
use std::fs::File;
use std::io::{self, Write};

/// Render through the shared [`Player`] — the *exact* code path the Daisy firmware runs in its SAI
/// callback (`pm-daisy`). This is the host-side preview/"simulator": the samples here are what the
/// hardware will produce (before its codec). `secs` of audio, looping at the pattern boundary.
fn render_device(prog: &str, bars: i64, secs: f32) -> Vec<i16> {
    let track = track_format::parse(prog);
    let mut player = Player::new(&track, bars);
    let total = (SR * secs) as usize;
    (0..total).map(|_| (player.next_sample() * 30000.0) as i16).collect()
}

fn render(prog: &str, bars: i64) -> Vec<i16> {
    let track = track_format::parse(prog);
    let mbar = track_format::schedule::master_bar_ns(&track);
    let clicks = track_format::schedule::render(&track, bars);
    let total_ns = mbar * bars + 1_000_000_000; // + 1 s tail for decays
    let total_samples = (total_ns as f64 / 1e9 * SR as f64) as usize;
    let mut synth = Synth::new();
    let mut out = Vec::with_capacity(total_samples);
    let mut ci = 0usize;
    for n in 0..total_samples {
        let t_ns = (n as f64 / SR as f64 * 1e9) as i64;
        while ci < clicks.len() && clicks[ci].time_ns <= t_ns {
            let voice = default_kit(&track.lanes[clicks[ci].lane].sound);
            synth.trigger(voice, clicks[ci].level);
            ci += 1;
        }
        out.push((synth.next_sample() * 30000.0) as i16);
    }
    out
}

fn write_wav(path: &str, samples: &[i16]) -> io::Result<()> {
    let data_len = (samples.len() * 2) as u32;
    let sr = 48_000u32;
    let mut f = File::create(path)?;
    f.write_all(b"RIFF")?;
    f.write_all(&(36 + data_len).to_le_bytes())?;
    f.write_all(b"WAVE")?;
    f.write_all(b"fmt ")?;
    f.write_all(&16u32.to_le_bytes())?; // PCM fmt chunk
    f.write_all(&1u16.to_le_bytes())?; // format = PCM
    f.write_all(&1u16.to_le_bytes())?; // channels = mono
    f.write_all(&sr.to_le_bytes())?;
    f.write_all(&(sr * 2).to_le_bytes())?; // byte rate
    f.write_all(&2u16.to_le_bytes())?; // block align
    f.write_all(&16u16.to_le_bytes())?; // bits/sample
    f.write_all(b"data")?;
    f.write_all(&data_len.to_le_bytes())?;
    for s in samples {
        f.write_all(&s.to_le_bytes())?;
    }
    Ok(())
}

fn main() {
    let demos: &[(&str, &str, i64)] = &[
        ("pm-synth-909.wav", "t124;kick909:4;clap909:4=.X.X;hat909:4/2=.X.X.X.X", 4),
        ("pm-synth-808.wav", "t90;kick808:4=X..x;snare808:4=.X.X;hat808:4/2", 4),
        ("pm-synth-default.wav", "t120;kick:4;snare:4=.x.x;hatClosed:4/2", 4),
        ("pm-synth-poly.wav", "t100;kick909:4;clap909:5~;hat808:4/2", 4),
    ];
    for (name, prog, bars) in demos {
        let s = render(prog, *bars);
        match write_wav(name, &s) {
            Ok(_) => println!("wrote {} ({:.1}s, {} samples)", name, s.len() as f32 / SR, s.len()),
            Err(e) => eprintln!("error writing {}: {}", name, e),
        }
    }

    // Device preview: the SAME Player the Daisy firmware runs, rendered to a WAV. Eight seconds of
    // SPIKE_PROGRAM, looping at the pattern boundary — a faithful preview of the hardware output.
    let preview = render_device(SPIKE_PROGRAM, SPIKE_BARS, 8.0);
    match write_wav("pm-daisy-preview.wav", &preview) {
        Ok(_) => println!(
            "wrote pm-daisy-preview.wav ({:.1}s) — device-identical render of `{}`",
            preview.len() as f32 / SR,
            SPIKE_PROGRAM
        ),
        Err(e) => eprintln!("error writing pm-daisy-preview.wav: {}", e),
    }
}
