package cat

import (
	"fmt"
	"strings"
)

// CW keying over TCI — a sixth CW engine, so a SunSDR needs no WinKeyer and no
// second serial port: the radio's own macro keyer is driven over the WebSocket
// that already carries the CAT.
//
// The commands, from the TCI command table (confirmed against ars-ka0s/eesdr-tci,
// the same source that settled SPOT):
//
//	CW_MACROS:<trx>,<text>;   send text through the radio's keyer
//	CW_MACROS_SPEED:<wpm>;    the speed those macros are keyed at
//	CW_MACROS_STOP;           abort what is being keyed
//	CW_MACROS_EMPTY;          the radio saying the buffer has run dry
//
// CW_MSG (TCI 2.0) does the same with separate before/after callsign fields.
// CW_MACROS is used instead because it exists from 1.6 and OpsLog resolves the
// variables itself — the text handed here is already what should go on the air.
//
// Notably absent: there is no backspace. The FlexRadio CWX keyer can un-type
// what has not been sent yet; TCI can only stop. So the type-ahead correction
// the Flex engine offers is not offered here rather than faked.

// tciCWTextLimit caps one macro. A runaway paste down a WebSocket that also
// carries audio is worth refusing, and no real CW message is this long.
const tciCWTextLimit = 512

// SendCW keys a message through the radio's macro keyer.
func (t *TCI) SendCW(text string) error {
	msg := sanitiseTCICW(text)
	if msg == "" {
		return nil
	}
	return t.send(fmt.Sprintf("cw_macros:0,%s;", msg))
}

// StopCW aborts the message being keyed.
func (t *TCI) StopCW() error { return t.send("cw_macros_stop;") }

// SetCWSpeed sets the macro keyer speed in words per minute.
//
// Only the MACRO speed: the paddle keyer has its own (CW_KEYER_SPEED) and an
// operator who has set their paddle to 28 wpm did not ask the logger to change
// it because a macro went out at 25.
func (t *TCI) SetCWSpeed(wpm int) error {
	if wpm < 5 {
		wpm = 5
	}
	if wpm > 60 {
		wpm = 60
	}
	return t.send(fmt.Sprintf("cw_macros_speed:%d;", wpm))
}

// sanitiseTCICW makes a message safe to put in a TCI command.
//
// Commas and semicolons are the protocol's own separators — a comma inside the
// text would be read as another argument and a semicolon would end the command
// early, keying half a message and leaving the rest to be parsed as a command of
// its own. Neither belongs in Morse anyway.
func sanitiseTCICW(text string) string {
	s := strings.ToUpper(strings.TrimSpace(text))
	s = strings.NewReplacer(",", " ", ";", " ", "\r", " ", "\n", " ").Replace(s)
	if len(s) > tciCWTextLimit {
		s = s[:tciCWTextLimit]
	}
	return strings.TrimSpace(s)
}
