package cat

import "testing"

// The IF status frame, read by POSITION.
//
// One frame carries frequency, TX state, mode, VFO and split, which is why it is
// the poll. Every field is a fixed offset, so a length check comes first: a
// truncated read would otherwise index into the wrong characters and yield a
// plausible-looking wrong frequency — the worst kind, because it reaches the log.
//
// The layout is the one internal/catemu already EMITS to satisfy an ACOM
// amplifier, so these two halves of the repository agree by construction.
func TestParseKenwoodIF(t *testing.T) {
	// IF | freq(11) | step(4) | RIT(±5) | 3 | mem(2) | rx/tx | mode | VFO | scan |
	// split | tone | tone#(2) | shift | ;      → 38 characters
	const rx20mUSB = "IF00014250000" + "0000" + "+00000" + "000" + "00" + "0" + "2" + "0" + "0" + "0" + "0" + "00" + "0" + ";"
	if len(rx20mUSB) != 38 {
		t.Fatalf("the test's own frame is %d chars, not 38 — fix the fixture first", len(rx20mUSB))
	}

	f, ok := parseKenwoodIF(rx20mUSB)
	if !ok {
		t.Fatalf("a valid frame was rejected: %q", rx20mUSB)
	}
	if f.FreqHz != 14250000 {
		t.Errorf("frequency = %d, want 14250000", f.FreqHz)
	}
	if f.Mode != '2' {
		t.Errorf("mode = %q, want '2' (USB)", f.Mode)
	}
	if f.VFO != "A" || f.Split || f.TX {
		t.Errorf("got VFO=%s split=%v tx=%v — want A, simplex, receiving", f.VFO, f.Split, f.TX)
	}

	// Same frame transmitting, on VFO B, split, in CW.
	const txSplitB = "IF00007030000" + "0000" + "+00000" + "000" + "00" + "1" + "3" + "1" + "0" + "1" + "0" + "00" + "0" + ";"
	f2, ok := parseKenwoodIF(txSplitB)
	if !ok {
		t.Fatalf("valid frame rejected: %q", txSplitB)
	}
	if !f2.TX || f2.Mode != '3' || f2.VFO != "B" || !f2.Split {
		t.Errorf("got tx=%v mode=%q vfo=%s split=%v — want transmitting, CW, B, split",
			f2.TX, f2.Mode, f2.VFO, f2.Split)
	}

	// Rejections: anything that would make position-reading unsafe.
	for _, bad := range []string{
		"",
		"IF;",            // query echoed with no payload
		"IF0001425000;",  // short — the trap this guards against
		"FA00014250000;", // another command's reply
		"IF0001425000x0000+00000000000203000000;", // non-numeric frequency
	} {
		if _, ok := parseKenwoodIF(bad); ok {
			t.Errorf("accepted a frame it should have refused: %q", bad)
		}
	}
}

// FA/FB carry ELEVEN digits on Kenwood where Yaesu uses nine — the single most
// likely place to copy the Yaesu backend and be wrong by a factor of a hundred.
func TestParseKenwoodFreq(t *testing.T) {
	cases := []struct {
		reply, prefix string
		want          int64
		ok            bool
	}{
		{"FA00014250000;", "FA", 14250000, true},
		{"FB00007030000;", "FB", 7030000, true},
		{"FA00000474000;", "FA", 474000, true},      // 630 m — leading zeros must not truncate
		{"FA10368000000;", "FA", 10368000000, true}, // 3 cm
		{"FA00014250000;", "FB", 0, false},          // wrong VFO is never silently accepted
		{"FA;", "FA", 0, false},
		{"FAxxxxxxxxxxx;", "FA", 0, false},
		{"", "FA", 0, false},
	}
	for _, c := range cases {
		got, ok := parseKenwoodFreq(c.reply, c.prefix)
		if got != c.want || ok != c.ok {
			t.Errorf("parseKenwoodFreq(%q,%q) = %d,%v — want %d,%v", c.reply, c.prefix, got, ok, c.want, c.ok)
		}
	}
}

// The sideband follows the frequency by worldwide convention. A backend that
// puts USB on 40 m makes every SSB QSO in the log wrong, which is why this is
// pinned rather than left to the rig.
func TestKenwoodModeDigit(t *testing.T) {
	cases := []struct {
		mode string
		hz   int64
		want byte
	}{
		{"SSB", 7150000, '1'},  // LSB below 10 MHz
		{"SSB", 14250000, '2'}, // USB above
		{"LSB", 14250000, '1'}, // an explicit choice wins over the convention
		{"USB", 7150000, '2'},
		{"CW", 7030000, '3'},
		{"RTTY", 14080000, '6'},
		{"AM", 7150000, '5'},
		{"FM", 145000000, '4'},
		{"FT8", 7074000, '2'},  // data is ALWAYS USB, even below 10 MHz (K3 "DATA REV" otherwise)
		{"FT8", 14074000, '2'}, // …and above
		{"", 14074000, 0},      // nothing to set
	}
	for _, c := range cases {
		if got := kenwoodModeDigit(c.mode, c.hz); got != c.want {
			t.Errorf("kenwoodModeDigit(%q, %d) = %q, want %q", c.mode, c.hz, got, c.want)
		}
	}
}

// The DATA-mode preference only applies to soundcard/data modes, never to voice,
// CW or native RTTY — getting this wrong would hijack an SSB or CW mode change.
func TestIsKenwoodDataMode(t *testing.T) {
	data := []string{"FT8", "FT4", "PSK31", "JT65", "DATA", "DIGITAL", "OLIVIA"}
	notData := []string{"", "LSB", "USB", "SSB", "CW", "CW-R", "FM", "AM", "RTTY", "FSK", "RTTY-R"}
	for _, m := range data {
		if !isKenwoodDataMode(m) {
			t.Errorf("isKenwoodDataMode(%q) = false, want true", m)
		}
	}
	for _, m := range notData {
		if isKenwoodDataMode(m) {
			t.Errorf("isKenwoodDataMode(%q) = true, want false", m)
		}
	}
}

// What the log records for each mode digit the rig reports.
func TestKenwoodModeToADIF(t *testing.T) {
	cases := []struct {
		d    byte
		want string
	}{
		{'1', "LSB"}, {'2', "USB"},
		{'3', "CW"}, {'7', "CW"}, // CW-R is still CW in the log
		{'4', "FM"}, {'5', "AM"},
		{'6', "RTTY"}, {'9', "RTTY"}, // FSK-R likewise
		{'0', ""}, // unknown → say nothing rather than guess
	}
	for _, c := range cases {
		if got := kenwoodModeToADIF(c.d, "FT8", false); got != c.want {
			t.Errorf("kenwoodModeToADIF(%q) = %q, want %q", c.d, got, c.want)
		}
	}
}

// On a K3/K4 the same digits mean DATA / DATA-REV, not FSK — and DATA is what
// OpsLog itself sets for FT8. Decoding them as RTTY logged a K3 running FT8 on
// the wrong mode and told the shared-CAT clients the rig was in RTTY.
func TestKenwoodModeToADIFElecraftData(t *testing.T) {
	for _, d := range []byte{'6', '9'} {
		if got := kenwoodModeToADIF(d, "FT8", true); got != "FT8" {
			t.Errorf("kenwoodModeToADIF(%q, elecraft) = %q, want %q", d, got, "FT8")
		}
	}
	// Everything else is unaffected by the Elecraft dialect.
	if got := kenwoodModeToADIF('2', "FT8", true); got != "USB" {
		t.Errorf("kenwoodModeToADIF('2', elecraft) = %q, want USB", got)
	}
}
