package relay

import (
	"fmt"
	"os"

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

// CH341 serial relay protocol (LCUS-type modules on /dev/ttyUSBx).
//
// Command format:
//   ON:  0xA0 <channel> 0x01 <checksum>
//   OFF: 0xA0 <channel> 0x00 <checksum>
//   checksum = (0xA0 + channel + state) & 0xFF

type Relay struct {
	devicePath string
	channel    byte
	baud       int
}

func New(devicePath string, channel, baud int) *Relay {
	return &Relay{
		devicePath: devicePath,
		channel:    byte(channel),
		baud:       baud,
	}
}

func (r *Relay) Set(on bool) error {
	f, err := os.OpenFile(r.devicePath, os.O_RDWR|unix.O_NOCTTY, 0)
	if err != nil {
		return fmt.Errorf("relay open %s: %w", r.devicePath, err)
	}
	defer f.Close()

	fd := int(f.Fd())
	if err := configureSerial(fd, r.baud); err != nil {
		return fmt.Errorf("relay serial config: %w", err)
	}

	var state byte
	if on {
		state = 0x01
	}

	checksum := (0xA0 + r.channel + state) & 0xFF
	cmd := []byte{0xA0, r.channel, state, checksum}

	if _, err := f.Write(cmd); err != nil {
		return fmt.Errorf("relay write: %w", err)
	}

	return nil
}

func (r *Relay) Close() error {
	return nil
}

func configureSerial(fd, baud int) error {
	baudRate, ok := baudRates[baud]
	if !ok {
		return fmt.Errorf("unsupported baud rate: %d", baud)
	}

	var t unix.Termios
	t.Cflag = unix.CS8 | unix.CLOCAL | unix.CREAD | baudRate
	t.Cc[unix.VMIN] = 1
	t.Cc[unix.VTIME] = 0

	return unix.IoctlSetTermios(fd, unix.TCSETS, &t)
}

var baudRates = map[int]uint32{
	9600:   unix.B9600,
	19200:  unix.B19200,
	38400:  unix.B38400,
	57600:  unix.B57600,
	115200: unix.B115200,
}
