package cwdecode

import (
	"math"
	"strings"
	"testing"
)

// reverse Morse map for the synthesizer.
func charToMorse() map[byte]string {
	m := map[byte]string{}
	for code, ch := range morse {
		m[ch] = code
	}
	return m
}

// keyMessage synthesizes a clean keyed tone for msg at the given WPM/pitch.
func keyMessage(msg string, fs, wpm int, pitch float64) []int16 {
	return keyMessageAmp(msg, fs, wpm, pitch, 9000)
}

func keyMessageAmp(msg string, fs, wpm int, pitch, amp float64) []int16 {
	dot := fs * 1200 / (wpm * 1000) // samples per dot
	c2m := charToMorse()
	var out []int16
	phase := 0.0
	dphi := 2 * math.Pi * pitch / float64(fs)

	tone := func(n int) {
		for i := 0; i < n; i++ {
			out = append(out, int16(amp*math.Sin(phase)))
			phase += dphi
		}
	}
	silence := func(n int) {
		for i := 0; i < n; i++ {
			out = append(out, 0)
		}
	}

	silence(fs / 4) // 250 ms lead-in for AGC warmup
	for i := 0; i < len(msg); i++ {
		ch := msg[i]
		if ch == ' ' {
			silence(7 * dot)
			continue
		}
		code := c2m[ch]
		for j := 0; j < len(code); j++ {
			if code[j] == '.' {
				tone(dot)
			} else {
				tone(3 * dot)
			}
			silence(dot) // inter-element gap
		}
		silence(3 * dot) // inter-character gap (on top of the trailing element gap)
	}
	silence(fs / 4)
	return out
}

func TestDecodeCleanSignal(t *testing.T) {
	const fs = 16000
	var sb strings.Builder
	d := New(fs, func(s string) { sb.WriteString(s) }, nil)

	// Repeat so AGC warm-up only costs the first word.
	samples := keyMessage("PARIS PARIS PARIS", fs, 22, 700)
	// Feed in small chunks like the live capture would.
	for i := 0; i < len(samples); i += 256 {
		end := i + 256
		if end > len(samples) {
			end = len(samples)
		}
		d.Process(samples[i:end])
	}

	got := strings.ToUpper(sb.String())
	if !strings.Contains(got, "PARIS") {
		t.Fatalf("decoded %q, want it to contain PARIS", got)
	}
}

func TestDecodeWithQRM(t *testing.T) {
	const fs = 16000
	// Target at 700 Hz; a strong interfering keyed signal at 950 Hz, slightly
	// quieter, sending different text. The pitch lock should hold on the target.
	target := keyMessageAmp("PARIS PARIS PARIS", fs, 20, 700, 9000)
	qrm := keyMessageAmp("BK DE QRZ QRZ TEST", fs, 26, 950, 6500)
	mix := make([]int16, len(target))
	for i := range target {
		v := int(target[i])
		if i < len(qrm) {
			v += int(qrm[i])
		}
		if v > 32767 {
			v = 32767
		} else if v < -32768 {
			v = -32768
		}
		mix[i] = int16(v)
	}

	var sb strings.Builder
	d := New(fs, func(s string) { sb.WriteString(s) }, nil)
	for i := 0; i < len(mix); i += 256 {
		end := i + 256
		if end > len(mix) {
			end = len(mix)
		}
		d.Process(mix[i:end])
	}
	got := strings.ToUpper(sb.String())
	if !strings.Contains(got, "PARIS") {
		t.Fatalf("with QRM, decoded %q, want it to contain PARIS", got)
	}
}

func TestDecodeFirstCharStrong(t *testing.T) {
	const fs = 16000
	var sb strings.Builder
	d := New(fs, func(s string) { sb.WriteString(s) }, nil)
	// Strong signal: the very first element (T = a dash) must not be eaten by
	// lock acquisition. Output should begin with the first character.
	samples := keyMessageAmp("TEST DE", fs, 20, 700, 16000)
	for i := 0; i < len(samples); i += 200 {
		end := i + 200
		if end > len(samples) {
			end = len(samples)
		}
		d.Process(samples[i:end])
	}
	got := strings.ToUpper(strings.TrimSpace(sb.String()))
	if !strings.HasPrefix(got, "TEST") {
		t.Fatalf("first chars lost on a strong signal: decoded %q, want it to start with TEST", got)
	}
}

func TestDecodeWithAmplitudeRipple(t *testing.T) {
	const fs = 16000
	// A real signal's tone amplitude wobbles within a mark; if the floor chases
	// it, dashes fragment into dots ("all dots" garbage). Apply ±30% ripple.
	samples := keyMessageAmp("CQ TEST DE OM", fs, 24, 800, 10000)
	rp := 0.0
	for i := range samples {
		rp += 2 * math.Pi * 35 / float64(fs) // 35 Hz amplitude wobble
		samples[i] = int16(float64(samples[i]) * (1 + 0.3*math.Sin(rp)))
	}
	var sb strings.Builder
	d := New(fs, func(s string) { sb.WriteString(s) }, nil)
	for i := 0; i < len(samples); i += 256 {
		end := i + 256
		if end > len(samples) {
			end = len(samples)
		}
		d.Process(samples[i:end])
	}
	got := strings.ToUpper(sb.String())
	if !strings.Contains(got, "TEST DE OM") {
		t.Fatalf("dashes fragmented under amplitude ripple: decoded %q", got)
	}
}

func TestDecodeCQFixedPitch(t *testing.T) {
	const fs = 16000
	var sb strings.Builder
	d := New(fs, func(s string) { sb.WriteString(s) }, nil)
	d.SetTarget(700) // fixed pitch like the user's manual override
	samples := keyMessageAmp("CQ CQ CQ DE OM", fs, 26, 700, 9000)
	for i := 0; i < len(samples); i += 200 {
		end := i + 200
		if end > len(samples) {
			end = len(samples)
		}
		d.Process(samples[i:end])
	}
	got := strings.ToUpper(sb.String())
	if n := strings.Count(got, "CQ"); n < 2 {
		t.Fatalf("first element of CQ dropped: decoded %q (only %d CQ)", got, n)
	}
}

func TestDecodeNumbersAndProsign(t *testing.T) {
	const fs = 16000
	var sb strings.Builder
	d := New(fs, func(s string) { sb.WriteString(s) }, nil)
	samples := keyMessage("TEST 599 TEST", fs, 18, 650)
	for i := 0; i < len(samples); i += 200 {
		end := i + 200
		if end > len(samples) {
			end = len(samples)
		}
		d.Process(samples[i:end])
	}
	got := strings.ToUpper(sb.String())
	if !strings.Contains(got, "599") {
		t.Fatalf("decoded %q, want it to contain 599", got)
	}
}
