package cat

// Playing a recorded message to the radio over TCI — the voice keyer's path.
//
// The same exchange the tone probe established, with a WAV in place of the
// sine: the radio asks for a frame, we answer with the next slice of the
// message, and it sets the pace. What is added here is the conversion, because
// a recording is whatever the microphone gave it — 16-bit, often mono, often
// not 48 kHz — and the radio wants interleaved float32 at the stream's rate.
//
// The message is converted ONCE, up front, rather than per frame. A voice
// message is a few hundred kilobytes; resampling it inside the callback would
// put arithmetic on the path that has 21 ms to answer, and a late frame is a
// gap in what goes out.

import (
	"encoding/binary"
	"fmt"
	"math"
	"time"
)

// tciTXFirstAskTimeout is how long to wait for the radio to ask for the first
// frame before giving up.
//
// It answers within a frame or two when it is going to answer at all, so this
// is generous. When it stays quiet the cause is always the same — the transmit
// audio source is the microphone rather than TCI — and a fifth of a second of
// carrier is a cheap way to find that out.
const tciTXFirstAskTimeout = 200 * time.Millisecond

// PlayTXAudio sends one message and returns when it has all been handed over,
// or when stop is closed.
//
// The PTT is NOT touched here. The voice keyer keys before calling and unkeys
// after, exactly as it does with a sound card, so the transmission is bracketed
// by the same code whichever way the audio travels.
func (t *TCI) PlayTXAudio(pcm []byte, rate, ch, bits int, stop <-chan struct{}) error {
	t.mu.Lock()
	connected := t.conn != nil
	t.mu.Unlock()
	if !connected {
		return fmt.Errorf("not connected to the radio")
	}

	t.audio.mu.Lock()
	outRate := t.audio.rate
	t.audio.mu.Unlock()
	if outRate <= 0 {
		outRate = 48000
	}

	mono := decodeToMono(pcm, ch, bits)
	if len(mono) == 0 {
		return fmt.Errorf("the message is empty")
	}
	if rate > 0 && rate != outRate {
		mono = resampleLinear(mono, rate, outRate)
	}

	// Served from here on. The callback does nothing but copy and interleave,
	// which is what keeps it inside the frame interval.
	pos := 0
	done := make(chan struct{})
	var closed bool
	t.setTXFeed(func(samples int) []byte {
		if samples <= 0 {
			samples = 2048
		}
		pairs := samples / 2
		if pos >= len(mono) {
			if !closed {
				closed = true
				close(done)
			}
			return nil
		}
		payload := make([]byte, samples*4)
		le := binary.LittleEndian
		for i := 0; i < pairs; i++ {
			var v float32
			if pos < len(mono) {
				v = mono[pos]
				pos++
			}
			bits := math.Float32bits(v)
			le.PutUint32(payload[(i*2)*4:], bits)   // left
			le.PutUint32(payload[(i*2+1)*4:], bits) // right
		}
		return payload
	})
	defer t.setTXFeed(nil)

	// Nothing asked for in a fifth of a second means nothing is listening.
	// Reported plainly: the message would otherwise go out as silence, and a
	// voice keyer that transmits silence is worse than one that refuses.
	deadline := time.Now().Add(tciTXFirstAskTimeout)
	for time.Now().Before(deadline) {
		t.audio.mu.Lock()
		asked := t.audio.txSent > 0
		t.audio.mu.Unlock()
		if asked {
			break
		}
		select {
		case <-stop:
			return nil
		case <-time.After(10 * time.Millisecond):
		}
	}
	t.audio.mu.Lock()
	asked := t.audio.txSent
	t.audio.mu.Unlock()
	if asked == 0 {
		return fmt.Errorf("the radio did not ask for any audio — set its transmit audio source to TCI instead of the microphone")
	}

	// The radio drains the message at real time, so this waits for the feed to
	// run out. The cap is the message's own length with a second to spare: a
	// radio that stops asking mid-message must not hold the transmitter up.
	limit := time.Duration(float64(len(mono))/float64(outRate)*float64(time.Second)) + time.Second
	select {
	case <-done:
	case <-stop:
	case <-time.After(limit):
		debugLog.Printf("TCI: the radio stopped asking for audio before the message ended")
	}
	return nil
}

// decodeToMono turns interleaved PCM into one channel of -1…1 floats.
func decodeToMono(pcm []byte, ch, bits int) []float32 {
	if ch <= 0 {
		ch = 1
	}
	switch bits {
	case 16:
		frame := ch * 2
		out := make([]float32, 0, len(pcm)/frame+1)
		for i := 0; i+frame <= len(pcm); i += frame {
			var sum float32
			for c := 0; c < ch; c++ {
				v := int16(uint16(pcm[i+c*2]) | uint16(pcm[i+c*2+1])<<8)
				sum += float32(v) / 32768
			}
			out = append(out, sum/float32(ch))
		}
		return out
	case 8:
		// Unsigned, centred on 128 — the one format where silence is not zero.
		out := make([]float32, 0, len(pcm)/ch+1)
		for i := 0; i+ch <= len(pcm); i += ch {
			var sum float32
			for c := 0; c < ch; c++ {
				sum += (float32(pcm[i+c]) - 128) / 128
			}
			out = append(out, sum/float32(ch))
		}
		return out
	}
	return nil
}

// resampleLinear moves samples from one rate to another.
//
// Linear interpolation, which is crude and entirely adequate here: a voice
// recording at 16 kHz going to 48 kHz is being INTERPOLATED, and interpolation
// invents no frequencies to alias. Going the other way would want a filter
// first, but a message recorded above the radio's stream rate is not a case
// that arises — the recorder works at 16 kHz and radios stream at 48.
func resampleLinear(in []float32, from, to int) []float32 {
	if from <= 0 || to <= 0 || from == to || len(in) == 0 {
		return in
	}
	ratio := float64(from) / float64(to)
	n := int(float64(len(in)) / ratio)
	out := make([]float32, n)
	for i := 0; i < n; i++ {
		src := float64(i) * ratio
		j := int(src)
		frac := float32(src - float64(j))
		if j+1 < len(in) {
			out[i] = in[j]*(1-frac) + in[j+1]*frac
		} else {
			out[i] = in[len(in)-1]
		}
	}
	return out
}
