/**
 * 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 nmble

import (
	"fmt"
	"sync"
	"time"

	log "github.com/sirupsen/logrus"

	. "mynewt.apache.org/newtmgr/nmxact/bledefs"
	"mynewt.apache.org/newtmgr/nmxact/nmxutil"
	"mynewt.apache.org/newtmgr/nmxact/sesn"
)

const WRITE_CMD_BASE_SZ = 3
const NOTIFY_CMD_BASE_SZ = 3

var nextSeq BleSeq = BLE_SEQ_MIN
var seqMtx sync.Mutex

func NextSeq() BleSeq {
	seqMtx.Lock()
	defer seqMtx.Unlock()

	seq := nextSeq
	nextSeq++
	if nextSeq >= BLE_SEQ_EVT_MIN {
		nextSeq = BLE_SEQ_MIN
	}

	return seq
}

func BhdTimeoutError(rspType MsgType, seq BleSeq) error {
	str := fmt.Sprintf(
		"Timeout waiting for blehostd to send %s response (seq=%d)",
		MsgTypeToString(rspType), seq)

	log.Debug(str)

	return nmxutil.NewXportError(str)
}

func StatusError(op MsgOp, msgType MsgType, status int) error {
	str := fmt.Sprintf("%s %s indicates error: %s (%d)",
		MsgOpToString(op),
		MsgTypeToString(msgType),
		ErrCodeToString(status),
		status)

	log.Debug(str)
	return nmxutil.NewBleHostError(status, str)
}

func BleDescFromConnFindRsp(r *BleConnFindRsp) BleConnDesc {
	return BleConnDesc{
		ConnHandle:      r.ConnHandle,
		OwnIdAddrType:   r.OwnIdAddrType,
		OwnIdAddr:       r.OwnIdAddr,
		OwnOtaAddrType:  r.OwnOtaAddrType,
		OwnOtaAddr:      r.OwnOtaAddr,
		PeerIdAddrType:  r.PeerIdAddrType,
		PeerIdAddr:      r.PeerIdAddr,
		PeerOtaAddrType: r.PeerOtaAddrType,
		PeerOtaAddr:     r.PeerOtaAddr,
		Role:            r.Role,
		Encrypted:       r.Encrypted,
		Authenticated:   r.Authenticated,
		Bonded:          r.Bonded,
		KeySize:         r.KeySize,
	}
}

func BleAdvReportFromScanEvt(e *BleScanEvt) BleAdvReport {
	return BleAdvReport{
		EventType: e.EventType,
		Sender: BleDev{
			AddrType: e.AddrType,
			Addr:     e.Addr,
		},
		Rssi: e.Rssi,

		Fields: BleAdvFields{
			Data: e.Data.Bytes,

			Flags:              e.DataFlags,
			Uuids16:            e.DataUuids16,
			Uuids16IsComplete:  e.DataUuids16IsComplete,
			Uuids32:            e.DataUuids32,
			Uuids32IsComplete:  e.DataUuids32IsComplete,
			Uuids128:           e.DataUuids128,
			Uuids128IsComplete: e.DataUuids128IsComplete,
			Name:               e.DataName,
			NameIsComplete:     e.DataNameIsComplete,
			TxPwrLvl:           e.DataTxPwrLvl,
			SlaveItvlMin:       e.DataSlaveItvlMin,
			SlaveItvlMax:       e.DataSlaveItvlMax,
			SvcDataUuid16:      e.DataSvcDataUuid16.Bytes,
			PublicTgtAddrs:     e.DataPublicTgtAddrs,
			Appearance:         e.DataAppearance,
			AdvItvl:            e.DataAdvItvl,
			SvcDataUuid32:      e.DataSvcDataUuid32.Bytes,
			SvcDataUuid128:     e.DataSvcDataUuid128.Bytes,
			Uri:                e.DataUri,
			MfgData:            e.DataMfgData.Bytes,
		},
	}
}

func NewBleConnectReq() *BleConnectReq {
	return &BleConnectReq{
		Op:   MSG_OP_REQ,
		Type: MSG_TYPE_CONNECT,
		Seq:  NextSeq(),

		OwnAddrType:  BLE_ADDR_TYPE_PUBLIC,
		PeerAddrType: BLE_ADDR_TYPE_PUBLIC,
		PeerAddr:     BleAddr{},

		DurationMs:         30000,
		ScanItvl:           0x0010,
		ScanWindow:         0x0010,
		ItvlMin:            24,
		ItvlMax:            40,
		Latency:            0,
		SupervisionTimeout: 0x0200,
		MinCeLen:           0x0010,
		MaxCeLen:           0x0300,
	}
}

func NewBleTerminateReq() *BleTerminateReq {
	return &BleTerminateReq{
		Op:   MSG_OP_REQ,
		Type: MSG_TYPE_TERMINATE,
		Seq:  NextSeq(),

		ConnHandle: 0,
		HciReason:  0,
	}
}

func NewBleConnCancelReq() *BleConnCancelReq {
	return &BleConnCancelReq{
		Op:   MSG_OP_REQ,
		Type: MSG_TYPE_CONN_CANCEL,
		Seq:  NextSeq(),
	}
}

func NewBleDiscAllSvcsReq() *BleDiscAllSvcsReq {
	return &BleDiscAllSvcsReq{
		Op:   MSG_OP_REQ,
		Type: MSG_TYPE_DISC_ALL_SVCS,
		Seq:  NextSeq(),
	}
}

func NewBleDiscSvcUuidReq() *BleDiscSvcUuidReq {
	return &BleDiscSvcUuidReq{
		Op:   MSG_OP_REQ,
		Type: MSG_TYPE_DISC_SVC_UUID,
		Seq:  NextSeq(),

		ConnHandle: 0,
		Uuid:       BleUuid{},
	}
}

func NewBleDiscAllChrsReq() *BleDiscAllChrsReq {
	return &BleDiscAllChrsReq{
		Op:   MSG_OP_REQ,
		Type: MSG_TYPE_DISC_ALL_CHRS,
		Seq:  NextSeq(),
	}
}

func NewBleDiscAllDscsReq() *BleDiscAllDscsReq {
	return &BleDiscAllDscsReq{
		Op:   MSG_OP_REQ,
		Type: MSG_TYPE_DISC_ALL_DSCS,
		Seq:  NextSeq(),
	}
}

func NewBleExchangeMtuReq() *BleExchangeMtuReq {
	return &BleExchangeMtuReq{
		Op:   MSG_OP_REQ,
		Type: MSG_TYPE_EXCHANGE_MTU,
		Seq:  NextSeq(),

		ConnHandle: 0,
	}
}

func NewBleGenRandAddrReq() *BleGenRandAddrReq {
	return &BleGenRandAddrReq{
		Op:   MSG_OP_REQ,
		Type: MSG_TYPE_GEN_RAND_ADDR,
		Seq:  NextSeq(),
	}
}

func NewBleSetRandAddrReq() *BleSetRandAddrReq {
	return &BleSetRandAddrReq{
		Op:   MSG_OP_REQ,
		Type: MSG_TYPE_SET_RAND_ADDR,
		Seq:  NextSeq(),
	}
}

func NewBleWriteCmdReq() *BleWriteCmdReq {
	return &BleWriteCmdReq{
		Op:   MSG_OP_REQ,
		Type: MSG_TYPE_WRITE_CMD,
		Seq:  NextSeq(),
	}
}

func NewBleWriteReq() *BleWriteReq {
	return &BleWriteReq{
		Op:   MSG_OP_REQ,
		Type: MSG_TYPE_WRITE,
		Seq:  NextSeq(),
	}
}

func NewBleScanReq() *BleScanReq {
	return &BleScanReq{
		Op:   MSG_OP_REQ,
		Type: MSG_TYPE_SCAN,
		Seq:  NextSeq(),
	}
}

func NewBleScanCancelReq() *BleScanCancelReq {
	return &BleScanCancelReq{
		Op:   MSG_OP_REQ,
		Type: MSG_TYPE_SCAN_CANCEL,
		Seq:  NextSeq(),
	}
}

func NewBleSetPreferredMtuReq() *BleSetPreferredMtuReq {
	return &BleSetPreferredMtuReq{
		Op:   MSG_OP_REQ,
		Type: MSG_TYPE_SET_PREFERRED_MTU,
		Seq:  NextSeq(),
	}
}

func NewBleConnFindReq() *BleConnFindReq {
	return &BleConnFindReq{
		Op:   MSG_OP_REQ,
		Type: MSG_TYPE_CONN_FIND,
		Seq:  NextSeq(),
	}
}

func NewResetReq() *BleResetReq {
	return &BleResetReq{
		Op:   MSG_OP_REQ,
		Type: MSG_TYPE_RESET,
		Seq:  NextSeq(),
	}
}

func NewBleSecurityInitiateReq() *BleSecurityInitiateReq {
	return &BleSecurityInitiateReq{
		Op:   MSG_OP_REQ,
		Type: MSG_TYPE_SECURITY_INITIATE,
		Seq:  NextSeq(),
	}
}

func NewBleAdvFieldsReq() *BleAdvFieldsReq {
	return &BleAdvFieldsReq{
		Op:   MSG_OP_REQ,
		Type: MSG_TYPE_ADV_FIELDS,
		Seq:  NextSeq(),
	}
}

func NewBleAdvSetDataReq() *BleAdvSetDataReq {
	return &BleAdvSetDataReq{
		Op:   MSG_OP_REQ,
		Type: MSG_TYPE_ADV_SET_DATA,
		Seq:  NextSeq(),
	}
}

func NewBleAdvRspSetDataReq() *BleAdvRspSetDataReq {
	return &BleAdvRspSetDataReq{
		Op:   MSG_OP_REQ,
		Type: MSG_TYPE_ADV_RSP_SET_DATA,
		Seq:  NextSeq(),
	}
}

func NewBleAdvStartReq() *BleAdvStartReq {
	return &BleAdvStartReq{
		Op:   MSG_OP_REQ,
		Type: MSG_TYPE_ADV_START,
		Seq:  NextSeq(),
	}
}

func NewBleAdvStopReq() *BleAdvStopReq {
	return &BleAdvStopReq{
		Op:   MSG_OP_REQ,
		Type: MSG_TYPE_ADV_STOP,
		Seq:  NextSeq(),
	}
}

func NewBleClearSvcsReq() *BleClearSvcsReq {
	return &BleClearSvcsReq{
		Op:   MSG_OP_REQ,
		Type: MSG_TYPE_CLEAR_SVCS,
		Seq:  NextSeq(),
	}
}

func NewBleAddSvcsReq() *BleAddSvcsReq {
	return &BleAddSvcsReq{
		Op:   MSG_OP_REQ,
		Type: MSG_TYPE_ADD_SVCS,
		Seq:  NextSeq(),
	}
}

func NewBleCommitSvcsReq() *BleCommitSvcsReq {
	return &BleCommitSvcsReq{
		Op:   MSG_OP_REQ,
		Type: MSG_TYPE_COMMIT_SVCS,
		Seq:  NextSeq(),
	}
}

func NewAccessStatusReq() *BleAccessStatusReq {
	return &BleAccessStatusReq{
		Op:   MSG_OP_REQ,
		Type: MSG_TYPE_ACCESS_STATUS,
		Seq:  NextSeq(),
	}
}

func NewNotifyReq() *BleNotifyReq {
	return &BleNotifyReq{
		Op:   MSG_OP_REQ,
		Type: MSG_TYPE_NOTIFY,
		Seq:  NextSeq(),
	}
}

func NewFindChrReq() *BleFindChrReq {
	return &BleFindChrReq{
		Op:   MSG_OP_REQ,
		Type: MSG_TYPE_FIND_CHR,
		Seq:  NextSeq(),
	}
}

func NewSyncReq() *BleSyncReq {
	return &BleSyncReq{
		Op:   MSG_OP_REQ,
		Type: MSG_TYPE_SYNC,
		Seq:  NextSeq(),
	}
}

func NewBleSmInjectIoReq() *BleSmInjectIoReq {
	return &BleSmInjectIoReq{
		Op:   MSG_OP_REQ,
		Type: MSG_TYPE_SM_INJECT_IO,
		Seq:  NextSeq(),
	}
}

func ConnFindXact(x *BleXport, connHandle uint16) (BleConnDesc, error) {
	r := NewBleConnFindReq()
	r.ConnHandle = connHandle

	key := SeqKey(r.Seq)
	bl, err := x.AddListener(key)
	if err != nil {
		return BleConnDesc{}, err
	}
	defer x.RemoveListener(bl)

	return connFind(x, bl, r)
}

func GenRandAddrXact(x *BleXport) (BleAddr, error) {
	r := NewBleGenRandAddrReq()

	key := SeqKey(r.Seq)
	bl, err := x.AddListener(key)
	if err != nil {
		return BleAddr{}, err
	}
	defer x.RemoveListener(bl)

	return genRandAddr(x, bl, r)
}

func SetRandAddrXact(x *BleXport, addr BleAddr) error {
	r := NewBleSetRandAddrReq()
	r.Addr = addr

	key := SeqKey(r.Seq)
	bl, err := x.AddListener(key)
	if err != nil {
		return err
	}
	defer x.RemoveListener(bl)

	return setRandAddr(x, bl, r)
}

func SetPreferredMtuXact(x *BleXport, mtu uint16) error {
	r := NewBleSetPreferredMtuReq()
	r.Mtu = mtu

	key := SeqKey(r.Seq)
	bl, err := x.AddListener(key)
	if err != nil {
		return err
	}
	defer x.RemoveListener(bl)

	return setPreferredMtu(x, bl, r)
}

func ResetXact(x *BleXport) error {
	r := NewResetReq()

	key := SeqKey(r.Seq)
	bl, err := x.AddListener(key)
	if err != nil {
		return err
	}
	defer x.RemoveListener(bl)

	return reset(x, bl, r)
}

func ClearSvcsXact(x *BleXport) error {
	r := NewBleClearSvcsReq()

	bl, err := x.AddListener(SeqKey(r.Seq))
	if err != nil {
		return err
	}
	defer x.RemoveListener(bl)

	return clearSvcs(x, bl, r)
}

func AddSvcsXact(x *BleXport, svcs []BleAddSvc) error {
	r := NewBleAddSvcsReq()
	r.Svcs = svcs

	bl, err := x.AddListener(SeqKey(r.Seq))
	if err != nil {
		return err
	}
	defer x.RemoveListener(bl)

	return addSvcs(x, bl, r)
}

func CommitSvcsXact(x *BleXport) ([]BleRegSvc, error) {
	r := NewBleCommitSvcsReq()

	bl, err := x.AddListener(SeqKey(r.Seq))
	if err != nil {
		return nil, err
	}
	defer x.RemoveListener(bl)

	return commitSvcs(x, bl, r)
}

func AccessStatusXact(x *BleXport, attStatus uint8, data []byte) error {
	r := NewAccessStatusReq()
	r.AttStatus = attStatus
	r.Data.Bytes = data

	bl, err := x.AddListener(SeqKey(r.Seq))
	if err != nil {
		return err
	}
	defer x.RemoveListener(bl)

	return accessStatus(x, bl, r)
}

func NotifyXact(x *BleXport, connHandle uint16, attrHandle uint16,
	data []byte) error {

	r := NewNotifyReq()
	r.ConnHandle = connHandle
	r.AttrHandle = attrHandle
	r.Data.Bytes = data

	bl, err := x.AddListener(SeqKey(r.Seq))
	if err != nil {
		return err
	}
	defer x.RemoveListener(bl)

	return notify(x, bl, r)
}

func FindChrXact(x *BleXport, svcUuid BleUuid, chrUuid BleUuid) (
	uint16, uint16, error) {

	r := NewFindChrReq()
	r.SvcUuid = svcUuid
	r.ChrUuid = chrUuid

	bl, err := x.AddListener(SeqKey(r.Seq))
	if err != nil {
		return 0, 0, err
	}
	defer x.RemoveListener(bl)

	return findChr(x, bl, r)
}

func SyncXact(x *BleXport) (bool, error) {
	r := NewSyncReq()

	bl, err := x.AddListener(SeqKey(r.Seq))
	if err != nil {
		return false, err
	}

	synced, err := checkSync(x, bl, r)
	if err != nil {
		return false, err
	}

	return synced, nil
}

func DiscoverDeviceWithName(
	bx *BleXport,
	ownAddrType BleAddrType,
	timeout time.Duration,
	name string) (*BleDev, error) {

	advPred := func(r BleAdvReport) bool {
		return r.Fields.Name != nil && *r.Fields.Name == name
	}

	return DiscoverDevice(bx, ownAddrType, timeout, advPred)
}

func BleAdvFieldsToReq(f BleAdvFields) *BleAdvFieldsReq {
	r := NewBleAdvFieldsReq()

	r.Flags = f.Flags
	r.Uuids16 = f.Uuids16
	r.Uuids16IsComplete = f.Uuids16IsComplete
	r.Uuids32 = f.Uuids32
	r.Uuids32IsComplete = f.Uuids32IsComplete
	r.Uuids128 = f.Uuids128
	r.Uuids128IsComplete = f.Uuids128IsComplete
	r.Name = f.Name
	r.NameIsComplete = f.NameIsComplete
	r.TxPwrLvl = f.TxPwrLvl
	r.SlaveItvlMin = f.SlaveItvlMin
	r.SlaveItvlMax = f.SlaveItvlMax
	r.SvcDataUuid16 = BleBytes{f.SvcDataUuid16}
	r.PublicTgtAddrs = f.PublicTgtAddrs
	r.Appearance = f.Appearance
	r.AdvItvl = f.AdvItvl
	r.SvcDataUuid32 = BleBytes{f.SvcDataUuid32}
	r.SvcDataUuid128 = BleBytes{f.SvcDataUuid128}
	r.Uri = f.Uri
	r.MfgData = BleBytes{f.MfgData}

	return r
}

func BleSvcToAddSvc(svc BleSvc) BleAddSvc {
	as := BleAddSvc{
		Uuid:    svc.Uuid,
		SvcType: svc.SvcType,
	}

	for _, chr := range svc.Chrs {
		ac := BleAddChr{
			Uuid:       chr.Uuid,
			Flags:      chr.Flags,
			MinKeySize: chr.MinKeySize,
		}

		for _, dsc := range chr.Dscs {
			ad := BleAddDsc{
				Uuid:       dsc.Uuid,
				AttFlags:   dsc.AttFlags,
				MinKeySize: dsc.MinKeySize,
			}

			ac.Dscs = append(ac.Dscs, ad)
		}

		as.Chrs = append(as.Chrs, ac)
	}

	return as
}

func GapService(devName string) BleSvc {
	return BleSvc{
		Uuid:    BleUuid{0x1800, [16]byte{}},
		SvcType: BLE_SVC_TYPE_PRIMARY,
		Chrs: []BleChr{
			// Device name.
			BleChr{
				Uuid:       BleUuid{0x2a00, [16]byte{}},
				Flags:      BLE_GATT_F_READ,
				MinKeySize: 0,
				AccessCb: func(access BleGattAccess) (uint8, []byte) {
					return 0, []byte(devName)
				},
			},

			// Appearance.
			BleChr{
				Uuid:       BleUuid{0x2a01, [16]byte{}},
				Flags:      BLE_GATT_F_READ,
				MinKeySize: 0,
				AccessCb: func(access BleGattAccess) (uint8, []byte) {
					return 0, []byte{0, 0}
				},
			},

			// Peripheral privacy flag.
			BleChr{
				Uuid:       BleUuid{0x2a02, [16]byte{}},
				Flags:      BLE_GATT_F_READ,
				MinKeySize: 0,
				AccessCb: func(access BleGattAccess) (uint8, []byte) {
					return 0, []byte{0}
				},
			},

			// Reconnection address.
			BleChr{
				Uuid:       BleUuid{0x2a03, [16]byte{}},
				Flags:      BLE_GATT_F_READ,
				MinKeySize: 0,
				AccessCb: func(access BleGattAccess) (uint8, []byte) {
					return 0, []byte{0, 0, 0, 0, 0, 0}
				},
			},

			// Peripheral preferred connection parameters.
			BleChr{
				Uuid:       BleUuid{0x2a04, [16]byte{}},
				Flags:      BLE_GATT_F_READ,
				MinKeySize: 0,
				AccessCb: func(access BleGattAccess) (uint8, []byte) {
					return 0, []byte{0, 0, 0, 0, 0, 0, 0, 0}
				},
			},
		},
	}
}

func GattService() BleSvc {
	return BleSvc{
		Uuid:    BleUuid{0x1801, [16]byte{}},
		SvcType: BLE_SVC_TYPE_PRIMARY,
		Chrs: []BleChr{
			// Device name.
			BleChr{
				Uuid:       BleUuid{0x2a05, [16]byte{}},
				Flags:      BLE_GATT_F_INDICATE,
				MinKeySize: 0,
				AccessCb: func(access BleGattAccess) (uint8, []byte) {
					return 0, []byte{0, 0, 0, 0}
				},
			},
		},
	}
}

func BuildMgmtChrs(mgmtProto sesn.MgmtProto) (BleMgmtChrs, error) {
	mgmtChrs := BleMgmtChrs{}

	nmpSvcUuid, _ := ParseUuid(NmpPlainSvcUuid)
	nmpChrUuid, _ := ParseUuid(NmpPlainChrUuid)

	ompSvcUuid, _ := ParseUuid(OmpUnsecSvcUuid)
	ompReqChrUuid, _ := ParseUuid(OmpUnsecReqChrUuid)
	ompRspChrUuid, _ := ParseUuid(OmpUnsecRspChrUuid)

	resSvcUuid, _ := ParseUuid(IotivitySvcUuid)
	resReqChrUuid, _ := ParseUuid(IotivityReqChrUuid)
	resRspChrUuid, _ := ParseUuid(IotivityRspChrUuid)

	switch mgmtProto {
	case sesn.MGMT_PROTO_NMP:
		mgmtChrs.NmpReqChr = &BleChrId{nmpSvcUuid, nmpChrUuid}
		mgmtChrs.NmpRspChr = &BleChrId{nmpSvcUuid, nmpChrUuid}

	case sesn.MGMT_PROTO_OMP:
		mgmtChrs.NmpReqChr = &BleChrId{ompSvcUuid, ompReqChrUuid}
		mgmtChrs.NmpRspChr = &BleChrId{ompSvcUuid, ompRspChrUuid}

	default:
		return mgmtChrs,
			fmt.Errorf("invalid management protocol: %+v", mgmtProto)
	}

	mgmtChrs.ResReqChr = &BleChrId{resSvcUuid, resReqChrUuid}
	mgmtChrs.ResRspChr = &BleChrId{resSvcUuid, resRspChrUuid}

	return mgmtChrs, nil
}

func IsSecErr(err error) bool {
	bhdErr := nmxutil.ToBleHost(err)
	if bhdErr == nil {
		return false
	}

	switch bhdErr.Status - ERR_CODE_ATT_BASE {
	case ERR_CODE_ATT_INSUFFICIENT_AUTHEN,
		ERR_CODE_ATT_INSUFFICIENT_AUTHOR,
		ERR_CODE_ATT_INSUFFICIENT_KEY_SZ,
		ERR_CODE_ATT_INSUFFICIENT_ENC:

		return true

	default:
		return false
	}
}

// Attempts to convert the given error to a BLE security error.  The conversion
// succeeds if the error represents a pairing failure due to missing or
// mismatched key material.
func ToSecurityErr(err error) error {
	bhe := nmxutil.ToBleHost(err)
	if bhe == nil {
		return nil
	}

	code := ErrCodeToSmUs(bhe.Status)
	if code == -1 {
		code = ErrCodeToSmPeer(bhe.Status)
	}

	switch code {
	case ERR_CODE_SM_ERR_PASSKEY,
		ERR_CODE_SM_ERR_OOB,
		ERR_CODE_SM_ERR_CONFIRM_MISMATCH,
		ERR_CODE_SM_ERR_UNSPECIFIED,
		ERR_CODE_SM_ERR_DHKEY,
		ERR_CODE_SM_ERR_NUMCMP:
		return nmxutil.NewBleSecurityError(err.Error())

	default:
		return nil
	}
}
