package audio

import (
	"testing"
	"time"
)

// A silent second source must not silence the recording.
//
// From a real session: a Flex with "From Radio" on DAX RX and "Recording mic"
// on DAX Mic. In CW the mic path does not run, so DAX Mic delivered nothing —
// and because the mixer took min(len(A), len(B)) samples, nothing at all was
// recorded. The whole QSO ended in "recording was empty".
func TestMixerSurvivesASilentSource(t *testing.T) {
	r := &Recorder{twoSrc: true, gainA: 1, gainB: 1, running: true, active: true, prerollSamples: sampleRate}

	// The radio has been talking; the mic has not spoken for well over the
	// grace period.
	r.bufA = make([]int16, 800)
	for i := range r.bufA {
		r.bufA[i] = 1000
	}
	r.lastA = time.Now()
	r.lastB = time.Now().Add(-5 * time.Second)

	r.mixTick()

	if len(r.acc) == 0 {
		t.Fatal("nothing was recorded — a silent mic must not stop the radio being captured")
	}
	if r.acc[0] != 1000 {
		t.Errorf("sample = %d, want 1000 — the live source must pass through unchanged", r.acc[0])
	}
}

// While BOTH sources are alive the two are mixed, as before.
func TestMixerStillMixesBothSources(t *testing.T) {
	r := &Recorder{twoSrc: true, gainA: 1, gainB: 1, running: true, active: true, prerollSamples: sampleRate}
	r.bufA = []int16{100, 100, 100}
	r.bufB = []int16{50, 50, 50}
	now := time.Now()
	r.lastA, r.lastB = now, now

	r.mixTick()

	if len(r.acc) != 3 {
		t.Fatalf("recorded %d samples, want 3", len(r.acc))
	}
	if r.acc[0] != 150 {
		t.Errorf("sample = %d, want 150 — both sources should be summed", r.acc[0])
	}
}

// Just after capture starts, a source that has not delivered yet is NOT written
// off: the first samples take a moment to arrive, and declaring the mic dead at
// once would drop the opening of every recording.
func TestMixerWaitsBrieflyAtStartup(t *testing.T) {
	r := &Recorder{twoSrc: true, gainA: 1, gainB: 1, running: true, active: true, prerollSamples: sampleRate}
	r.startedAt = time.Now()
	r.bufA = []int16{100, 100}

	r.mixTick()

	if len(r.acc) != 0 {
		t.Errorf("recorded %d samples immediately — the second source deserves a moment to start", len(r.acc))
	}
}

// A source that has NEVER delivered is written off once enough time has passed.
//
// This is the case that actually happens, and the one the first version missed:
// a DAX Mic channel switched off delivers not one sample, so it had no
// last-delivery time and stayed forever "not yet dry" — taking the whole
// recording down with it while the radio audio streamed perfectly.
func TestMixerWritesOffASourceThatNeverSpoke(t *testing.T) {
	r := &Recorder{twoSrc: true, gainA: 1, gainB: 1, running: true, active: true, prerollSamples: sampleRate}
	r.startedAt = time.Now().Add(-5 * time.Second)
	r.bufA = []int16{700, 700, 700}
	r.lastA = time.Now()
	// lastB stays zero: the mic has never produced anything at all.

	r.mixTick()

	if len(r.acc) != 3 {
		t.Fatalf("recorded %d samples, want 3 — the radio was streaming the whole time", len(r.acc))
	}
	if r.acc[0] != 700 {
		t.Errorf("sample = %d, want 700", r.acc[0])
	}
}

// Nothing captured during the grace period is thrown away.
//
// The drift guard exists for two sound cards running on their own clocks. A
// STARVED source is not drift, and treating it as such discarded the radio
// audio a second at a time while the mixer was still waiting for the mic — so
// the opening of every CW recording was lost although it had been captured.
func TestGracePeriodKeepsWhatWasCaptured(t *testing.T) {
	r := &Recorder{twoSrc: true, gainA: 1, gainB: 1, running: true, active: true, prerollSamples: sampleRate}
	r.startedAt = time.Now()

	// Three seconds of radio audio arrive while the mic says nothing at all.
	r.bufA = make([]int16, 3*sampleRate)
	for i := range r.bufA {
		r.bufA[i] = 500
	}
	r.lastA = time.Now()
	r.mixTick() // still inside the grace period: nothing is written yet…

	if len(r.acc) != 0 {
		t.Fatalf("wrote %d samples before the mic was written off", len(r.acc))
	}
	if len(r.bufA) != 3*sampleRate {
		t.Fatalf("buffered audio was trimmed to %d samples — it must be kept, not dropped", len(r.bufA))
	}

	// …and once the mic is written off, everything captured comes through.
	r.startedAt = time.Now().Add(-5 * time.Second)
	r.mixTick()
	if len(r.acc) != 3*sampleRate {
		t.Errorf("recorded %d samples, want the full %d captured during the wait", len(r.acc), 3*sampleRate)
	}
}
