package kpa

// Decoding the amplifier's answers.
//
// Every format here is quoted from the KPA1500 Programming Reference, with the
// document's own example kept in the test next door. That is the whole
// discipline: a meter decoded from a guess reports a good match on a bad
// antenna, and nobody finds out until something is damaged.

import (
	"fmt"
	"strconv"
	"strings"
)

// payload strips the leading "^", the command letters and the trailing ";",
// leaving the value. Returns false when the answer is not for this command —
// which happens on a shared serial line and on the first read after a
// reconnect, where a stale reply is still in flight.
func payload(reply, cmd string) (string, bool) {
	r := strings.TrimSpace(reply)
	r = strings.TrimSuffix(r, ";")
	r = strings.TrimPrefix(r, "^")
	cmd = strings.TrimSuffix(strings.TrimPrefix(cmd, "^"), ";")
	if !strings.HasPrefix(strings.ToUpper(r), strings.ToUpper(cmd)) {
		return "", false
	}
	return strings.TrimSpace(r[len(cmd):]), true
}

// parseWS reads forward power and SWR from one answer.
//
//	^WS1204 014;  →  1204 W, SWR 1.4
//
// The watts field is FOUR digits on a KPA1500 and THREE on a KPA500 — the
// reference says so where it explains that ^WS exists for KPA500 compatibility
// — so the split is on the space and not on a width. The SWR is in tenths, the
// same units as everywhere else in this protocol.
func parseWS(reply string) (watts int, swr float64, err error) {
	v, ok := payload(reply, "^WS")
	if !ok {
		return 0, 0, fmt.Errorf("not a ^WS answer: %q", reply)
	}
	f := strings.Fields(v)
	if len(f) != 2 {
		return 0, 0, fmt.Errorf("^WS wants two fields, got %q", v)
	}
	w, err1 := strconv.Atoi(f[0])
	s, err2 := strconv.Atoi(f[1])
	if err1 != nil || err2 != nil {
		return 0, 0, fmt.Errorf("^WS not numeric: %q", v)
	}
	return w, float64(s) / 10, nil
}

// parseVI reads the PA supply voltage and current.
//
//	^VI513 061;  →  51.3 V, 61 A
//
// Volts in TENTHS, amps whole. Two different scales in one answer, which is
// exactly the kind of detail that is wrong when it is assumed.
func parseVI(reply string) (volts float64, amps int, err error) {
	v, ok := payload(reply, "^VI")
	if !ok {
		return 0, 0, fmt.Errorf("not a ^VI answer: %q", reply)
	}
	f := strings.Fields(v)
	if len(f) != 2 {
		return 0, 0, fmt.Errorf("^VI wants two fields, got %q", v)
	}
	dv, err1 := strconv.Atoi(f[0])
	a, err2 := strconv.Atoi(f[1])
	if err1 != nil || err2 != nil {
		return 0, 0, fmt.Errorf("^VI not numeric: %q", v)
	}
	return float64(dv) / 10, a, nil
}

// parseInt reads the plain numeric answers: ^TMxxx (°C), ^PCnnn (A),
// ^BNbb (band number), ^OSx, ^ONx, ^TPx.
func parseInt(reply, cmd string) (int, error) {
	v, ok := payload(reply, cmd)
	if !ok {
		return 0, fmt.Errorf("not a %s answer: %q", cmd, reply)
	}
	n, err := strconv.Atoi(strings.TrimSpace(v))
	if err != nil {
		return 0, fmt.Errorf("%s not numeric: %q", cmd, v)
	}
	return n, nil
}

// parseFault reads ^FLhh — TWO HEX DIGITS, not decimal. Fault 90 is reflected
// power and fault 91 is "antenna not connected"; read as decimal they would be
// 144 and 145 and match nothing in the table.
func parseFault(reply string) (int, error) {
	v, ok := payload(reply, "^FL")
	if !ok {
		return 0, fmt.Errorf("not a ^FL answer: %q", reply)
	}
	n, err := strconv.ParseInt(strings.TrimSpace(v), 16, 32)
	if err != nil {
		return 0, fmt.Errorf("^FL not hex: %q", v)
	}
	return int(n), nil
}

// faultNames is the table from the reference, keyed by the hex code.
//
// Said in the operator's terms rather than the amplifier's: "the antenna is not
// connected" is a thing to go and fix, "fault 91" is a thing to go and look up.
var faultNames = map[int]string{
	0x00: "no fault",
	0x10: "watchdog timer reset",
	0x20: "PA current too high",
	0x40: "too hot — clears as it cools",
	0x60: "drive power too high",
	0x61: "gain too low for the drive",
	0x70: "frequency outside a ham band",
	0x80: "50 V supply out of range",
	0x81: "5 V supply out of range",
	0x82: "10 V supply out of range",
	0x83: "12 V supply out of range",
	0x84: "-12 V supply out of range",
	0x85: "no LPF board supply detected",
	0x90: "reflected power too high",
	0x91: "SWR very high — antenna not connected?",
	0x92: "the ATU found no match",
	0xB0: "dissipated power too high",
	0xC0: "forward power too high",
	0xC1: "forward power too high for this ATU setting",
	0xF0: "gain too high for the drive",
}

// FaultName describes a fault code, or says the code itself when the firmware
// reports one this table does not know — a newer amplifier must not be able to
// produce a blank explanation.
func FaultName(code int) string {
	if code == 0 {
		return ""
	}
	if s, ok := faultNames[code]; ok {
		return s
	}
	return fmt.Sprintf("fault %02X", code)
}

// bandNames maps ^BN to the ADIF band. The numbering is the K3/K4 one, which is
// why it is worth writing down: it is not frequency order beyond 6 m and there
// is no arithmetic that produces it.
var bandNames = map[int]string{
	0: "160m", 1: "80m", 2: "60m", 3: "40m", 4: "30m", 5: "20m",
	6: "17m", 7: "15m", 8: "12m", 9: "10m", 10: "6m",
}

// BandName is the ADIF band for a ^BN number, or "" when unknown.
func BandName(n int) string { return bandNames[n] }
