package cat

import (
	"strings"
	"testing"
	"time"
)

// What reaches the rig's keyer.
//
// An unsupported byte does not produce an error on a Yaesu — it can abort the
// whole buffer, so the rest of the message is lost silently. Filtering is
// therefore part of correctness, not tidiness.
func TestFilterYaesuCW(t *testing.T) {
	cases := []struct{ in, want string }{
		{"cq cq de f4bpo", "CQ CQ DE F4BPO"},
		{"F4BPO/P", "F4BPO/P"},
		{"599 tu 73", "599 TU 73"},
		// Accents and symbols the keyer has no code for.
		{"héllo", "HLLO"},
		{"a*b#c", "ABC"},
		// Runs of whitespace collapse: the rig sends each space as a word gap, so
		// three in a row is three gaps and the message crawls.
		{"cq    cq", "CQ CQ"},
		{"  padded  ", "PADDED"},
		{"\tcq\ncq\t", "CQ CQ"},
		{"", ""},
		{"   ", ""},
	}
	for _, c := range cases {
		if got := filterYaesuCW(c.in); got != c.want {
			t.Errorf("filterYaesuCW(%q) = %q, want %q", c.in, got, c.want)
		}
	}
}

// Long macros have to be split: one KY command carries 24 characters and the rig
// DROPS what does not fit rather than reporting an error, so a contest CQ would
// lose its tail with no sign anywhere.
func TestYaesuCWChunking(t *testing.T) {
	msg := filterYaesuCW("cq cq cq de f4bpo f4bpo f4bpo pse k")
	if len(msg) <= yaesuCWChunk {
		t.Fatalf("test message is %d chars — too short to exercise chunking", len(msg))
	}
	var chunks []string
	for rest := msg; len(rest) > 0; {
		n := yaesuCWChunk
		if len(rest) < n {
			n = len(rest)
		}
		chunks = append(chunks, rest[:n])
		rest = rest[n:]
	}
	for i, c := range chunks {
		if len(c) > yaesuCWChunk {
			t.Errorf("chunk %d is %d chars, over the %d limit", i, len(c), yaesuCWChunk)
		}
	}
	// Nothing added, nothing lost: the message the operator typed is what the
	// keyer receives.
	if joined := strings.Join(chunks, ""); joined != msg {
		t.Errorf("chunks rejoin to %q, want %q", joined, msg)
	}
}

// Reading the KY status reply.
//
// The first version demanded exactly "KY0;" with the digit at byte 2, and on a
// real FTDX10 that refused to send at all: the reply was phrased differently,
// every poll read as "still full", and after four seconds the send gave up
// having keyed nothing. Hence the asymmetry — only a clear "1" blocks.
func TestYaesuCWBufferFull(t *testing.T) {
	cases := []struct {
		reply string
		full  bool
	}{
		{"KY0;", false},
		{"KY1;", true},
		{"KY 0;", false}, // a space before the digit
		{"KY 1;", true},
		{"ky1;", true}, // lower case
		{" KY0; ", false},
		// Shapes we have no sample of must NOT block: silence with no explanation
		// is a worse failure than sending a moment early.
		{"KY;", false},
		{"", false},
		{"FA014074000;", false}, // another command's reply arriving first
		{"KYX;", false},
	}
	for _, c := range cases {
		if got := yaesuCWBufferFull(c.reply); got != c.full {
			t.Errorf("yaesuCWBufferFull(%q) = %v, want %v", c.reply, got, c.full)
		}
	}
}

// Pacing when the rig will not report its buffer.
//
// An FTDX10 answers "?;" to KY;, so there is nothing to wait on and the send has
// to be spaced by how long the text takes to key — otherwise the second piece
// overruns the 24-character buffer and the rig drops it silently.
func TestYaesuCWDuration(t *testing.T) {
	// PARIS: at 20 wpm a dit is 60 ms and a character averages 10 dits, so 24
	// characters take about 14.4 s.
	got := yaesuCWDuration("ABCDEFGHIJKLMNOPQRSTUVWX", 20)
	if got < 13*time.Second || got > 16*time.Second {
		t.Errorf("24 chars at 20 wpm = %s, expected about 14.4s", got)
	}
	// Twice the speed, half the time.
	fast := yaesuCWDuration("ABCDEFGHIJKLMNOPQRSTUVWX", 40)
	if fast >= got || fast < got/3 {
		t.Errorf("40 wpm = %s vs 20 wpm = %s — should be about half", fast, got)
	}
	// A nonsense speed must not produce a zero wait (send everything at once) nor
	// an absurd one (the operator waiting minutes).
	if d := yaesuCWDuration("TEST", 0); d <= 0 || d > 10*time.Second {
		t.Errorf("4 chars at an unknown speed = %s, want a sane fallback", d)
	}
	if d := yaesuCWDuration("", 20); d != 0 {
		t.Errorf("empty text = %s, want 0", d)
	}
}
