//go:build windows

package winkeyer

// linectl_windows.go — DTR/RTS line control via EscapeCommFunction, the direct
// Win32 method N1MM/WSJT/fldigi use. It sets ONE line at a time without a
// GetCommState/SetCommState round-trip, so a held RTS (PTT) is NOT disturbed when
// DTR (CW) is toggled. go.bug.st/serial drives the lines via SetCommState, which
// on USB-serial adapters (the SCU-17's CP210x, FTDI) drops the held RTS the moment
// DTR changes — the "rig drops to RX between words" bug this fixes.

import (
	"fmt"
	"strings"

	"golang.org/x/sys/windows"
)

type winLineCtl struct {
	h windows.Handle
}

// openLineCtl opens a COM port for line control only (no data I/O).
func openLineCtl(port string) (lineCtl, error) {
	name := strings.TrimSpace(port)
	if name == "" {
		return nil, fmt.Errorf("no serial port")
	}
	// The \\.\ prefix is required for COM10+ and harmless for COM1-9.
	if !strings.HasPrefix(name, `\\.\`) {
		name = `\\.\` + name
	}
	p, err := windows.UTF16PtrFromString(name)
	if err != nil {
		return nil, err
	}
	h, err := windows.CreateFile(p,
		windows.GENERIC_READ|windows.GENERIC_WRITE, 0, nil,
		windows.OPEN_EXISTING, 0, 0)
	if err != nil {
		return nil, fmt.Errorf("open %s: %w", port, err)
	}
	// A minimal DCB so the driver is in a sane state. Baud is irrelevant for pure
	// line control, but leaving fDtr/fRtsControl at DISABLE means only our explicit
	// EscapeCommFunction calls drive the lines.
	var dcb windows.DCB
	if windows.GetCommState(h, &dcb) == nil {
		dcb.BaudRate = 9600
		dcb.ByteSize = 8
		dcb.Parity = 0  // NOPARITY
		dcb.StopBits = 0 // ONESTOPBIT
		// Clear DTR (bits 4-5) and RTS (bits 12-13) control fields → DISABLE, so the
		// lines idle low until we assert them, and no hardware flow control fights us.
		dcb.Flags &^= 0x00000030
		dcb.Flags &^= 0x00003000
		_ = windows.SetCommState(h, &dcb)
	}
	c := &winLineCtl{h: h}
	// Start both lines low (inactive) — the keyer's idle() will do this too.
	_ = c.setDTR(false)
	_ = c.setRTS(false)
	return c, nil
}

func (c *winLineCtl) setDTR(on bool) error {
	f := uint32(windows.CLRDTR)
	if on {
		f = windows.SETDTR
	}
	return windows.EscapeCommFunction(c.h, f)
}

func (c *winLineCtl) setRTS(on bool) error {
	f := uint32(windows.CLRRTS)
	if on {
		f = windows.SETRTS
	}
	return windows.EscapeCommFunction(c.h, f)
}

func (c *winLineCtl) close() error { return windows.CloseHandle(c.h) }
