package relay

import (
	"context"
	"errors"
	"github.com/grid-x/modbus"
	"go.uber.org/zap"
	"io"
	"io/fs"
	"os"
	"strings"
	"time"
)

type Relay interface {
	io.Closer
	// SetSlave 设置继电器的 UnitID
	SetSlave(byte)
	// On 打开继电器
	On(int) error
	// Off 关闭继电器
	Off(int) error
	// OnAll 打开所有继电器
	OnAll() error
	// OffAll 关闭所有继电器
	OffAll() error
}

type device struct {
	h modbus.ClientHandler
	c modbus.Client
}

func (r *device) SetSlave(slaveID byte) {
	r.h.SetSlave(slaveID)
}

func (r *device) Close() error {
	return r.h.Close()
}

func (r *device) OnAll() error {
	_, err := r.c.WriteMultipleCoils(uint16(0), 16, []byte{0xFF, 0xFF})
	return err
}

func (r *device) OffAll() error {
	_, err := r.c.WriteMultipleCoils(uint16(0), 16, []byte{0x00, 0x00})
	return err
}

func (r *device) On(num int) error {
	_, err := r.c.WriteSingleCoil(uint16(num), 0xFF00)
	return err
}

func (r *device) Off(num int) error {
	_, err := r.c.WriteSingleCoil(uint16(num), 0x0000)
	return err
}

func New(address string) (Relay, error) {
	zap.S().Infoln("连接继电器: ", address)

	ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
	defer cancel()
	a := make(chan struct{})
	go func() {
		defer close(a)
		for {
			select {
			case <-ctx.Done():
				zap.S().Infoln("等待继电器资源释放超时:", address)
				return
			case <-time.After(3 * time.Second):
				_, err := os.OpenFile(address, os.O_RDWR, 0666)
				var e *fs.PathError
				if errors.As(err, &e) && strings.Contains(e.Error(), "busy") {
					zap.S().Infoln("等待继电器资源释放:", address)
				} else {
					return
				}
			}
		}
	}()
	<-a

	h := modbus.NewRTUClientHandler(address)
	h.SlaveID = 1
	h.BaudRate = 9600
	h.DataBits = 8
	h.Parity = "N"
	h.StopBits = 1

	if err := h.Connect(); err != nil {
		return nil, err
	}
	c := modbus.NewClient(h)

	return &device{h, c}, nil
}
