package card_reader

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

type Reader interface {
	io.Closer
	// SetSlave 设置读卡器的 UnitID
	SetSlave(byte)
	// WriteUnitId 写入读卡器的 UnitID
	WriteUnitId(uint16) error
	// Init 初始化读卡器配置
	Init() error
	// OnCardInfo 会在卡号信息发生变化时调用回调函数，直到上下文被取消，或者 Reader 被关闭。需要在 goroutine 中调用。
	OnCardInfo(context.Context, func(*CardInfo))
	GetCardInfo() *CardInfo
}

type CardInfo struct {
	Type []byte
	ID   string
}

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

// WriteUnitId 写入读卡器的 UnitID
func (r *reader) WriteUnitId(id uint16) error {
	v := id<<8 + 1
	zap.S().Infof("WriteRegister 40007 %#x", v)
	_, err := r.c.WriteSingleRegister(6, v)
	return err
}

// Init 初始化读卡器配置
func (r *reader) Init() error {
	// 配置读卡间隔时间为 50 毫秒
	_, err := r.c.WriteSingleRegister(8, 0x0511)
	if err != nil {
		return err
	}
	// 由低到高输出 500 毫秒
	_, err = r.c.WriteSingleRegister(9, 0xF20A)
	if err != nil {
		return err
	}
	// 读取后自动清空，高字节：00 不清空 01 清空
	_, err = r.c.WriteSingleRegister(10, 0x0103)
	if err != nil {
		return err
	}
	// 自动读卡号，高字节高4位：0 读一次 1 连续读
	_, err = r.c.WriteSingleRegister(11, 0x0301)
	if err != nil {
		return err
	}
	return nil
}

// GetCardInfo 获取卡号信息
func (r *reader) GetCardInfo() *CardInfo {
	// 读取状态寄存器确认是否信息已经准备好
	status, err := r.c.ReadHoldingRegisters(29, 1)
	if err != nil {
		zap.S().Errorln("ReadRegister 40030 error:", err)
		return nil
	} else if len(status) != 2 || status[1] == 0 {
		return nil
	}

	// 读取卡号长度
	cardLength, err := r.c.ReadHoldingRegisters(30, 1)
	if err != nil {
		zap.S().Errorln("ReadRegister 40031 error:", err)
		return nil
	}
	dataLength := cardLength[0]

	// 读取卡类型
	cardType, err := r.c.ReadHoldingRegisters(31, 1)
	if err != nil {
		zap.S().Errorln("ReadRegister 40032 error:", err)
		return nil
	}

	// 读取卡号数据
	cardData, err := r.c.ReadHoldingRegisters(32, uint16(math.Round(float64(dataLength)/2)))
	if err != nil {
		zap.S().Errorln("ReadRegister 40033~ error:", err)
		return nil
	}

	s := make([]string, dataLength)
	for i := range s {
		s[i] = fmt.Sprintf("%02X", cardData[i])
	}

	return &CardInfo{
		Type: cardType,
		ID:   strings.Join(s, ""),
	}
}

// OnCardInfo 会在卡号信息发生变化时调用回调函数，直到上下文被取消，或者 Reader 被关闭。需要在 goroutine 中调用。
func (r *reader) OnCardInfo(ctx context.Context, f func(info *CardInfo)) {
	for {
		select {
		case <-ctx.Done():
			return
		case <-time.After(100 * time.Millisecond):
			info := r.GetCardInfo()
			if info != nil {
				f(info)
			}
		}
	}
}

func (r *reader) SetSlave(unitID byte) {
	r.h.SetSlave(unitID)
}

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

// NewReader 创建一个新的读卡器
func NewReader(address string) (Reader, error) {
	// 配置串口客户端
	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
	}
	// 创建 modbus 客户端
	c := modbus.NewClient(h)

	return &reader{h, c}, nil
}
