/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package bledefs

import (
	"bytes"
	"encoding/json"
	"fmt"
	"strconv"
	"strings"
)

const BLE_ATT_ATTR_MAX_LEN = 512

const BLE_ATT_MTU_DFLT = 23

const CccdUuid = 0x2902

const IotivitySvcUuid = "ade3d529-c784-4f63-a987-eb69f70ee816"
const IotivityReqChrUuid = "ad7b334f-4637-4b86-90b6-9d787f03d218"
const IotivityRspChrUuid = "e9241982-4580-42c4-8831-95048216b256"

const UnauthSvcUuid = "0c08c213-98ed-4e43-a499-7e1137c39567"
const UnauthReqChrUuid = "69b8a928-2ab2-487b-923e-54ce53a18bc1"
const UnauthRspChrUuid = "bca10aea-5df1-4248-b72b-f52955ad9c88"

const SecureSvcUuid = 0xfe18
const SecureReqChrUuid = 0x1000
const SecureRspChrUuid = 0x1001

const NmpPlainSvcUuid = "8d53dc1d-1db7-4cd3-868b-8a527460aa84"
const NmpPlainChrUuid = "da2e7828-fbce-4e01-ae9e-261174997c48"

const OmpUnsecSvcUuid = "ade3d529-c784-4f63-a987-eb69f70ee816"
const OmpUnsecReqChrUuid = "ad7b334f-4637-4b86-90b6-9d787f03d218"
const OmpUnsecRspChrUuid = "e9241982-4580-42c4-8831-95048216b256"

const OmpSecSvcUuid = SecureSvcUuid
const OmpSecReqChrUuid = SecureReqChrUuid
const OmpSecRspChrUuid = SecureRspChrUuid

type BleAddrType int

const (
	BLE_ADDR_TYPE_PUBLIC  BleAddrType = 0
	BLE_ADDR_TYPE_RANDOM              = 1
	BLE_ADDR_TYPE_RPA_PUB             = 2
	BLE_ADDR_TYPE_RPA_RND             = 3
)

var BleAddrTypeStringMap = map[BleAddrType]string{
	BLE_ADDR_TYPE_PUBLIC:  "public",
	BLE_ADDR_TYPE_RANDOM:  "random",
	BLE_ADDR_TYPE_RPA_PUB: "rpa_pub",
	BLE_ADDR_TYPE_RPA_RND: "rpa_rnd",
}

func BleAddrTypeToString(addrType BleAddrType) string {
	s := BleAddrTypeStringMap[addrType]
	if s == "" {
		return "???"
	}

	return s
}

func BleAddrTypeFromString(s string) (BleAddrType, error) {
	for addrType, name := range BleAddrTypeStringMap {
		if s == name {
			return addrType, nil
		}
	}

	return BleAddrType(0), fmt.Errorf("Invalid BleAddrType string: %s", s)
}

func (a BleAddrType) MarshalJSON() ([]byte, error) {
	return json.Marshal(BleAddrTypeToString(a))
}

func (a *BleAddrType) UnmarshalJSON(data []byte) error {
	var err error

	var s string
	if err := json.Unmarshal(data, &s); err != nil {
		return err
	}

	*a, err = BleAddrTypeFromString(s)
	return err
}

type BleAddr struct {
	Bytes [6]byte
}

func ParseBleAddr(s string) (BleAddr, error) {
	ba := BleAddr{}

	toks := strings.Split(strings.ToLower(s), ":")
	if len(toks) != 6 {
		return ba, fmt.Errorf("invalid BLE addr string: %s", s)
	}

	for i, t := range toks {
		u64, err := strconv.ParseUint(t, 16, 8)
		if err != nil {
			return ba, err
		}
		ba.Bytes[i] = byte(u64)
	}

	return ba, nil
}

func (ba *BleAddr) String() string {
	var buf bytes.Buffer
	buf.Grow(len(ba.Bytes) * 3)

	for i, b := range ba.Bytes {
		if i != 0 {
			buf.WriteString(":")
		}
		fmt.Fprintf(&buf, "%02x", b)
	}

	return buf.String()
}

func (ba *BleAddr) MarshalJSON() ([]byte, error) {
	return json.Marshal(ba.String())
}

func (ba *BleAddr) UnmarshalJSON(data []byte) error {
	var s string
	if err := json.Unmarshal(data, &s); err != nil {
		return err
	}

	var err error
	*ba, err = ParseBleAddr(s)
	if err != nil {
		return err
	}

	return nil
}

type BleDev struct {
	AddrType BleAddrType
	Addr     BleAddr
}

func (bd *BleDev) String() string {
	return fmt.Sprintf("%s,%s",
		BleAddrTypeToString(bd.AddrType),
		bd.Addr.String())
}

type BleUuid16 uint16

func (bu16 *BleUuid16) String() string {
	return fmt.Sprintf("0x%04x", *bu16)
}

func ParseUuid16(s string) (BleUuid16, error) {
	val, err := strconv.ParseUint(s, 0, 16)
	if err != nil {
		return BleUuid16(0), fmt.Errorf("Invalid UUID: %s", s)
	}

	return BleUuid16(val), nil
}

type BleUuid128 [16]byte

func (bu128 *BleUuid128) String() string {
	var buf bytes.Buffer
	buf.Grow(len(bu128)*2 + 3)

	for i, b := range bu128 {
		switch i {
		case 4, 6, 8, 10:
			buf.WriteString("-")
		}

		fmt.Fprintf(&buf, "%02x", b)
	}

	return buf.String()
}

func ParseUuid128(s string) (BleUuid128, error) {
	var bu128 BleUuid128

	if len(s) != 36 {
		return bu128, fmt.Errorf("Invalid UUID: %s", s)
	}

	boff := 0
	for i := 0; i < 36; {
		switch i {
		case 8, 13, 18, 23:
			if s[i] != '-' {
				return bu128, fmt.Errorf("Invalid UUID: %s", s)
			}
			i++

		default:
			u64, err := strconv.ParseUint(s[i:i+2], 16, 8)
			if err != nil {
				return bu128, fmt.Errorf("Invalid UUID: %s", s)
			}
			bu128[boff] = byte(u64)
			i += 2
			boff++
		}
	}

	return bu128, nil
}

func (bu128 *BleUuid128) MarshalJSON() ([]byte, error) {
	return json.Marshal(bu128.String())
}

func (bu128 *BleUuid128) UnmarshalJSON(data []byte) error {
	var s string
	if err := json.Unmarshal(data, &s); err != nil {
		return err
	}

	var err error
	*bu128, err = ParseUuid128(s)
	if err != nil {
		return err
	}

	return nil
}

type BleUuid struct {
	// Set to 0 if the 128-bit UUID should be used.
	U16 BleUuid16

	U128 BleUuid128
}

func (bu *BleUuid) String() string {
	if bu.U16 != 0 {
		return bu.U16.String()
	} else {
		return bu.U128.String()
	}
}

func ParseUuid(uuidStr string) (BleUuid, error) {
	bu := BleUuid{}
	var err error

	// First, try to parse as a 16-bit UUID.
	bu.U16, err = ParseUuid16(uuidStr)
	if err == nil {
		return bu, nil
	}

	// Try to parse as a 128-bit UUID.
	bu.U128, err = ParseUuid128(uuidStr)
	if err == nil {
		return bu, nil
	}

	return bu, err
}

func NewBleUuid16(uuid16 uint16) BleUuid {
	return BleUuid{BleUuid16(uuid16), BleUuid128{}}
}

func (bu *BleUuid) MarshalJSON() ([]byte, error) {
	if bu.U16 != 0 {
		return json.Marshal(bu.U16)
	} else {
		return json.Marshal(bu.U128.String())
	}
}

func (bu *BleUuid) UnmarshalJSON(data []byte) error {
	var err error

	// If the value is a string, try to parse a UUID from it. */
	var s string
	if err := json.Unmarshal(data, &s); err == nil {
		*bu, err = ParseUuid(s)
		return err
	}

	// Not a string; maybe it's a raw 16-bit number.
	if err = json.Unmarshal(data, &bu.U16); err != nil {
		return err
	}

	return nil
}

func CompareUuids(a BleUuid, b BleUuid) int {
	if a.U16 != 0 || b.U16 != 0 {
		return int(a.U16) - int(b.U16)
	} else {
		return bytes.Compare(a.U128[:], b.U128[:])
	}
}

type BleScanFilterPolicy int

const (
	BLE_SCAN_FILT_NO_WL        BleScanFilterPolicy = 0
	BLE_SCAN_FILT_USE_WL                           = 1
	BLE_SCAN_FILT_NO_WL_INITA                      = 2
	BLE_SCAN_FILT_USE_WL_INITA                     = 3
)

var BleScanFilterPolicyStringMap = map[BleScanFilterPolicy]string{
	BLE_SCAN_FILT_NO_WL:        "no_wl",
	BLE_SCAN_FILT_USE_WL:       "use_wl",
	BLE_SCAN_FILT_NO_WL_INITA:  "no_wl_inita",
	BLE_SCAN_FILT_USE_WL_INITA: "use_wl_inita",
}

func BleScanFilterPolicyToString(filtPolicy BleScanFilterPolicy) string {
	s := BleScanFilterPolicyStringMap[filtPolicy]
	if s == "" {
		return "???"
	}

	return s
}

func BleScanFilterPolicyFromString(s string) (BleScanFilterPolicy, error) {
	for filtPolicy, name := range BleScanFilterPolicyStringMap {
		if s == name {
			return filtPolicy, nil
		}
	}

	return BleScanFilterPolicy(0),
		fmt.Errorf("Invalid BleScanFilterPolicy string: %s", s)
}

func (a BleScanFilterPolicy) MarshalJSON() ([]byte, error) {
	return json.Marshal(BleScanFilterPolicyToString(a))
}

func (a *BleScanFilterPolicy) UnmarshalJSON(data []byte) error {
	var err error

	var s string
	if err := json.Unmarshal(data, &s); err != nil {
		return err
	}

	*a, err = BleScanFilterPolicyFromString(s)
	return err
}

type BleAdvEventType int

const (
	BLE_ADV_EVENT_IND           BleAdvEventType = 0
	BLE_ADV_EVENT_DIRECT_IND_HD                 = 1
	BLE_ADV_EVENT_SCAN_IND                      = 2
	BLE_ADV_EVENT_NONCONN_IND                   = 3
	BLE_ADV_EVENT_DIRECT_IND_LD                 = 4
)

var BleAdvEventTypeStringMap = map[BleAdvEventType]string{
	BLE_ADV_EVENT_IND:           "ind",
	BLE_ADV_EVENT_DIRECT_IND_HD: "direct_ind_hd",
	BLE_ADV_EVENT_SCAN_IND:      "scan_ind",
	BLE_ADV_EVENT_NONCONN_IND:   "nonconn_ind",
	BLE_ADV_EVENT_DIRECT_IND_LD: "direct_ind_ld",
}

func BleAdvEventTypeToString(advEventType BleAdvEventType) string {
	s := BleAdvEventTypeStringMap[advEventType]
	if s == "" {
		return "???"
	}

	return s
}

func BleAdvEventTypeFromString(s string) (BleAdvEventType, error) {
	for advEventType, name := range BleAdvEventTypeStringMap {
		if s == name {
			return advEventType, nil
		}
	}

	return BleAdvEventType(0),
		fmt.Errorf("Invalid BleAdvEventType string: %s", s)
}

func (a BleAdvEventType) MarshalJSON() ([]byte, error) {
	return json.Marshal(BleAdvEventTypeToString(a))
}

func (a *BleAdvEventType) UnmarshalJSON(data []byte) error {
	var err error

	var s string
	if err := json.Unmarshal(data, &s); err != nil {
		return err
	}

	*a, err = BleAdvEventTypeFromString(s)
	return err
}

type BleAdvConnMode int

const (
	BLE_ADV_CONN_MODE_NON BleAdvConnMode = iota
	BLE_ADV_CONN_MODE_DIR
	BLE_ADV_CONN_MODE_UND
)

var BleAdvConnModeStringMap = map[BleAdvConnMode]string{
	BLE_ADV_CONN_MODE_NON: "non",
	BLE_ADV_CONN_MODE_DIR: "dir",
	BLE_ADV_CONN_MODE_UND: "und",
}

func BleAdvConnModeToString(connMode BleAdvConnMode) string {
	s := BleAdvConnModeStringMap[connMode]
	if s == "" {
		return "???"
	}

	return s
}

func BleAdvConnModeFromString(s string) (BleAdvConnMode, error) {
	for advConnMode, name := range BleAdvConnModeStringMap {
		if s == name {
			return advConnMode, nil
		}
	}

	return BleAdvConnMode(0),
		fmt.Errorf("Invalid BleAdvConnMode string: %s", s)
}

func (a BleAdvConnMode) MarshalJSON() ([]byte, error) {
	return json.Marshal(BleAdvConnModeToString(a))
}

func (a *BleAdvConnMode) UnmarshalJSON(data []byte) error {
	var err error

	var s string
	if err := json.Unmarshal(data, &s); err != nil {
		return err
	}

	*a, err = BleAdvConnModeFromString(s)
	return err
}

type BleAdvDiscMode int

const (
	BLE_ADV_DISC_MODE_NON BleAdvDiscMode = iota
	BLE_ADV_DISC_MODE_LTD
	BLE_ADV_DISC_MODE_GEN
)

var BleAdvDiscModeStringMap = map[BleAdvDiscMode]string{
	BLE_ADV_DISC_MODE_NON: "non",
	BLE_ADV_DISC_MODE_LTD: "ltd",
	BLE_ADV_DISC_MODE_GEN: "gen",
}

func BleAdvDiscModeToString(discMode BleAdvDiscMode) string {
	s := BleAdvDiscModeStringMap[discMode]
	if s == "" {
		return "???"
	}

	return s
}

func BleAdvDiscModeFromString(s string) (BleAdvDiscMode, error) {
	for advDiscMode, name := range BleAdvDiscModeStringMap {
		if s == name {
			return advDiscMode, nil
		}
	}

	return BleAdvDiscMode(0),
		fmt.Errorf("Invalid BleAdvDiscMode string: %s", s)
}

func (a BleAdvDiscMode) MarshalJSON() ([]byte, error) {
	return json.Marshal(BleAdvDiscModeToString(a))
}

func (a *BleAdvDiscMode) UnmarshalJSON(data []byte) error {
	var err error

	var s string
	if err := json.Unmarshal(data, &s); err != nil {
		return err
	}

	*a, err = BleAdvDiscModeFromString(s)
	return err
}

type BleAdvFilterPolicy int

const (
	BLE_ADV_FILTER_POLICY_NONE BleAdvFilterPolicy = iota
	BLE_ADV_FILTER_POLICY_SCAN
	BLE_ADV_FILTER_POLICY_CONN
	BLE_ADV_FILTER_POLICY_BOTH
)

var BleAdvFilterPolicyStringMap = map[BleAdvFilterPolicy]string{
	BLE_ADV_FILTER_POLICY_NONE: "none",
	BLE_ADV_FILTER_POLICY_SCAN: "scan",
	BLE_ADV_FILTER_POLICY_CONN: "conn",
	BLE_ADV_FILTER_POLICY_BOTH: "both",
}

func BleAdvFilterPolicyToString(discMode BleAdvFilterPolicy) string {
	s := BleAdvFilterPolicyStringMap[discMode]
	if s == "" {
		return "???"
	}

	return s
}

func BleAdvFilterPolicyFromString(s string) (BleAdvFilterPolicy, error) {
	for advFilterPolicy, name := range BleAdvFilterPolicyStringMap {
		if s == name {
			return advFilterPolicy, nil
		}
	}

	return BleAdvFilterPolicy(0),
		fmt.Errorf("Invalid BleAdvFilterPolicy string: %s", s)
}

func (a BleAdvFilterPolicy) MarshalJSON() ([]byte, error) {
	return json.Marshal(BleAdvFilterPolicyToString(a))
}

func (a *BleAdvFilterPolicy) UnmarshalJSON(data []byte) error {
	var err error

	var s string
	if err := json.Unmarshal(data, &s); err != nil {
		return err
	}

	*a, err = BleAdvFilterPolicyFromString(s)
	return err
}

type BleAdvFields struct {
	Data []byte

	// Each field is only present if the sender included it in its
	// advertisement.
	Flags              *uint8
	Uuids16            []BleUuid16
	Uuids16IsComplete  bool
	Uuids32            []uint32
	Uuids32IsComplete  bool
	Uuids128           []BleUuid128
	Uuids128IsComplete bool
	Name               *string
	NameIsComplete     bool
	TxPwrLvl           *int8
	SlaveItvlMin       *uint16
	SlaveItvlMax       *uint16
	SvcDataUuid16      []byte
	PublicTgtAddrs     []BleAddr
	Appearance         *uint16
	AdvItvl            *uint16
	SvcDataUuid32      []byte
	SvcDataUuid128     []byte
	Uri                *string
	MfgData            []byte
}

type BleAdvReport struct {
	// These fields are always present.
	EventType BleAdvEventType
	Sender    BleDev
	Rssi      int8

	Fields BleAdvFields
}

type BleAdvRptFn func(r BleAdvReport)
type BleAdvPredicate func(adv BleAdvReport) bool

type BleRole int

const (
	BLE_ROLE_MASTER BleRole = iota
	BLE_ROLE_SLAVE
)

type BleConnDesc struct {
	ConnHandle      uint16
	OwnIdAddrType   BleAddrType
	OwnIdAddr       BleAddr
	OwnOtaAddrType  BleAddrType
	OwnOtaAddr      BleAddr
	PeerIdAddrType  BleAddrType
	PeerIdAddr      BleAddr
	PeerOtaAddrType BleAddrType
	PeerOtaAddr     BleAddr
	Role            BleRole
	Encrypted       bool
	Authenticated   bool
	Bonded          bool
	KeySize         int
}

func (d *BleConnDesc) String() string {
	return fmt.Sprintf("conn_handle=%d "+
		"own_id_addr=%s,%s own_ota_addr=%s,%s "+
		"peer_id_addr=%s,%s peer_ota_addr=%s,%s",
		d.ConnHandle,
		BleAddrTypeToString(d.OwnIdAddrType),
		d.OwnIdAddr.String(),
		BleAddrTypeToString(d.OwnOtaAddrType),
		d.OwnOtaAddr.String(),
		BleAddrTypeToString(d.PeerIdAddrType),
		d.PeerIdAddr.String(),
		BleAddrTypeToString(d.PeerOtaAddrType),
		d.PeerOtaAddr.String())
}

type BleEncryptWhen int

const (
	BLE_ENCRYPT_NEVER BleEncryptWhen = iota
	BLE_ENCRYPT_AS_REQD
	BLE_ENCRYPT_ALWAYS
)

type BleGattOp int

const (
	BLE_GATT_ACCESS_OP_READ_CHR  BleGattOp = 0
	BLE_GATT_ACCESS_OP_WRITE_CHR           = 1
	BLE_GATT_ACCESS_OP_READ_DSC            = 2
	BLE_GATT_ACCESS_OP_WRITE_DSC           = 3
)

var BleGattOpStringMap = map[BleGattOp]string{
	BLE_GATT_ACCESS_OP_READ_CHR:  "read_chr",
	BLE_GATT_ACCESS_OP_WRITE_CHR: "write_chr",
	BLE_GATT_ACCESS_OP_READ_DSC:  "read_dsc",
	BLE_GATT_ACCESS_OP_WRITE_DSC: "write_dsc",
}

func BleGattOpToString(op BleGattOp) string {
	s := BleGattOpStringMap[op]
	if s == "" {
		return "???"
	}

	return s
}

func BleGattOpFromString(s string) (BleGattOp, error) {
	for op, name := range BleGattOpStringMap {
		if s == name {
			return op, nil
		}
	}

	return BleGattOp(0),
		fmt.Errorf("Invalid BleGattOp string: %s", s)
}

type BleSvcType int

const (
	BLE_SVC_TYPE_PRIMARY BleSvcType = iota
	BLE_SVC_TYPE_SECONDARY
)

var BleSvcTypeStringMap = map[BleSvcType]string{
	BLE_SVC_TYPE_PRIMARY:   "primary",
	BLE_SVC_TYPE_SECONDARY: "secondary",
}

func BleSvcTypeToString(svcType BleSvcType) string {
	s := BleSvcTypeStringMap[svcType]
	if s == "" {
		return "???"
	}

	return s
}

func BleSvcTypeFromString(s string) (BleSvcType, error) {
	for svcType, name := range BleSvcTypeStringMap {
		if s == name {
			return svcType, nil
		}
	}

	return BleSvcType(0),
		fmt.Errorf("Invalid BleSvcType string: %s", s)
}

func (a BleSvcType) MarshalJSON() ([]byte, error) {
	return json.Marshal(BleSvcTypeToString(a))
}

func (a *BleSvcType) UnmarshalJSON(data []byte) error {
	var err error

	var s string
	if err := json.Unmarshal(data, &s); err != nil {
		return err
	}

	*a, err = BleSvcTypeFromString(s)
	return err
}

type BleChrFlags int

const (
	BLE_GATT_F_BROADCAST       BleChrFlags = 0x0001
	BLE_GATT_F_READ                        = 0x0002
	BLE_GATT_F_WRITE_NO_RSP                = 0x0004
	BLE_GATT_F_WRITE                       = 0x0008
	BLE_GATT_F_NOTIFY                      = 0x0010
	BLE_GATT_F_INDICATE                    = 0x0020
	BLE_GATT_F_AUTH_SIGN_WRITE             = 0x0040
	BLE_GATT_F_RELIABLE_WRITE              = 0x0080
	BLE_GATT_F_AUX_WRITE                   = 0x0100
	BLE_GATT_F_READ_ENC                    = 0x0200
	BLE_GATT_F_READ_AUTHEN                 = 0x0400
	BLE_GATT_F_READ_AUTHOR                 = 0x0800
	BLE_GATT_F_WRITE_ENC                   = 0x1000
	BLE_GATT_F_WRITE_AUTHEN                = 0x2000
	BLE_GATT_F_WRITE_AUTHOR                = 0x4000
)

type BleAttFlags int

const (
	BLE_ATT_F_READ         BleAttFlags = 0x01
	BLE_ATT_F_WRITE                    = 0x02
	BLE_ATT_F_READ_ENC                 = 0x04
	BLE_ATT_F_READ_AUTHEN              = 0x08
	BLE_ATT_F_READ_AUTHOR              = 0x10
	BLE_ATT_F_WRITE_ENC                = 0x20
	BLE_ATT_F_WRITE_AUTHEN             = 0x40
	BLE_ATT_F_WRITE_AUTHOR             = 0x80
)

type BleDiscChrProperties int

const (
	BLE_DISC_CHR_PROP_BROADCAST       BleDiscChrProperties = 0x01
	BLE_DISC_CHR_PROP_READ                                 = 0x02
	BLE_DISC_CHR_PROP_WRITE_NO_RSP                         = 0x04
	BLE_DISC_CHR_PROP_WRITE                                = 0x08
	BLE_DISC_CHR_PROP_NOTIFY                               = 0x10
	BLE_DISC_CHR_PROP_INDICATE                             = 0x20
	BLE_DISC_CHR_PROP_AUTH_SIGN_WRITE                      = 0x40
	BLE_DISC_CHR_PROP_EXTENDED                             = 0x80
)

type BleGattAccess struct {
	Op         BleGattOp
	ConnHandle uint16
	SvcUuid    BleUuid
	ChrUuid    BleUuid
	Data       []byte
}

type BleGattAccessFn func(access BleGattAccess) (uint8, []byte)

type BleDsc struct {
	Uuid       BleUuid
	AttFlags   BleAttFlags
	MinKeySize int
}

type BleChr struct {
	Uuid       BleUuid
	Flags      BleChrFlags
	MinKeySize int
	AccessCb   BleGattAccessFn
	Dscs       []BleDsc
}

type BleSvc struct {
	Uuid    BleUuid
	SvcType BleSvcType
	Chrs    []BleChr
}

type BleChrId struct {
	SvcUuid BleUuid
	ChrUuid BleUuid
}

func (b *BleChrId) String() string {
	return fmt.Sprintf("s=%s c=%s", b.SvcUuid.String(), b.ChrUuid.String())
}

func CompareChrIds(a BleChrId, b BleChrId) int {
	if rc := CompareUuids(a.SvcUuid, b.SvcUuid); rc != 0 {
		return rc
	}
	if rc := CompareUuids(a.ChrUuid, b.ChrUuid); rc != 0 {
		return rc
	}
	return 0
}

type BleMgmtChrs struct {
	NmpReqChr *BleChrId
	NmpRspChr *BleChrId
	ResReqChr *BleChrId
	ResRspChr *BleChrId
}

type BleSmAction int

const (
	BLE_SM_ACTION_OOB BleSmAction = iota
	BLE_SM_ACTION_INPUT
	BLE_SM_ACTION_DISP
	BLE_SM_ACTION_NUMCMP
)

var BleSmActionStringMap = map[BleSmAction]string{
	BLE_SM_ACTION_OOB:    "oob",
	BLE_SM_ACTION_INPUT:  "input",
	BLE_SM_ACTION_DISP:   "disp",
	BLE_SM_ACTION_NUMCMP: "numcmp",
}

func BleSmActionToString(smAction BleSmAction) string {
	s := BleSmActionStringMap[smAction]
	if s == "" {
		return "???"
	}

	return s
}

func BleSmActionFromString(s string) (BleSmAction, error) {
	for smAction, name := range BleSmActionStringMap {
		if s == name {
			return smAction, nil
		}
	}

	return BleSmAction(0),
		fmt.Errorf("Invalid BleSmAction string: %s", s)
}

func (io BleSmAction) String() string {
	return BleSmActionToString(io)
}

func (a BleSmAction) MarshalJSON() ([]byte, error) {
	return json.Marshal(BleSmActionToString(a))
}

func (a *BleSmAction) UnmarshalJSON(data []byte) error {
	var err error

	var s string
	if err := json.Unmarshal(data, &s); err != nil {
		return err
	}

	*a, err = BleSmActionFromString(s)
	return err
}

type BleSmIoCap int

const (
	BLE_SM_IO_CAP_DISP_ONLY BleSmIoCap = iota
	BLE_SM_IO_CAP_DISP_YES_NO
	BLE_SM_IO_CAP_KEYBOARD_ONLY
	BLE_SM_IO_CAP_NO_IO
	BLE_SM_IO_CAP_KEYBOARD_DISP
)

var BleSmIoCapStringMap = map[BleSmIoCap]string{
	BLE_SM_IO_CAP_DISP_ONLY:     "disp_only",
	BLE_SM_IO_CAP_DISP_YES_NO:   "disp_yes_no",
	BLE_SM_IO_CAP_KEYBOARD_ONLY: "keyboard_only",
	BLE_SM_IO_CAP_NO_IO:         "no_io",
	BLE_SM_IO_CAP_KEYBOARD_DISP: "keyboard_disp",
}

func BleSmIoCapToString(smAction BleSmIoCap) string {
	s := BleSmIoCapStringMap[smAction]
	if s == "" {
		return "???"
	}

	return s
}

func BleSmIoCapFromString(s string) (BleSmIoCap, error) {
	for smAction, name := range BleSmIoCapStringMap {
		if s == name {
			return smAction, nil
		}
	}

	return BleSmIoCap(0),
		fmt.Errorf("Invalid BleSmIoCap string: %s", s)
}

func (io BleSmIoCap) String() string {
	return BleSmIoCapToString(io)
}

func (a BleSmIoCap) MarshalJSON() ([]byte, error) {
	return json.Marshal(BleSmIoCapToString(a))
}

func (a *BleSmIoCap) UnmarshalJSON(data []byte) error {
	var err error

	var s string
	if err := json.Unmarshal(data, &s); err != nil {
		return err
	}

	*a, err = BleSmIoCapFromString(s)
	return err
}

type BleSmKeyDist int

const (
	BLE_SM_KEY_DIST_ENC BleSmKeyDist = iota
	BLE_SM_KEY_DIST_ID
	BLE_SM_KEY_DIST_SIGN
	BLE_SM_KEY_DIST_LINK
)

var BleSmKeyDistStringMap = map[BleSmKeyDist]string{
	BLE_SM_KEY_DIST_ENC:  "enc",
	BLE_SM_KEY_DIST_ID:   "id",
	BLE_SM_KEY_DIST_SIGN: "sign",
	BLE_SM_KEY_DIST_LINK: "link",
}

func BleSmKeyDistToString(smAction BleSmKeyDist) string {
	s := BleSmKeyDistStringMap[smAction]
	if s == "" {
		return "???"
	}

	return s
}

func BleSmKeyDistFromString(s string) (BleSmKeyDist, error) {
	for smAction, name := range BleSmKeyDistStringMap {
		if s == name {
			return smAction, nil
		}
	}

	return BleSmKeyDist(0),
		fmt.Errorf("Invalid BleSmKeyDist string: %s", s)
}

func (io BleSmKeyDist) String() string {
	return BleSmKeyDistToString(io)
}

func (a BleSmKeyDist) MarshalJSON() ([]byte, error) {
	return json.Marshal(BleSmKeyDistToString(a))
}

func (a *BleSmKeyDist) UnmarshalJSON(data []byte) error {
	var err error

	var s string
	if err := json.Unmarshal(data, &s); err != nil {
		return err
	}

	*a, err = BleSmKeyDistFromString(s)
	return err
}

type BleSmAuthReq int

const (
	BLE_SM_AUTHREQ_BOND BleSmAuthReq = iota
	BLE_SM_AUTHREQ_MITM
	BLE_SM_AUTHREQ_SC
	BLE_SM_AUTHREQ_KEYPRESS
)

var BleSmAuthReqStringMap = map[BleSmAuthReq]string{
	BLE_SM_AUTHREQ_BOND:     "bond",
	BLE_SM_AUTHREQ_MITM:     "mitm",
	BLE_SM_AUTHREQ_SC:       "sc",
	BLE_SM_AUTHREQ_KEYPRESS: "keypress",
}

func BleSmAuthReqToString(smAction BleSmAuthReq) string {
	s := BleSmAuthReqStringMap[smAction]
	if s == "" {
		return "???"
	}

	return s
}

func BleSmAuthReqFromString(s string) (BleSmAuthReq, error) {
	for smAction, name := range BleSmAuthReqStringMap {
		if s == name {
			return smAction, nil
		}
	}

	return BleSmAuthReq(0),
		fmt.Errorf("Invalid BleSmAuthReq string: %s", s)
}

func (io BleSmAuthReq) String() string {
	return BleSmAuthReqToString(io)
}

func (a BleSmAuthReq) MarshalJSON() ([]byte, error) {
	return json.Marshal(BleSmAuthReqToString(a))
}

func (a *BleSmAuthReq) UnmarshalJSON(data []byte) error {
	var err error

	var s string
	if err := json.Unmarshal(data, &s); err != nil {
		return err
	}

	*a, err = BleSmAuthReqFromString(s)
	return err
}

type BlePairCfg struct {
	IoCap        BleSmIoCap
	Oob          bool
	Bonding      bool
	Mitm         bool
	Sc           bool
	Keypress     bool
	OurKeyDist   BleSmKeyDist
	TheirKeyDist BleSmKeyDist
}
