package att

import (
	"encoding/binary"
	"fmt"
	"time"

	"github.com/JuulLabs-OSS/ble"
	"github.com/pkg/errors"
)

// NotificationHandler handles notification or indication.
type NotificationHandler interface {
	HandleNotification(req []byte)
}

// Client implementa an Attribute Protocol Client.
type Client struct {
	l2c  ble.Conn
	rspc chan []byte

	rxBuf   []byte
	chTxBuf chan []byte
	chErr   chan error
	handler NotificationHandler
}

// NewClient returns an Attribute Protocol Client.
func NewClient(l2c ble.Conn, h NotificationHandler) *Client {
	c := &Client{
		l2c:     l2c,
		rspc:    make(chan []byte),
		chTxBuf: make(chan []byte, 1),
		rxBuf:   make([]byte, ble.MaxMTU),
		chErr:   make(chan error, 1),
		handler: h,
	}
	c.chTxBuf <- make([]byte, l2c.TxMTU(), l2c.TxMTU())
	return c
}

// ExchangeMTU informs the server of the client’s maximum receive MTU size and
// request the server to respond with its maximum receive MTU size. [Vol 3, Part F, 3.4.2.1]
func (c *Client) ExchangeMTU(clientRxMTU int) (serverRxMTU int, err error) {
	if clientRxMTU < ble.DefaultMTU || clientRxMTU > ble.MaxMTU {
		return 0, ErrInvalidArgument
	}

	// Acquire and reuse the txBuf, and release it after usage.
	// The same txBuf, or a newly allocate one, if the txMTU is changed,
	// will be released back to the channel.
	txBuf := <-c.chTxBuf
	defer func() { c.chTxBuf <- txBuf }()

	// Let L2CAP know the MTU we can handle.
	c.l2c.SetRxMTU(clientRxMTU)

	req := ExchangeMTURequest(txBuf[:3])
	req.SetAttributeOpcode()
	req.SetClientRxMTU(uint16(clientRxMTU))

	b, err := c.sendReq(req)
	if err != nil {
		return 0, err
	}

	// Convert and validate the response.
	rsp := ExchangeMTUResponse(b)
	switch {
	case rsp[0] == ErrorResponseCode && len(rsp) == 5:
		return 0, ble.ATTError(rsp[4])
	case rsp[0] == ErrorResponseCode && len(rsp) != 5:
		fallthrough
	case rsp[0] != rsp.AttributeOpcode():
		fallthrough
	case len(rsp) != 3:
		return 0, ErrInvalidResponse
	}

	txMTU := int(rsp.ServerRxMTU())
	if len(txBuf) != txMTU {
		// Let L2CAP know the MTU that the remote device can handle.
		c.l2c.SetTxMTU(txMTU)
		// Put a re-allocated txBuf back to the channel.
		// The txBuf has been captured in deferred function.
		txBuf = make([]byte, txMTU, txMTU)
	}

	return txMTU, nil
}

// FindInformation obtains the mapping of attribute handles with their associated types.
// This allows a Client to discover the list of attributes and their types on a server.
// [Vol 3, Part F, 3.4.3.1 & 3.4.3.2]
func (c *Client) FindInformation(starth, endh uint16) (fmt int, data []byte, err error) {
	if starth == 0 || starth > endh {
		return 0x00, nil, ErrInvalidArgument
	}

	// Acquire and reuse the txBuf, and release it after usage.
	txBuf := <-c.chTxBuf
	defer func() { c.chTxBuf <- txBuf }()

	req := FindInformationRequest(txBuf[:5])
	req.SetAttributeOpcode()
	req.SetStartingHandle(starth)
	req.SetEndingHandle(endh)

	b, err := c.sendReq(req)
	if err != nil {
		return 0x00, nil, err
	}

	// Convert and validate the response.
	rsp := FindInformationResponse(b)
	switch {
	case rsp[0] == ErrorResponseCode && len(rsp) == 5:
		return 0x00, nil, ble.ATTError(rsp[4])
	case rsp[0] == ErrorResponseCode && len(rsp) != 5:
		fallthrough
	case rsp[0] != rsp.AttributeOpcode():
		fallthrough
	case len(rsp) < 6:
		fallthrough
	case rsp.Format() == 0x01 && ((len(rsp)-2)%4) != 0:
		fallthrough
	case rsp.Format() == 0x02 && ((len(rsp)-2)%18) != 0:
		return 0x00, nil, ErrInvalidResponse
	}
	return int(rsp.Format()), rsp.InformationData(), nil
}

// // HandleInformationList ...
// type HandleInformationList []byte
//
// // FoundAttributeHandle ...
// func (l HandleInformationList) FoundAttributeHandle() []byte { return l[:2] }
//
// // GroupEndHandle ...
// func (l HandleInformationList) GroupEndHandle() []byte { return l[2:4] }
//
// // FindByTypeValue ...
// func (c *Client) FindByTypeValue(starth, endh, attrType uint16, value []byte) ([]HandleInformationList, error) {
// 	return nil, nil
// }

// ReadByType obtains the values of attributes where the attribute type is known
// but the handle is not known. [Vol 3, Part F, 3.4.4.1 & 3.4.4.2]
func (c *Client) ReadByType(starth, endh uint16, uuid ble.UUID) (int, []byte, error) {
	if starth > endh || (len(uuid) != 2 && len(uuid) != 16) {
		return 0, nil, ErrInvalidArgument
	}

	// Acquire and reuse the txBuf, and release it after usage.
	txBuf := <-c.chTxBuf
	defer func() { c.chTxBuf <- txBuf }()

	req := ReadByTypeRequest(txBuf[:5+len(uuid)])
	req.SetAttributeOpcode()
	req.SetStartingHandle(starth)
	req.SetEndingHandle(endh)
	req.SetAttributeType(uuid)

	b, err := c.sendReq(req)
	if err != nil {
		return 0, nil, err
	}

	// Convert and validate the response.
	rsp := ReadByTypeResponse(b)
	switch {
	case rsp[0] == ErrorResponseCode && len(rsp) == 5:
		return 0, nil, ble.ATTError(rsp[4])
	case rsp[0] == ErrorResponseCode && len(rsp) != 5:
		fallthrough
	case rsp[0] != rsp.AttributeOpcode():
		fallthrough
	case len(rsp) < 4 || len(rsp.AttributeDataList())%int(rsp.Length()) != 0:
		return 0, nil, ErrInvalidResponse
	}
	return int(rsp.Length()), rsp.AttributeDataList(), nil
}

// Read requests the server to read the value of an attribute and return its
// value in a Read Response. [Vol 3, Part F, 3.4.4.3 & 3.4.4.4]
func (c *Client) Read(handle uint16) ([]byte, error) {

	// Acquire and reuse the txBuf, and release it after usage.
	txBuf := <-c.chTxBuf
	defer func() { c.chTxBuf <- txBuf }()

	req := ReadRequest(txBuf[:3])
	req.SetAttributeOpcode()
	req.SetAttributeHandle(handle)

	b, err := c.sendReq(req)
	if err != nil {
		return nil, err
	}

	// Convert and validate the response.
	rsp := ReadResponse(b)
	switch {
	case rsp[0] == ErrorResponseCode && len(rsp) == 5:
		return nil, ble.ATTError(rsp[4])
	case rsp[0] == ErrorResponseCode && len(rsp) != 5:
		fallthrough
	case rsp[0] != rsp.AttributeOpcode():
		fallthrough
	case len(rsp) < 1:
		return nil, ErrInvalidResponse
	}
	return rsp.AttributeValue(), nil
}

// ReadBlob requests the server to read part of the value of an attribute at a
// given offset and return a specific part of the value in a Read Blob Response.
// [Vol 3, Part F, 3.4.4.5 & 3.4.4.6]
func (c *Client) ReadBlob(handle, offset uint16) ([]byte, error) {

	// Acquire and reuse the txBuf, and release it after usage.
	txBuf := <-c.chTxBuf
	defer func() { c.chTxBuf <- txBuf }()

	req := ReadBlobRequest(txBuf[:5])
	req.SetAttributeOpcode()
	req.SetAttributeHandle(handle)
	req.SetValueOffset(offset)

	b, err := c.sendReq(req)
	if err != nil {
		return nil, err
	}

	// Convert and validate the response.
	rsp := ReadBlobResponse(b)
	switch {
	case rsp[0] == ErrorResponseCode && len(rsp) == 5:
		return nil, ble.ATTError(rsp[4])
	case rsp[0] == ErrorResponseCode && len(rsp) != 5:
		fallthrough
	case rsp[0] != rsp.AttributeOpcode():
		fallthrough
	case len(rsp) < 1:
		return nil, ErrInvalidResponse
	}
	return rsp.PartAttributeValue(), nil
}

// ReadMultiple requests the server to read two or more values of a set of
// attributes and return their values in a Read Multiple Response.
// Only values that have a known fixed size can be read, with the exception of
// the last value that can have a variable length. The knowledge of whether
// attributes have a known fixed size is defined in a higher layer specification.
// [Vol 3, Part F, 3.4.4.7 & 3.4.4.8]
func (c *Client) ReadMultiple(handles []uint16) ([]byte, error) {
	// Should request to read two or more values.
	if len(handles) < 2 || len(handles)*2 > c.l2c.TxMTU()-1 {
		return nil, ErrInvalidArgument
	}

	// Acquire and reuse the txBuf, and release it after usage.
	txBuf := <-c.chTxBuf
	defer func() { c.chTxBuf <- txBuf }()

	req := ReadMultipleRequest(txBuf[:1+len(handles)*2])
	req.SetAttributeOpcode()
	p := req.SetOfHandles()
	for _, h := range handles {
		binary.LittleEndian.PutUint16(p, h)
		p = p[2:]
	}

	b, err := c.sendReq(req)
	if err != nil {
		return nil, err
	}

	// Convert and validate the response.
	rsp := ReadMultipleResponse(b)
	switch {
	case rsp[0] == ErrorResponseCode && len(rsp) == 5:
		return nil, ble.ATTError(rsp[4])
	case rsp[0] == ErrorResponseCode && len(rsp) != 5:
		fallthrough
	case rsp[0] != rsp.AttributeOpcode():
		fallthrough
	case len(rsp) < 1:
		return nil, ErrInvalidResponse
	}
	return rsp.SetOfValues(), nil
}

// ReadByGroupType obtains the values of attributes where the attribute type is known,
// the type of a grouping attribute as defined by a higher layer specification, but
// the handle is not known. [Vol 3, Part F, 3.4.4.9 & 3.4.4.10]
func (c *Client) ReadByGroupType(starth, endh uint16, uuid ble.UUID) (int, []byte, error) {
	if starth > endh || (len(uuid) != 2 && len(uuid) != 16) {
		return 0, nil, ErrInvalidArgument
	}

	// Acquire and reuse the txBuf, and release it after usage.
	txBuf := <-c.chTxBuf
	defer func() { c.chTxBuf <- txBuf }()

	req := ReadByGroupTypeRequest(txBuf[:5+len(uuid)])
	req.SetAttributeOpcode()
	req.SetStartingHandle(starth)
	req.SetEndingHandle(endh)
	req.SetAttributeGroupType(uuid)

	b, err := c.sendReq(req)
	if err != nil {
		return 0, nil, err
	}

	// Convert and validate the response.
	rsp := ReadByGroupTypeResponse(b)
	switch {
	case rsp[0] == ErrorResponseCode && len(rsp) == 5:
		return 0, nil, ble.ATTError(rsp[4])
	case rsp[0] == ErrorResponseCode && len(rsp) != 5:
		fallthrough
	case rsp[0] != rsp.AttributeOpcode():
		fallthrough
	case len(rsp) < 4:
		fallthrough
	case len(rsp.AttributeDataList())%int(rsp.Length()) != 0:
		return 0, nil, ErrInvalidResponse
	}

	return int(rsp.Length()), rsp.AttributeDataList(), nil
}

// Write requests the server to write the value of an attribute and acknowledge that
// this has been achieved in a Write Response. [Vol 3, Part F, 3.4.5.1 & 3.4.5.2]
func (c *Client) Write(handle uint16, value []byte) error {
	if len(value) > c.l2c.TxMTU()-3 {
		return ErrInvalidArgument
	}

	// Acquire and reuse the txBuf, and release it after usage.
	txBuf := <-c.chTxBuf
	defer func() { c.chTxBuf <- txBuf }()

	req := WriteRequest(txBuf[:3+len(value)])
	req.SetAttributeOpcode()
	req.SetAttributeHandle(handle)
	req.SetAttributeValue(value)

	b, err := c.sendReq(req)
	if err != nil {
		return err
	}

	// Convert and validate the response.
	rsp := WriteResponse(b)
	switch {
	case rsp[0] == ErrorResponseCode && len(rsp) == 5:
		return ble.ATTError(rsp[4])
	case rsp[0] == ErrorResponseCode && len(rsp) != 5:
		fallthrough
	case rsp[0] != rsp.AttributeOpcode():
		return ErrInvalidResponse
	}
	return nil
}

// WriteCommand requests the server to write the value of an attribute, typically
// into a control-point attribute. [Vol 3, Part F, 3.4.5.3]
func (c *Client) WriteCommand(handle uint16, value []byte) error {
	if len(value) > c.l2c.TxMTU()-3 {
		return ErrInvalidArgument
	}

	// Acquire and reuse the txBuf, and release it after usage.
	txBuf := <-c.chTxBuf
	defer func() { c.chTxBuf <- txBuf }()

	req := WriteCommand(txBuf[:3+len(value)])
	req.SetAttributeOpcode()
	req.SetAttributeHandle(handle)
	req.SetAttributeValue(value)

	return c.sendCmd(req)
}

// SignedWrite requests the server to write the value of an attribute with an authentication
// signature, typically into a control-point attribute. [Vol 3, Part F, 3.4.5.4]
func (c *Client) SignedWrite(handle uint16, value []byte, signature [12]byte) error {
	if len(value) > c.l2c.TxMTU()-15 {
		return ErrInvalidArgument
	}

	// Acquire and reuse the txBuf, and release it after usage.
	txBuf := <-c.chTxBuf
	defer func() { c.chTxBuf <- txBuf }()

	req := SignedWriteCommand(txBuf[:15+len(value)])
	req.SetAttributeOpcode()
	req.SetAttributeHandle(handle)
	req.SetAttributeValue(value)
	req.SetAuthenticationSignature(signature)

	return c.sendCmd(req)
}

// PrepareWrite requests the server to prepare to write the value of an attribute.
// The server will respond to this request with a Prepare Write Response, so that
// the Client can verify that the value was received correctly.
// [Vol 3, Part F, 3.4.6.1 & 3.4.6.2]
func (c *Client) PrepareWrite(handle uint16, offset uint16, value []byte) (uint16, uint16, []byte, error) {
	if len(value) > c.l2c.TxMTU()-5 {
		return 0, 0, nil, ErrInvalidArgument
	}

	// Acquire and reuse the txBuf, and release it after usage.
	txBuf := <-c.chTxBuf
	defer func() { c.chTxBuf <- txBuf }()

	req := PrepareWriteRequest(txBuf[:5+len(value)])
	req.SetAttributeOpcode()
	req.SetAttributeHandle(handle)
	req.SetValueOffset(offset)

	b, err := c.sendReq(req)
	if err != nil {
		return 0, 0, nil, err
	}

	// Convert and validate the response.
	rsp := PrepareWriteResponse(b)
	switch {
	case rsp[0] == ErrorResponseCode && len(rsp) == 5:
		return 0, 0, nil, ble.ATTError(rsp[4])
	case rsp[0] == ErrorResponseCode && len(rsp) != 5:
		fallthrough
	case rsp[0] != rsp.AttributeOpcode():
		fallthrough
	case len(rsp) < 5:
		return 0, 0, nil, ErrInvalidResponse
	}
	return rsp.AttributeHandle(), rsp.ValueOffset(), rsp.PartAttributeValue(), nil
}

// ExecuteWrite requests the server to write or cancel the write of all the prepared
// values currently held in the prepare queue from this Client. This request shall be
// handled by the server as an atomic operation. [Vol 3, Part F, 3.4.6.3 & 3.4.6.4]
func (c *Client) ExecuteWrite(flags uint8) error {

	// Acquire and reuse the txBuf, and release it after usage.
	txBuf := <-c.chTxBuf
	defer func() { c.chTxBuf <- txBuf }()

	req := ExecuteWriteRequest(txBuf[:1])
	req.SetAttributeOpcode()
	req.SetFlags(flags)

	b, err := c.sendReq(req)
	if err != nil {
		return err
	}

	// Convert and validate the response.
	rsp := ExecuteWriteResponse(b)
	switch {
	case rsp[0] == ErrorResponseCode && len(rsp) == 5:
		return ble.ATTError(rsp[4])
	case rsp[0] == ErrorResponseCode && len(rsp) == 5:
		fallthrough
	case rsp[0] != rsp.AttributeOpcode():
		return ErrInvalidResponse
	}
	return nil
}

func (c *Client) sendCmd(b []byte) error {
	_, err := c.l2c.Write(b)
	return err
}

func (c *Client) sendReq(b []byte) (rsp []byte, err error) {
	logger.Debug("client", "req", fmt.Sprintf("% X", b))
	if _, err := c.l2c.Write(b); err != nil {
		return nil, errors.Wrap(err, "send ATT request failed")
	}
	for {
		select {
		case rsp := <-c.rspc:
			if rsp[0] == ErrorResponseCode || rsp[0] == rspOfReq[b[0]] {
				return rsp, nil
			}
			// Sometimes when we connect to an Apple device, it sends
			// ATT requests asynchronously to us. // In this case, we
			// returns an ErrReqNotSupp response, and continue to wait
			// the response to our request.
			errRsp := newErrorResponse(rsp[0], 0x0000, ble.ErrReqNotSupp)
			logger.Debug("client", "req", fmt.Sprintf("% X", b))
			_, err := c.l2c.Write(errRsp)
			if err != nil {
				return nil, errors.Wrap(err, "unexpected ATT response received")
			}
		case err := <-c.chErr:
			return nil, errors.Wrap(err, "ATT request failed")
		case <-time.After(30 * time.Second):
			return nil, errors.Wrap(ErrSeqProtoTimeout, "ATT request timeout")
		}
	}
}

// Loop ...
func (c *Client) Loop() {

	type asyncWork struct {
		handle func([]byte)
		data   []byte
	}

	ch := make(chan asyncWork, 16)
	defer close(ch)
	go func() {
		for w := range ch {
			w.handle(w.data)
		}
	}()

	confirmation := []byte{HandleValueConfirmationCode}
	for {
		n, err := c.l2c.Read(c.rxBuf)
		logger.Debug("client", "rsp", fmt.Sprintf("% X", c.rxBuf[:n]))
		if err != nil {
			// We don't expect any error from the bearer (L2CAP ACL-U)
			// Pass it along to the pending request, if any, and escape.
			c.chErr <- err
			return
		}

		b := make([]byte, n)
		copy(b, c.rxBuf)

		if (b[0] != HandleValueNotificationCode) && (b[0] != HandleValueIndicationCode) {
			c.rspc <- b
			continue
		}

		// Deliver the full request to upper layer.
		select {
		case ch <- asyncWork{handle: c.handler.HandleNotification, data: b}:
		default:
			// If this really happens, especially on a slow machine, enlarge the channel buffer.
			_ = logger.Error("client", "req", "can't enqueue incoming notification.")
		}

		// Always write aknowledgement for an indication, even it was an invalid request.
		if b[0] == HandleValueIndicationCode {
			logger.Debug("client", "req", fmt.Sprintf("% X", b))
			_, _ = c.l2c.Write(confirmation)
		}
	}
}
