package modbus

import (
	"errors"
	"fmt"
	"io"

	"git.simanc.com/software/golib/v4/gnet"
)

type SocketReader interface {
	ReadRaw(register, quantity int) ([]byte, error)
	ReadBool(register, quantity, bitIdx int) bool
	ReadSingleBool(register, bitIdx int) bool
	ReadNumber(register, quantity int) int
	ReadSingleNumber(register int) uint16
}

type SocketWriter interface {
	Write(register, quantity uint16, value []uint16) error
	WriteSingle(register, value uint16) error
	WriteBit(register uint16, bitIdx uint) error
}

type Socket interface {
	SocketReader
	SocketWriter
}

var ErrSocketDiscard = errors.New("socket discard")

type socketDiscard struct{}

func (s *socketDiscard) ReadRaw(_, _ int) ([]byte, error)    { return nil, ErrSocketDiscard }
func (s *socketDiscard) ReadBool(_, _, _ int) bool           { return false }
func (s *socketDiscard) ReadSingleBool(_, _ int) bool        { return false }
func (s *socketDiscard) ReadNumber(_, _ int) int             { return 0 }
func (s *socketDiscard) ReadSingleNumber(_ int) uint16       { return 0 }
func (s *socketDiscard) Write(_, _ uint16, _ []uint16) error { return ErrSocketDiscard }
func (s *socketDiscard) WriteSingle(_, _ uint16) error       { return ErrSocketDiscard }
func (s *socketDiscard) WriteBit(_ uint16, _ uint) error     { return ErrSocketDiscard }

var (
	SocketDiscard Socket = &socketDiscard{}
)

type socketReader struct {
	reader io.Reader
}

func (r *socketReader) ReadRaw(register, quantity int) ([]byte, error) {
	return ReadRegisterFrom(r.reader, register, quantity)
}

func (r *socketReader) ReadBool(register, quantity, bitIdx int) bool {
	data, err := ReadRegisterFrom(r.reader, register, quantity)
	if err != nil {
		return false
	}
	bit := gnet.LittleEndian.BitSplit(data)
	v := bit.Is1(bitIdx)
	return v
}

func (r *socketReader) ReadSingleBool(register, bitIdx int) bool {
	data, err := ReadRegisterSingleFrom(r.reader, register)
	if err != nil {
		return false
	}
	bit := gnet.LittleEndian.BitSplit(data)
	v := bit.Is1(bitIdx)
	return v
}

func (r *socketReader) ReadNumber(register, quantity int) int {
	data, err := ReadRegisterFrom(r.reader, register, quantity)
	if err != nil {
		return 0
	}
	p := make([]byte, 8)
	copy(p, data)
	n := gnet.BigEndian.Uint64(p)
	return int(n)
}
func (r *socketReader) ReadSingleNumber(register int) uint16 {
	data, err := ReadRegisterSingleFrom(r.reader, register)
	if err != nil {
		return 0
	}
	n := gnet.BigEndian.Uint16(data)
	return n
}

func NewSocketReader(r io.Reader) SocketReader {
	return &socketReader{reader: r}
}

type socketWriter struct {
	unitID uint8
	conn   Conn
}

func (w *socketWriter) Write(register, quantity uint16, value []uint16) error {
	var pdu PDU
	var err error
	if quantity == 1 {
		pdu = NewPDUWriteSingleRegister(register, value[0])
	} else {
		pdu, err = NewPDUWriteMultipleRegisters(register, quantity, value)
	}
	if err != nil {
		return err
	}
	req := NewADU(register, Protocol, w.unitID, pdu)
	resp, err := w.conn.WriteResponse(req.Serialize())
	if err != nil {
		return err
	}
	ret, err := ParseADU(resp)
	if err != nil {
		return err
	}
	if ret.TransactionID != req.TransactionID {
		return fmt.Errorf("socketWriter: Write: Read: w.TransactionId != r.TransactionId: %d->%d", req.TransactionID, ret.TransactionID)
	}
	if ret.PDU.FunctionCode != req.PDU.FunctionCode {
		return fmt.Errorf("socketWriter: Write: Read: r.FunctionCode != w.FunctionCode: %d->%d", ret.PDU.FunctionCode, req.PDU.FunctionCode)
	}
	return nil
}

func (w *socketWriter) WriteSingle(register, value uint16) error {
	return w.Write(register, 1, []uint16{value})
}

func (w *socketWriter) WriteBit(register uint16, bitIdx uint) error {
	var v uint16
	gnet.SetBit(&v, bitIdx)
	return w.WriteSingle(register, v)
}

func NewSocketWriter(unitID uint8, conn Conn) SocketWriter {
	return &socketWriter{unitID: unitID, conn: conn}
}
