package civ

import (
	"bytes"
	"testing"
)

func TestFreqBCDRoundTrip(t *testing.T) {
	cases := []int64{0, 1, 7074000, 14250000, 28074000, 50313000, 144174000, 1296000000}
	for _, hz := range cases {
		b := FreqToBCD(hz)
		if len(b) != 5 {
			t.Fatalf("FreqToBCD(%d) len=%d, want 5", hz, len(b))
		}
		if got := BCDToFreq(b); got != hz {
			t.Errorf("round trip %d → % X → %d", hz, b, got)
		}
	}
}

func TestFreqBCDKnownEncoding(t *testing.T) {
	// 14.250.000 Hz → little-endian BCD 00 00 25 14 00.
	want := []byte{0x00, 0x00, 0x25, 0x14, 0x00}
	if got := FreqToBCD(14250000); !bytes.Equal(got, want) {
		t.Errorf("FreqToBCD(14250000) = % X, want % X", got, want)
	}
}

func TestFrame(t *testing.T) {
	// Read-frequency request to a 7610 (0x98) from the controller (0xE0).
	got := Frame(0x98, AddrController, CmdReadFreq)
	want := []byte{0xFE, 0xFE, 0x98, 0xE0, 0x03, 0xFD}
	if !bytes.Equal(got, want) {
		t.Errorf("Frame = % X, want % X", got, want)
	}
}

func TestScanSingleFreqResponse(t *testing.T) {
	// Rig (0x98) → controller (0xE0): freq read response for 14.250 MHz.
	in := Frame(AddrController, 0x98, CmdReadFreq, 0x00, 0x00, 0x25, 0x14, 0x00)
	frames, consumed := Scan(in)
	if consumed != len(in) {
		t.Fatalf("consumed=%d, want %d", consumed, len(in))
	}
	if len(frames) != 1 {
		t.Fatalf("got %d frames, want 1", len(frames))
	}
	f := frames[0]
	if f.From != 0x98 || f.To != AddrController || f.Cmd != CmdReadFreq {
		t.Errorf("addrs/cmd wrong: %+v", f)
	}
	if hz := BCDToFreq(f.Data); hz != 14250000 {
		t.Errorf("decoded freq %d, want 14250000", hz)
	}
}

func TestScanSkipsEchoAndKeepsPartial(t *testing.T) {
	echo := Frame(0x98, AddrController, CmdReadFreq)               // our outgoing (echoed back)
	resp := Frame(AddrController, 0x98, CmdReadMode, ModeCW, 0x01) // a real response
	buf := append(append([]byte{}, echo...), resp...)
	buf = append(buf, 0xFE, 0xFE, 0x98) // a partial third frame (no FD yet)

	frames, consumed := Scan(buf)
	if len(frames) != 2 {
		t.Fatalf("got %d frames, want 2", len(frames))
	}
	// The partial frame must be left unconsumed so the next read can finish it.
	if consumed != len(echo)+len(resp) {
		t.Errorf("consumed=%d, want %d (partial frame retained)", consumed, len(echo)+len(resp))
	}
	if frames[1].Cmd != CmdReadMode || len(frames[1].Data) < 1 || frames[1].Data[0] != ModeCW {
		t.Errorf("second frame wrong: %+v", frames[1])
	}
}

func TestModeToADIF(t *testing.T) {
	cases := []struct {
		m    byte
		data bool
		want string
	}{
		{ModeUSB, false, "SSB"},
		{ModeLSB, false, "SSB"},
		{ModeUSB, true, "DATA"},
		{ModeCW, false, "CW"},
		{ModeCWR, false, "CW"},
		{ModeRTTY, false, "RTTY"},
		{ModeAM, false, "AM"},
		{ModeFM, false, "FM"},
	}
	for _, c := range cases {
		if got := ModeToADIF(c.m, c.data); got != c.want {
			t.Errorf("ModeToADIF(0x%02X, %v) = %q, want %q", c.m, c.data, got, c.want)
		}
	}
}

func TestLevelBCDRoundTrip(t *testing.T) {
	for _, v := range []int{0, 1, 50, 99, 100, 128, 200, 255} {
		b := LevelToBCD(v)
		if len(b) != 2 {
			t.Fatalf("LevelToBCD(%d) len=%d", v, len(b))
		}
		if got := BCDToLevel(b); got != v {
			t.Errorf("level round trip %d → % X → %d", v, b, got)
		}
	}
	// Known encodings from the Icom CI-V reference.
	if got := LevelToBCD(128); !bytes.Equal(got, []byte{0x01, 0x28}) {
		t.Errorf("LevelToBCD(128) = % X, want 01 28", got)
	}
	if got := LevelToBCD(255); !bytes.Equal(got, []byte{0x02, 0x55}) {
		t.Errorf("LevelToBCD(255) = % X, want 02 55", got)
	}
}

func TestByteBCDRoundTrip(t *testing.T) {
	for _, v := range []int{0, 6, 12, 18, 21} {
		if got := BCDToByte(ByteToBCD(v)); got != v {
			t.Errorf("byte BCD round trip %d → %d", v, got)
		}
	}
}

func TestModelName(t *testing.T) {
	if got := ModelName(0x98); got != "IC-7610" {
		t.Errorf("ModelName(0x98) = %q, want IC-7610", got)
	}
	if got := ModelName(0x12); got != "Icom (0x12)" {
		t.Errorf("ModelName(0x12) = %q, want fallback", got)
	}
}

// CI-V addresses are hardware constants: a wrong one means the console shows the
// wrong model, and with it the wrong attenuator steps (6/12/18 dB on the big
// rigs, a single 20 dB on the small ones) — buttons the rig then NAKs.
//
// Two entries here were previously wrong in a way that pointed at each other:
// 0x80 was labelled IC-7800 (it is the IC-7410) and 0x88 IC-7700 (it is the
// IC-7100), while the real IC-7800 (0x6A) and IC-7700 (0x74) were missing — so an
// IC-7800 came up as "Icom (0x6A)" with a 20 dB attenuator it does not have.
func TestModelNameAddresses(t *testing.T) {
	for addr, want := range map[byte]string{
		0x6A: "IC-7800",
		0x74: "IC-7700",
		0x7A: "IC-7600",
		0x7C: "IC-9100",
		0x80: "IC-7410",
		0x88: "IC-7100",
		0x8E: "IC-7851",
		0x94: "IC-7300",
		0x98: "IC-7610",
		0xA2: "IC-9700",
		0xA4: "IC-705",
	} {
		if got := ModelName(addr); got != want {
			t.Errorf("ModelName(0x%02X) = %q, want %q", addr, got, want)
		}
	}
	// An unknown address must stay identifiable rather than masquerade as a model.
	if got := ModelName(0x42); got != "Icom (0x42)" {
		t.Errorf("ModelName(0x42) = %q, want the hex fallback", got)
	}
}
